• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import fs from 'fs';
17import path from 'path';
18import * as ts from 'typescript';
19const fse = require('fs-extra');
20
21import {
22  projectConfig,
23  systemModules,
24  globalProgram,
25  sdkConfigs,
26  sdkConfigPrefix,
27  ohosSystemModulePaths,
28  partialUpdateConfig
29} from '../main';
30import {
31  preprocessExtend,
32  preprocessNewExtend
33} from './validate_ui_syntax';
34import {
35  INNER_COMPONENT_MEMBER_DECORATORS,
36  COMPONENT_DECORATORS_PARAMS,
37  COMPONENT_BUILD_FUNCTION,
38  STYLE_ADD_DOUBLE_DOLLAR,
39  $$,
40  PROPERTIES_ADD_DOUBLE_DOLLAR,
41  $$_BLOCK_INTERFACE,
42  COMPONENT_EXTEND_DECORATOR,
43  COMPONENT_BUILDER_DECORATOR,
44  ESMODULE,
45  EXTNAME_D_ETS,
46  EXTNAME_JS,
47  FOREACH_LAZYFOREACH,
48  COMPONENT_IF,
49  TS_WATCH_END_MSG
50} from './pre_define';
51import { getName } from './process_component_build';
52import {
53  INNER_COMPONENT_NAMES,
54  JS_BIND_COMPONENTS
55} from './component_map';
56import {
57  props,
58  logger
59} from './compile_info';
60import { hasDecorator } from './utils';
61import { generateSourceFilesInHar } from './utils';
62import { isExtendFunction, isOriginalExtend } from './process_ui_syntax';
63import { visualTransform } from './process_visual';
64import { tsWatchEmitter } from './fast_build/ets_ui/rollup-plugin-ets-checker';
65import { doArkTSLinter, ArkTSLinterMode } from './do_arkTS_linter';
66
67export const SOURCE_FILES: Map<string, ts.SourceFile> = new Map();
68
69function collectSourceFilesMap(program: ts.Program): void {
70  program.getSourceFiles().forEach((sourceFile: ts.SourceFile) => {
71    SOURCE_FILES.set(path.normalize(sourceFile.fileName), sourceFile);
72  });
73}
74
75export function readDeaclareFiles(): string[] {
76  const declarationsFileNames: string[] = [];
77  fs.readdirSync(path.resolve(__dirname, '../declarations'))
78    .forEach((fileName: string) => {
79      if (/\.d\.ts$/.test(fileName)) {
80        declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName));
81      }
82    });
83  return declarationsFileNames;
84}
85
86export const compilerOptions: ts.CompilerOptions = ts.readConfigFile(
87  path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions;
88function setCompilerOptions(resolveModulePaths: string[]) {
89  const allPath: Array<string> = [
90    '*'
91  ];
92  const basePath: string = path.resolve(projectConfig.projectPath);
93  if (process.env.compileTool === 'rollup' && resolveModulePaths && resolveModulePaths.length) {
94    resolveModulePaths.forEach((item: string) => {
95      if (!(/oh_modules$/.test(item) || /node_modules$/.test(item))) {
96        allPath.push(path.join(path.relative(basePath, item), '*'));
97      }
98    });
99  } else {
100    if (!projectConfig.aceModuleJsonPath) {
101      allPath.push('../../../../../*');
102      allPath.push('../../*');
103    } else {
104      allPath.push('../../../../*');
105      allPath.push('../*');
106    }
107  }
108  Object.assign(compilerOptions, {
109    'allowJs': false,
110    'emitNodeModulesFiles': true,
111    'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve,
112    'module': ts.ModuleKind.CommonJS,
113    'moduleResolution': ts.ModuleResolutionKind.NodeJs,
114    'noEmit': true,
115    'target': ts.ScriptTarget.ES2017,
116    'baseUrl': basePath,
117    'paths': {
118      '*': allPath
119    },
120    'lib': [
121      'lib.es2020.d.ts'
122    ],
123    'types': projectConfig.compilerTypes,
124    'etsLoaderPath': projectConfig.etsLoaderPath,
125    'needDoArkTsLinter': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE,
126    'isCompatibleVersion':  getArkTSLinterMode() === ArkTSLinterMode.COMPATIBLE_MODE
127  });
128  if (projectConfig.compileMode === ESMODULE) {
129    Object.assign(compilerOptions, {
130      'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Remove,
131      'module': ts.ModuleKind.ES2020
132    });
133  }
134  if (projectConfig.packageDir === 'oh_modules') {
135    Object.assign(compilerOptions, {
136      'packageManagerType': 'ohpm'
137    });
138  }
139}
140
141interface InitCheckConfig {
142  tagName: string;
143  message: string;
144  needConditionCheck: boolean;
145  type: ts.DiagnosticCategory;
146  specifyCheckConditionFuncName: string;
147  tagNameShouldExisted: boolean;
148}
149
150interface CheckJSDocTagNameConfig {
151  needCheck: boolean;
152  checkConfig: InitCheckConfig[];
153}
154
155function getInitCheckConfig(tagName: string, message: string): InitCheckConfig {
156  return {
157    tagName: tagName,
158    message: message,
159    needConditionCheck: false,
160    type: ts.DiagnosticCategory.Error,
161    specifyCheckConditionFuncName: '',
162    tagNameShouldExisted: true
163  };
164}
165
166function getCheckJSDocTagNameConfig(fileName: string, sourceFileName: string): CheckJSDocTagNameConfig {
167  let needCheckResult: boolean = false;
168  const checkConfigArray: InitCheckConfig[] = [];
169  if (ohosSystemModulePaths.includes(path.normalize(sourceFileName)) || isArkuiDependence(sourceFileName)) {
170    if (isCardFile(fileName)) {
171      needCheckResult = true;
172      checkConfigArray.push(getInitCheckConfig('form', "'{0}' can't support form application."));
173    }
174    if (projectConfig.isCrossplatform) {
175      needCheckResult = true;
176      checkConfigArray.push(getInitCheckConfig('crossplatform', "'{0}' can't support crossplatform application."));
177    }
178  }
179
180  return {
181    needCheck: needCheckResult,
182    checkConfig: checkConfigArray
183  };
184}
185
186interface extendInfo {
187  start: number,
188  end: number,
189  compName: string
190}
191
192export const files: ts.MapLike<{ version: number }> = {};
193
194export function createLanguageService(rootFileNames: string[], resolveModulePaths: string[]): ts.LanguageService {
195  setCompilerOptions(resolveModulePaths);
196  rootFileNames.forEach((fileName: string) => {
197    files[fileName] = {version: 0};
198  });
199  const servicesHost: ts.LanguageServiceHost = {
200    getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()],
201    getScriptVersion: fileName => {
202      if (!files[path.resolve(fileName)]) {
203        files[path.resolve(fileName)] = {version: 0};
204      }
205      return files[path.resolve(fileName)].version.toString();
206    },
207    getScriptSnapshot: fileName => {
208      if (!fs.existsSync(fileName)) {
209        return undefined;
210      }
211      if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
212        appComponentCollection.set(path.join(fileName), new Set());
213        let content: string = processContent(fs.readFileSync(fileName).toString(), fileName);
214        const extendFunctionInfo: extendInfo[] = [];
215        content = instanceInsteadThis(content, fileName, extendFunctionInfo);
216        return ts.ScriptSnapshot.fromString(content);
217      }
218      return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString());
219    },
220    getCurrentDirectory: () => process.cwd(),
221    getCompilationSettings: () => compilerOptions,
222    getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
223    fileExists: ts.sys.fileExists,
224    readFile: ts.sys.readFile,
225    readDirectory: ts.sys.readDirectory,
226    resolveModuleNames: resolveModuleNames,
227    resolveTypeReferenceDirectives: resolveTypeReferenceDirectives,
228    directoryExists: ts.sys.directoryExists,
229    getDirectories: ts.sys.getDirectories,
230    getTagNameNeededCheckByFile: (fileName: string, sourceFileName: string) => {
231      return getCheckJSDocTagNameConfig(fileName, sourceFileName);
232    }
233  };
234  return ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
235}
236
237interface CacheFileName {
238  mtimeMs: number,
239  children: string[],
240  parent: string[],
241  error: boolean
242}
243interface NeedUpdateFlag {
244  flag: boolean;
245}
246interface CheckerResult {
247  count: number
248}
249
250interface WarnCheckerResult {
251  count: number
252}
253
254interface WholeCache {
255  runtimeOS: string,
256  sdkInfo: string,
257  fileList: Cache
258}
259type Cache = Record<string, CacheFileName>;
260export let cache: Cache = {};
261export const hotReloadSupportFiles: Set<string> = new Set();
262export const shouldResolvedFiles: Set<string> = new Set();
263export const appComponentCollection: Map<string, Set<string>> = new Map();
264const allResolvedModules: Set<string> = new Set();
265
266export let fastBuildLogger = null;
267
268export const checkerResult: CheckerResult = {count: 0};
269export const warnCheckerResult: WarnCheckerResult = {count: 0};
270export let languageService: ts.LanguageService = null;
271export function serviceChecker(rootFileNames: string[], newLogger: any = null, resolveModulePaths: string[] = null): void {
272  fastBuildLogger = newLogger;
273  let cacheFile: string = null;
274  if (projectConfig.xtsMode || process.env.watchMode === 'true') {
275    if (projectConfig.hotReload) {
276      rootFileNames.forEach(fileName => {
277        hotReloadSupportFiles.add(fileName);
278      });
279    }
280    languageService = createLanguageService(rootFileNames, resolveModulePaths);
281  } else {
282    cacheFile = path.resolve(projectConfig.cachePath, '../.ts_checker_cache');
283    const wholeCache: WholeCache = fs.existsSync(cacheFile) ?
284      JSON.parse(fs.readFileSync(cacheFile).toString()) :
285      {'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {}};
286    if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) {
287      cache = wholeCache.fileList;
288    } else {
289      cache = {};
290    }
291    const filterFiles: string[] = filterInput(rootFileNames);
292    languageService = createLanguageService(filterFiles, resolveModulePaths);
293  }
294  globalProgram.program = languageService.getProgram();
295  runArkTSLinter();
296  collectSourceFilesMap(globalProgram.program);
297  if (process.env.watchMode !== 'true') {
298    processBuildHap(cacheFile, rootFileNames);
299  }
300}
301
302function processBuildHap(cacheFile: string, rootFileNames: string[]): void {
303  const allDiagnostics: ts.Diagnostic[] = globalProgram.program
304    .getSyntacticDiagnostics()
305    .concat(globalProgram.program.getSemanticDiagnostics())
306    .concat(globalProgram.program.getDeclarationDiagnostics());
307  allDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
308    printDiagnostic(diagnostic);
309  });
310  if (!projectConfig.xtsMode) {
311    fse.ensureDirSync(projectConfig.cachePath);
312    fs.writeFileSync(cacheFile, JSON.stringify({
313      'runtimeOS': projectConfig.runtimeOS,
314      'sdkInfo': projectConfig.sdkInfo,
315      'fileList': cache
316    }, null, 2));
317  }
318  if (projectConfig.compileHar || projectConfig.compileShared) {
319    [...allResolvedModules, ...rootFileNames].forEach(moduleFile => {
320      if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar)) {
321        try {
322          if ((/\.d\.e?ts$/).test(moduleFile)) {
323            generateSourceFilesInHar(moduleFile, fs.readFileSync(moduleFile, 'utf-8'), path.extname(moduleFile),
324              projectConfig);
325          } else {
326            const emit: any = languageService.getEmitOutput(moduleFile, true, true);
327            if (emit.outputFiles[0]) {
328              generateSourceFilesInHar(moduleFile, emit.outputFiles[0].text, '.d' + path.extname(moduleFile),
329                projectConfig);
330            } else {
331              console.warn(this.yellow,
332                "ArkTS:WARN doesn't generate .d" + path.extname(moduleFile) + ' for ' + moduleFile, this.reset);
333            }
334          }
335        } catch (err) {}
336      }
337    });
338  }
339}
340
341function isArkuiDependence(file: string): boolean {
342  return /compiler\/declarations/.test(file) || /ets-loader\/declarations/.test(file);
343}
344
345function isCardFile(file: string): boolean {
346  for (const key in projectConfig.cardEntryObj) {
347    if (path.normalize(projectConfig.cardEntryObj[key]) === path.normalize(file)) {
348      return true;
349    }
350  }
351  return false;
352}
353
354function containFormError(message: string): boolean {
355  if (/can't support form application./.test(message)) {
356    return true;
357  }
358  return false;
359}
360
361export function printDiagnostic(diagnostic: ts.Diagnostic): void {
362  const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
363  if (validateError(message)) {
364    if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
365      updateErrorFileCache(diagnostic);
366    }
367
368    if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) {
369      return;
370    }
371
372    const logPrefix: string = diagnostic.category === ts.DiagnosticCategory.Error ? 'ERROR' : 'WARN';
373    const etsCheckerLogger = fastBuildLogger ? fastBuildLogger : logger;
374    let logMessage: string;
375    if (logPrefix === 'ERROR') {
376      checkerResult.count += 1;
377    } else {
378      warnCheckerResult.count += 1;
379    }
380    if (diagnostic.file) {
381      const { line, character }: ts.LineAndCharacter =
382        diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
383      logMessage = `ArkTS:${logPrefix} File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`;
384    } else {
385      logMessage = `ArkTS:${logPrefix}: ${message}`;
386    }
387
388    if (diagnostic.category === ts.DiagnosticCategory.Error) {
389      etsCheckerLogger.error('\u001b[31m' + logMessage);
390    } else {
391      etsCheckerLogger.warn('\u001b[33m' + logMessage);
392    }
393  }
394}
395
396function validateError(message: string): boolean {
397  const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/;
398  const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/;
399  if (matchMessage(message, props, propInfoReg) ||
400    matchMessage(message, props, stateInfoReg)) {
401    return false;
402  }
403  return true;
404}
405function matchMessage(message: string, nameArr: any, reg: RegExp): boolean {
406  if (reg.test(message)) {
407    const match: string[] = message.match(reg);
408    if (match[1] && nameArr.includes(match[1])) {
409      return true;
410    }
411  }
412  return false;
413}
414
415function updateErrorFileCache(diagnostic: ts.Diagnostic): void {
416  if (diagnostic.file && cache[path.resolve(diagnostic.file.fileName)]) {
417    cache[path.resolve(diagnostic.file.fileName)].error = true;
418  }
419}
420
421function filterInput(rootFileNames: string[]): string[] {
422  return rootFileNames.filter((file: string) => {
423    const needUpdate: NeedUpdateFlag = { flag: false };
424    const alreadyCheckedFiles: Set<string> = new Set();
425    checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles);
426    return needUpdate.flag;
427  });
428}
429
430function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set<string>): void {
431  if (alreadyCheckedFiles.has(file)) {
432    return;
433  } else {
434    alreadyCheckedFiles.add(file);
435  }
436
437  if (needUpdate.flag) {
438    return;
439  }
440
441  const value: CacheFileName = cache[file];
442  const mtimeMs: number = fs.statSync(file).mtimeMs;
443  if (value) {
444    if (value.error || value.mtimeMs !== mtimeMs) {
445      needUpdate.flag = true;
446      return;
447    }
448    for (let i = 0; i < value.children.length; ++i) {
449      if (fs.existsSync(value.children[i])) {
450        checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles);
451      } else {
452        needUpdate.flag = true;
453      }
454    }
455  } else {
456    cache[file] = { mtimeMs, children: [], parent: [], error: false };
457    needUpdate.flag = true;
458  }
459}
460
461const moduleResolutionHost: ts.ModuleResolutionHost = {
462  fileExists(fileName: string): boolean {
463    return ts.sys.fileExists(fileName);
464  },
465  readFile(fileName: string): string | undefined {
466    return ts.sys.readFile(fileName);
467  },
468  realpath(path: string): string {
469    return ts.sys.realpath(path);
470  },
471  trace(s: string): void {
472    console.info(s);
473  }
474}
475
476export function resolveTypeReferenceDirectives(typeDirectiveNames: string[]): ts.ResolvedTypeReferenceDirective[] {
477  if (typeDirectiveNames.length === 0) {
478    return [];
479  }
480
481  const resolvedTypeReferenceCache: ts.ResolvedTypeReferenceDirective[] = [];
482  const cache: Map<string, ts.ResolvedTypeReferenceDirective> = new Map<string, ts.ResolvedTypeReferenceDirective>();
483  const containingFile: string = path.join(projectConfig.modulePath, "build-profile.json5");
484
485  for (const typeName of typeDirectiveNames) {
486    if (!cache.has(typeName)) {
487      const resolvedFile = ts.resolveTypeReferenceDirective(typeName, containingFile, compilerOptions, moduleResolutionHost);
488      if (!resolvedFile || !resolvedFile.resolvedTypeReferenceDirective) {
489        logger.error('\u001b[31m', `ArkTS:Cannot find type definition file for: ${typeName}\n`);
490      }
491      const result: ts.ResolvedTypeReferenceDirective = resolvedFile.resolvedTypeReferenceDirective;
492      cache.set(typeName, result);
493      resolvedTypeReferenceCache.push(result);
494    }
495  }
496  return resolvedTypeReferenceCache;
497}
498
499const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map();
500
501export function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] {
502  const resolvedModules: ts.ResolvedModuleFull[] = [];
503  if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile))
504    || !(resolvedModulesCache[path.resolve(containingFile)] &&
505      resolvedModulesCache[path.resolve(containingFile)].length === moduleNames.length)) {
506    for (const moduleName of moduleNames) {
507      const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost);
508      if (result.resolvedModule) {
509        if (result.resolvedModule.resolvedFileName &&
510          path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) {
511          const resultDETSPath: string =
512            result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS);
513          if (ts.sys.fileExists(resultDETSPath)) {
514            resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS));
515          } else {
516            resolvedModules.push(result.resolvedModule);
517          }
518        } else {
519          resolvedModules.push(result.resolvedModule);
520        }
521      } else if (new RegExp(`^@(${sdkConfigPrefix})\\.`, 'i').test(moduleName.trim())) {
522        let apiFileExist: boolean = false;
523        for (let i = 0; i < sdkConfigs.length; i++) {
524          const sdkConfig = sdkConfigs[i];
525          let modulePath: string = path.resolve(sdkConfig.apiPath, moduleName + '.d.ts');
526          let isDETS: boolean = false;
527          if (!fs.existsSync(modulePath)) {
528            modulePath = path.resolve(sdkConfig.apiPath, moduleName + '.d.ets');
529            isDETS = true;
530          }
531          if (systemModules.includes(moduleName + (isDETS ? '.d.ets' : '.d.ts')) && ts.sys.fileExists(modulePath)) {
532            resolvedModules.push(getResolveModule(modulePath, isDETS ? '.d.ets' : '.d.ts'));
533            apiFileExist = true;
534            break;
535          }
536        }
537        if (!apiFileExist) {
538          resolvedModules.push(null);
539        }
540      } else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) {
541        const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
542        if (ts.sys.fileExists(modulePath)) {
543          resolvedModules.push(getResolveModule(modulePath, '.ets'));
544        } else {
545          resolvedModules.push(null);
546        }
547      } else if (/\.ts$/.test(moduleName)) {
548        const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
549        if (ts.sys.fileExists(modulePath)) {
550          resolvedModules.push(getResolveModule(modulePath, '.ts'));
551        } else {
552          resolvedModules.push(null);
553        }
554      } else {
555        const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts');
556        const systemDETSModulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ets');
557        const suffix: string = /\.js$/.test(moduleName) ? '' : '.js';
558        const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix);
559        const fileModulePath: string =
560          path.resolve(__dirname, '../node_modules', moduleName + '/index.js');
561        const DETSModulePath: string = path.resolve(path.dirname(containingFile),
562          /\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS);
563        if (ts.sys.fileExists(modulePath)) {
564          resolvedModules.push(getResolveModule(modulePath, '.d.ts'));
565        } else if (ts.sys.fileExists(systemDETSModulePath)) {
566          resolvedModules.push(getResolveModule(systemDETSModulePath, '.d.ets'));
567        } else if (ts.sys.fileExists(jsModulePath)) {
568          resolvedModules.push(getResolveModule(jsModulePath, '.js'));
569        } else if (ts.sys.fileExists(fileModulePath)) {
570          resolvedModules.push(getResolveModule(fileModulePath, '.js'));
571        } else if (ts.sys.fileExists(DETSModulePath)) {
572          resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets'));
573        } else {
574          const srcIndex: number = projectConfig.projectPath.indexOf('src' + path.sep + 'main');
575          let DETSModulePathFromModule: string;
576          if (srcIndex > 0) {
577            DETSModulePathFromModule = path.resolve(
578              projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS);
579            if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) {
580              resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets'));
581            } else {
582              resolvedModules.push(null);
583            }
584          } else {
585            resolvedModules.push(null);
586          }
587        }
588      }
589      if (projectConfig.hotReload && resolvedModules.length &&
590        resolvedModules[resolvedModules.length - 1]) {
591        hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName));
592      }
593      if (collectShouldPackedFiles(resolvedModules)) {
594        allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName);
595      }
596    }
597    if (!projectConfig.xtsMode) {
598      createOrUpdateCache(resolvedModules, path.resolve(containingFile));
599    }
600    resolvedModulesCache[path.resolve(containingFile)] = resolvedModules;
601    return resolvedModules;
602  }
603  return resolvedModulesCache[path.resolve(containingFile)];
604}
605
606function collectShouldPackedFiles(resolvedModules: ts.ResolvedModuleFull[]): boolean | RegExpMatchArray {
607  return (projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] &&
608    resolvedModules[resolvedModules.length - 1].resolvedFileName &&
609    (path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/) ||
610    path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/\.d\.e?ts$/) &&
611    path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(
612      new RegExp('\\' + path.sep + 'src' + '\\' + path.sep + 'main' + '\\' + path.sep)));
613}
614
615function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void {
616  const children: string[] = [];
617  const error: boolean = false;
618  resolvedModules.forEach(moduleObj => {
619    if (moduleObj && moduleObj.resolvedFileName && /(?<!\.d)\.(ets|ts)$/.test(moduleObj.resolvedFileName)) {
620      const file: string = path.resolve(moduleObj.resolvedFileName);
621      const mtimeMs: number = fs.statSync(file).mtimeMs;
622      children.push(file);
623      const value: CacheFileName = cache[file];
624      if (value) {
625        value.mtimeMs = mtimeMs;
626        value.error = error;
627        value.parent = value.parent || [];
628        value.parent.push(path.resolve(containingFile));
629        value.parent = [...new Set(value.parent)];
630      } else {
631        cache[file] = { mtimeMs, children: [], parent: [containingFile], error };
632      }
633    }
634  });
635  cache[path.resolve(containingFile)] = { mtimeMs: fs.statSync(containingFile).mtimeMs, children,
636    parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ?
637      cache[path.resolve(containingFile)].parent : [], error };
638}
639
640export function createWatchCompilerHost(rootFileNames: string[],
641  reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function,
642  isPipe: boolean = false, resolveModulePaths: string[] = null): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> {
643  if (projectConfig.hotReload) {
644    rootFileNames.forEach(fileName => {
645      hotReloadSupportFiles.add(fileName);
646    });
647  }
648  setCompilerOptions(resolveModulePaths);
649  const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
650  const host = ts.createWatchCompilerHost(
651    [...rootFileNames, ...readDeaclareFiles()], compilerOptions,
652    ts.sys, createProgram, reportDiagnostic,
653    (diagnostic: ts.Diagnostic) => {
654      if ([6031, 6032].includes(diagnostic.code)) {
655        if (!isPipe) {
656          process.env.watchTs = 'start';
657          resetErrorCount();
658        }
659      }
660      // End of compilation in watch mode flag.
661      if ([6193, 6194].includes(diagnostic.code)) {
662        if (!isPipe) {
663          process.env.watchTs = 'end';
664          if (fastBuildLogger) {
665            fastBuildLogger.debug(TS_WATCH_END_MSG);
666            tsWatchEmitter.emit(TS_WATCH_END_MSG);
667          }
668        }
669        delayPrintLogCount();
670      }
671    });
672  host.readFile = (fileName: string) => {
673    if (!fs.existsSync(fileName)) {
674      return undefined;
675    }
676    if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
677      let content: string = processContent(fs.readFileSync(fileName).toString(), fileName);
678      const extendFunctionInfo: extendInfo[] = [];
679      content = instanceInsteadThis(content, fileName, extendFunctionInfo);
680      return content;
681    }
682    return fs.readFileSync(fileName).toString();
683  };
684  host.resolveModuleNames = resolveModuleNames;
685  host.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives;
686  return host;
687}
688
689export function watchChecker(rootFileNames: string[], newLogger: any = null, resolveModulePaths: string[] = null): void {
690  fastBuildLogger = newLogger;
691  globalProgram.watchProgram = ts.createWatchProgram(
692    createWatchCompilerHost(rootFileNames, printDiagnostic, () => {}, () => {}, false, resolveModulePaths));
693}
694
695export function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[]): string {
696  checkUISyntax(content, fileName, extendFunctionInfo);
697  extendFunctionInfo.reverse().forEach((item) => {
698    const subStr: string = content.substring(item.start, item.end);
699    const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => {
700      return item1 + item.compName + 'Instance' + item2;
701    });
702    content = content.slice(0, item.start) + insert + content.slice(item.end);
703  });
704  return content;
705}
706
707function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull {
708  return {
709    resolvedFileName: modulePath,
710    isExternalLibraryImport: false,
711    extension: type
712  };
713}
714
715export const dollarCollection: Set<string> = new Set();
716export const decoratorParamsCollection: Set<string> = new Set();
717export const extendCollection: Set<string> = new Set();
718export const importModuleCollection: Set<string> = new Set();
719
720function checkUISyntax(source: string, fileName: string, extendFunctionInfo: extendInfo[]): void {
721  if (/\.ets$/.test(fileName)) {
722    if (process.env.compileMode === 'moduleJson' ||
723      path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) {
724      const sourceFile: ts.SourceFile = ts.createSourceFile(fileName, source,
725        ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS);
726      collectComponents(sourceFile);
727      parseAllNode(sourceFile, sourceFile, extendFunctionInfo);
728      props.push(...dollarCollection, ...decoratorParamsCollection, ...extendCollection);
729    }
730  }
731}
732
733function collectComponents(node: ts.SourceFile): void {
734  // @ts-ignore
735  if (process.env.watchMode !== 'true' && node.identifiers && node.identifiers.size) {
736    // @ts-ignore
737    for (const key of node.identifiers.keys()) {
738      if (JS_BIND_COMPONENTS.has(key)) {
739        appComponentCollection.get(path.join(node.fileName)).add(key);
740      }
741    }
742  }
743}
744
745function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void {
746  if (ts.isStructDeclaration(node)) {
747    if (node.members) {
748      node.members.forEach(item => {
749        if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) {
750          const propertyName: string = item.name.getText();
751          if (item.decorators && item.decorators.length) {
752            for (let i = 0; i < item.decorators.length; i++) {
753              const decoratorName: string = item.decorators[i].getText().replace(/\(.*\)$/, '').trim();
754              if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {
755                dollarCollection.add('$' + propertyName);
756              }
757              if (isDecoratorCollection(item.decorators[i], decoratorName)) {
758                decoratorParamsCollection.add(item.decorators[i].expression.arguments[0].getText());
759              }
760            }
761          }
762        }
763      });
764    }
765  }
766  if (process.env.watchMode !== 'true' && ts.isIfStatement(node)) {
767    appComponentCollection.get(path.join(sourceFileNode.fileName)).add(COMPONENT_IF);
768  }
769  if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION ||
770    (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) &&
771    hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) {
772    if (node.body && node.body.statements && node.body.statements.length) {
773      const checkProp: ts.NodeArray<ts.Statement> = node.body.statements;
774      checkProp.forEach((item, index) => {
775        traverseBuild(item, index);
776      });
777    }
778  }
779  if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) {
780    if (node.body && node.body.statements && node.body.statements.length &&
781      !isOriginalExtend(node.body)) {
782      extendFunctionInfo.push({
783        start: node.pos,
784        end: node.end,
785        compName: isExtendFunction(node, { decoratorName: '', componentName: '' })});
786    }
787  }
788  node.getChildren().forEach((item: ts.Node) => parseAllNode(item, sourceFileNode, extendFunctionInfo));
789}
790
791function isForeachAndLzayForEach(node: ts.Node): boolean {
792  return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) &&
793    FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] &&
794    ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body);
795}
796
797function traverseBuild(node: ts.Node, index: number): void {
798  if (ts.isExpressionStatement(node)) {
799    let parentComponentName: string = getName(node);
800    if (!INNER_COMPONENT_NAMES.has(parentComponentName) && node.parent && node.parent.statements && index >= 1 &&
801      node.parent.statements[index - 1].expression && node.parent.statements[index - 1].expression.expression) {
802      parentComponentName = node.parent.statements[index - 1].expression.expression.escapedText;
803    }
804    node = node.expression;
805    if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) &&
806      ts.isIdentifier(node.expression) && !$$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) {
807      node.body.statements.forEach((item: ts.Statement, indexBlock: number) => {
808        traverseBuild(item, indexBlock);
809      });
810    } else if (isForeachAndLzayForEach(node)) {
811      node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => {
812        traverseBuild(item, indexBlock);
813      });
814    } else {
815      loopNodeFindDoubleDollar(node, parentComponentName);
816    }
817  } else if (ts.isIfStatement(node)) {
818    if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) {
819      node.thenStatement.statements.forEach((item, indexIfBlock) => {
820        traverseBuild(item, indexIfBlock);
821      });
822    }
823    if (node.elseStatement && ts.isBlock(node.elseStatement) && node.elseStatement.statements) {
824      node.elseStatement.statements.forEach((item, indexElseBlock) => {
825        traverseBuild(item, indexElseBlock);
826      });
827    }
828  }
829}
830
831function isPropertiesAddDoubleDollar(node: ts.Node): boolean {
832  if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) {
833    return true;
834  } else if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) &&
835    ts.isIdentifier(node.expression) && $$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) {
836    return true;
837  } else {
838    return false;
839  }
840}
841function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void {
842  while (node) {
843    if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
844      const argument: ts.NodeArray<ts.Node> = node.arguments;
845      const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name;
846      if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) {
847        argument.forEach((item: ts.Node) => {
848          doubleDollarCollection(item);
849        });
850      }
851    } else if (isPropertiesAddDoubleDollar(node)) {
852      node.arguments.forEach((item: ts.Node) => {
853        if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) {
854          item.properties.forEach((param: ts.Node) => {
855            if (isObjectPram(param, parentComponentName)) {
856              doubleDollarCollection(param.initializer);
857            }
858          });
859        }
860        if (STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()) && ts.isPropertyAccessExpression(item)) {
861          doubleDollarCollection(item);
862        }
863      });
864    }
865    node = node.expression;
866  }
867}
868
869function doubleDollarCollection(item: ts.Node): void {
870  if (item.getText().startsWith($$)) {
871    while (item.expression) {
872      item = item.expression;
873    }
874    dollarCollection.add(item.getText());
875  }
876}
877
878function isObjectPram(param: ts.Node, parentComponentName:string): boolean {
879  return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) &&
880    param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
881    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText());
882}
883
884function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean {
885  return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
886    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) ||
887    STYLE_ADD_DOUBLE_DOLLAR.has(propertyName);
888}
889
890function isDecoratorCollection(item: ts.Decorator, decoratorName: string): boolean {
891  return COMPONENT_DECORATORS_PARAMS.has(decoratorName) &&
892    // @ts-ignore
893    item.expression.arguments && item.expression.arguments.length &&
894    // @ts-ignore
895    ts.isIdentifier(item.expression.arguments[0]);
896}
897
898function processContent(source: string, id: string): string {
899  if (fastBuildLogger) {
900    source = visualTransform(source, id, fastBuildLogger);
901  }
902  source = preprocessExtend(source, extendCollection);
903  source = preprocessNewExtend(source, extendCollection);
904  return source;
905}
906
907function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set<string>): void {
908  if (shouldResolvedFiles.has(file)) {
909    return;
910  }
911  shouldResolvedFiles.add(file);
912  if (cache && cache[file] && cache[file].parent) {
913    cache[file].parent.forEach((item) => {
914      judgeFileShouldResolved(item, shouldResolvedFiles);
915    });
916    cache[file].parent = [];
917  }
918  if (cache && cache[file] && cache[file].children) {
919    cache[file].children.forEach((item) => {
920      judgeFileShouldResolved(item, shouldResolvedFiles);
921    });
922    cache[file].children = [];
923  }
924}
925
926export function incrementWatchFile(watchModifiedFiles: string[],
927  watchRemovedFiles: string[]): void {
928  const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles];
929  if (changedFiles.length) {
930    shouldResolvedFiles.clear();
931  }
932  changedFiles.forEach((file) => {
933    judgeFileShouldResolved(file, shouldResolvedFiles);
934  });
935}
936
937function runArkTSLinter(): void {
938  const arkTSLinterDiagnostics =
939    doArkTSLinter(globalProgram.program, getArkTSLinterMode(), printArkTSLinterDiagnostic, !projectConfig.xtsMode);
940  if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
941    arkTSLinterDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
942      updateErrorFileCache(diagnostic);
943    });
944  }
945}
946
947function printArkTSLinterDiagnostic(diagnostic: ts.Diagnostic): void {
948  if (diagnostic.category === ts.DiagnosticCategory.Error && (isInOhModuleFile(diagnostic) || isInSDK(diagnostic))) {
949    const originalCategory = diagnostic.category;
950    diagnostic.category = ts.DiagnosticCategory.Warning;
951    printDiagnostic(diagnostic);
952    diagnostic.category = originalCategory;
953    return;
954  }
955  printDiagnostic(diagnostic);
956}
957
958function isInOhModuleFile(diagnostics: ts.Diagnostic): boolean {
959  return (diagnostics.file !== undefined) &&
960    ((diagnostics.file.fileName.indexOf('/oh_modules/') !== -1) || diagnostics.file.fileName.indexOf('\\oh_modules\\') !== -1);
961}
962
963function isInSDK(diagnostics: ts.Diagnostic): boolean {
964  const fileName = diagnostics.file?.fileName;
965  if (projectConfig.etsLoaderPath === undefined || fileName === undefined) {
966    return false;
967  }
968  const sdkPath = path.resolve(projectConfig.etsLoaderPath, '../../../');
969  return path.resolve(fileName).startsWith(sdkPath);
970}
971
972export function getArkTSLinterMode(): ArkTSLinterMode {
973  if (!partialUpdateConfig.executeArkTSLinter) {
974    return ArkTSLinterMode.NOT_USE;
975  }
976
977  if (!partialUpdateConfig.standardArkTSLinter) {
978    return ArkTSLinterMode.COMPATIBLE_MODE;
979  }
980
981  if (isStandardMode()) {
982    return ArkTSLinterMode.STANDARD_MODE;
983  }
984  return ArkTSLinterMode.COMPATIBLE_MODE;
985}
986
987export function isStandardMode(): boolean {
988  const STANDARD_MODE_COMPATIBLE_SDK_VERSION = 10;
989  if (projectConfig &&
990    projectConfig.compatibleSdkVersion &&
991    projectConfig.compatibleSdkVersion >= STANDARD_MODE_COMPATIBLE_SDK_VERSION) {
992    return true;
993  }
994  return false;
995}
996
997function initEtsStandaloneCheckerConfig(logger, config): void {
998  fastBuildLogger = logger;
999  if (config.packageManagerType === 'ohpm') {
1000    config.packageDir = 'oh_modules';
1001    config.packageJson = 'oh-package.json5';
1002  } else {
1003    config.packageDir = 'node_modules';
1004    config.packageJson = 'package.json';
1005  }
1006  if (config.aceModuleJsonPath && fs.existsSync(config.aceModuleJsonPath)) {
1007    process.env.compileMode = 'moduleJson';
1008  }
1009  Object.assign(projectConfig, config);
1010}
1011
1012export function etsStandaloneChecker(entryObj, logger, projectConfig): void {
1013  initEtsStandaloneCheckerConfig(logger, projectConfig);
1014  const rootFileNames: string[] = [];
1015  const resolveModulePaths: string[] = [];
1016  Object.values(entryObj).forEach((fileName: string) => {
1017    rootFileNames.push(path.resolve(fileName));
1018  });
1019  if (projectConfig.resolveModulePaths && Array.isArray(projectConfig.resolveModulePaths)) {
1020    resolveModulePaths.push(...projectConfig.resolveModulePaths);
1021  }
1022  const filterFiles: string[] = filterInput(rootFileNames);
1023  languageService = createLanguageService(filterFiles, resolveModulePaths);
1024  globalProgram.program = languageService.getProgram();
1025  runArkTSLinter();
1026  const allDiagnostics: ts.Diagnostic[] = globalProgram.program
1027    .getSyntacticDiagnostics()
1028    .concat(globalProgram.program.getSemanticDiagnostics())
1029    .concat(globalProgram.program.getDeclarationDiagnostics());
1030  allDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
1031    printDiagnostic(diagnostic);
1032  });
1033}
1034