• 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';
19import * as crypto from 'crypto';
20const fse = require('fs-extra');
21
22import {
23  projectConfig,
24  systemModules,
25  globalProgram,
26  sdkConfigs,
27  sdkConfigPrefix,
28  partialUpdateConfig,
29  resetProjectConfig,
30  resetGlobalProgram
31} from '../main';
32import {
33  preprocessExtend,
34  preprocessNewExtend
35} from './validate_ui_syntax';
36import {
37  INNER_COMPONENT_MEMBER_DECORATORS,
38  COMPONENT_DECORATORS_PARAMS,
39  COMPONENT_BUILD_FUNCTION,
40  STYLE_ADD_DOUBLE_DOLLAR,
41  $$,
42  PROPERTIES_ADD_DOUBLE_DOLLAR,
43  DOLLAR_BLOCK_INTERFACE,
44  COMPONENT_EXTEND_DECORATOR,
45  COMPONENT_BUILDER_DECORATOR,
46  ESMODULE,
47  EXTNAME_D_ETS,
48  EXTNAME_JS,
49  EXTNAME_ETS,
50  FOREACH_LAZYFOREACH,
51  COMPONENT_IF,
52  TS_WATCH_END_MSG,
53  TS_BUILD_INFO_SUFFIX,
54  HOT_RELOAD_BUILD_INFO_SUFFIX,
55  WATCH_COMPILER_BUILD_INFO_SUFFIX,
56  COMPONENT_STYLES_DECORATOR,
57  COMPONENT_LOCAL_BUILDER_DECORATOR
58} from './pre_define';
59import {
60  INNER_COMPONENT_NAMES,
61  JS_BIND_COMPONENTS,
62  BUILDIN_STYLE_NAMES
63} from './component_map';
64import { logger } from './compile_info';
65import {
66  hasDecorator,
67  isString,
68  generateSourceFilesInHar,
69  storedFileInfo,
70  toUnixPath,
71  isWindows,
72  isMac,
73  tryToLowerCasePath,
74  getRollupCache,
75  setRollupCache
76} from './utils';
77import {
78  isExtendFunction,
79  isOriginalExtend
80} from './process_ui_syntax';
81import { visualTransform } from './process_visual';
82import { tsWatchEmitter } from './fast_build/ets_ui/rollup-plugin-ets-checker';
83import {
84  doArkTSLinter,
85  ArkTSLinterMode,
86  ArkTSVersion,
87  transfromErrorCode,
88} from './do_arkTS_linter';
89import {
90  getJsDocNodeCheckConfig,
91  isCardFile,
92  getRealModulePath,
93  getJsDocNodeConditionCheckResult
94} from './fast_build/system_api/api_check_utils';
95import { sourceFileDependencies } from './fast_build/ark_compiler/common/ob_config_resolver';
96import { MemoryMonitor } from './fast_build/meomry_monitor/rollup-plugin-memory-monitor';
97import { MemoryDefine } from './fast_build/meomry_monitor/memory_define';
98import {
99  CompileEvent
100} from './performance';
101import {
102  LINTER_SUBSYSTEM_CODE,
103  HvigorErrorInfo
104} from './hvigor_error_code/hvigor_error_info';
105import { ErrorCodeModule } from './hvigor_error_code/const/error_code_module';
106import { buildErrorInfoFromDiagnostic } from './hvigor_error_code/utils';
107import { concatenateEtsOptions, getExternalComponentPaths } from './external_component_map';
108
109export interface LanguageServiceCache {
110  service?: ts.LanguageService;
111  pkgJsonFileHash?: string;
112  targetESVersion?: ts.ScriptTarget;
113  maxFlowDepth?: number;
114  preTsImportSendable?: boolean;
115  preSkipOhModulesLint?: boolean;
116  preMixCompile?: boolean;
117}
118
119export const SOURCE_FILES: Map<string, ts.SourceFile> = new Map();
120export let localPackageSet: Set<string> = new Set();
121export const TSC_SYSTEM_CODE = '105';
122export const fileCache: Map<string, string> = new Map();
123
124export const MAX_FLOW_DEPTH_DEFAULT_VALUE = 2000;
125export const MAX_FLOW_DEPTH_MAXIMUM_VALUE = 65535;
126
127export function readDeaclareFiles(): string[] {
128  const declarationsFileNames: string[] = [];
129  fs.readdirSync(path.resolve(__dirname, '../declarations'))
130    .forEach((fileName: string) => {
131      if (/\.d\.ts$/.test(fileName)) {
132        declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName));
133      }
134    });
135  return declarationsFileNames;
136}
137
138const buildInfoWriteFile: ts.WriteFileCallback = (fileName: string, data: string) => {
139  if (fileName.includes(TS_BUILD_INFO_SUFFIX)) {
140    const fd: number = fs.openSync(fileName, 'w');
141    fs.writeSync(fd, data, undefined, 'utf8');
142    fs.closeSync(fd);
143  }
144};
145// The collection records the file name and the corresponding version, where the version is the hash value of the text in last compilation.
146const filesBuildInfo: Map<string, string> = new Map();
147
148export let compilerOptions = ts.readConfigFile(
149  path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions;
150const componentPaths: string[] | undefined = getExternalComponentPaths();
151if (componentPaths) {
152  for (const componentPath of componentPaths) {
153    if (!fs.existsSync(componentPath)) {
154      continue;
155    }
156    const externalCompilerOptions: ts.CompilerOptions = ts.readConfigFile(
157      path.resolve(componentPath, 'externalconfig.json'), ts.sys.readFile
158    ).config.compilerOptions;
159    concatenateEtsOptions(compilerOptions, externalCompilerOptions);
160  }
161}
162function setCompilerOptions(resolveModulePaths: string[]): void {
163  const allPath: Array<string> = ['*'];
164  const basePath: string = path.resolve(projectConfig.projectPath);
165  if (process.env.compileTool === 'rollup' && resolveModulePaths && resolveModulePaths.length) {
166    resolveModulePaths.forEach((item: string) => {
167      if (!(/oh_modules$/.test(item) || /node_modules$/.test(item))) {
168        allPath.push(path.join(path.relative(basePath, item), '*'));
169      }
170    });
171  } else {
172    if (!projectConfig.aceModuleJsonPath) {
173      allPath.push('../../../../../*');
174      allPath.push('../../*');
175    } else {
176      allPath.push('../../../../*');
177      allPath.push('../*');
178    }
179  }
180  const suffix: string = projectConfig.hotReload ? HOT_RELOAD_BUILD_INFO_SUFFIX : TS_BUILD_INFO_SUFFIX;
181  const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', suffix);
182  checkArkTSVersion();
183  Object.assign(compilerOptions, {
184    'allowJs': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE ? true : false,
185    'checkJs': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE ? false : undefined,
186    'emitNodeModulesFiles': true,
187    'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve,
188    'module': ts.ModuleKind.CommonJS,
189    'moduleResolution': ts.ModuleResolutionKind.NodeJs,
190    'noEmit': true,
191    'target': convertConfigTarget(getTargetESVersion()),
192    'maxFlowDepth': getMaxFlowDepth(),
193    'baseUrl': basePath,
194    'paths': {
195      '*': allPath
196    },
197    'lib': convertConfigLib(getTargetESVersionLib()),
198    'types': projectConfig.compilerTypes,
199    'etsAnnotationsEnable': projectConfig.allowEtsAnnotations,
200    'etsLoaderPath': projectConfig.etsLoaderPath,
201    'needDoArkTsLinter': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE,
202    'isCompatibleVersion': getArkTSLinterMode() === ArkTSLinterMode.COMPATIBLE_MODE,
203    'skipTscOhModuleCheck': partialUpdateConfig.skipTscOhModuleCheck,
204    'skipArkTSStaticBlocksCheck': partialUpdateConfig.skipArkTSStaticBlocksCheck,
205    // options incremental && tsBuildInfoFile are required for applying incremental ability of typescript
206    'incremental': true,
207    'tsBuildInfoFile': buildInfoPath,
208    'tsImportSendableEnable': tsImportSendable,
209    'skipPathsInKeyForCompilationSettings': reuseLanguageServiceForDepChange,
210    'compatibleSdkVersionStage': projectConfig.compatibleSdkVersionStage,
211    'compatibleSdkVersion': projectConfig.compatibleSdkVersion,
212    'skipOhModulesLint': skipOhModulesLint,
213    'mixCompile': mixCompile
214  });
215  if (projectConfig.compileMode === ESMODULE) {
216    Object.assign(compilerOptions, {
217      'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Remove,
218      'module': ts.ModuleKind.ES2020
219    });
220  }
221  if (projectConfig.packageDir === 'oh_modules') {
222    Object.assign(compilerOptions, {'packageManagerType': 'ohpm'});
223  }
224  readTsBuildInfoFileInCrementalMode(buildInfoPath, projectConfig);
225}
226
227function checkArkTSVersion(): void {
228  const etsCheckerLogger = fastBuildLogger || logger;
229  if (getArkTSVersion() === ArkTSVersion.ArkTS_1_0 && tsImportSendable) {
230    const logMessage: string = 'ArkTS: ArkTSVersion1.0 does not support tsImportSendable in any condition';
231    etsCheckerLogger.error('\u001b[31m' + logMessage);
232    tsImportSendable = false;
233  }
234}
235
236// Change target to enum's value,e.g: "es2021" => ts.ScriptTarget.ES2021
237function convertConfigTarget(target: number | string): number | string {
238  if ((typeof target === 'number') && (target in ts.ScriptTarget)) {
239    return target;
240  }
241  return ts.convertCompilerOptionsFromJson({ 'target': target }, '').options.target;
242}
243
244// Change lib to libMap's value,e.g: "es2021" => "lib.es2021.d.ts"
245function convertConfigLib(libs: string[]): string[] {
246  let converted: boolean = true;
247  let libMapValues: string[] = Array.from(ts.libMap.values());
248  for (let i = 0; i < libs.length; i++) {
249    if (!libMapValues.includes(libs[i])) {
250      converted = false;
251      break;
252    }
253  }
254  if (converted) {
255    return libs;
256  }
257  return ts.convertCompilerOptionsFromJson({ 'lib': libs }, '').options.lib;
258}
259
260/**
261 * Read the source code information in the project of the last compilation process, and then use it
262 * to determine whether the file has been modified during this compilation process.
263 */
264function readTsBuildInfoFileInCrementalMode(buildInfoPath: string, projectConfig: Object): void {
265  if (!fs.existsSync(buildInfoPath) || !(projectConfig.compileHar || projectConfig.compileShared)) {
266    return;
267  }
268
269  type FileInfoType = {
270    version: string;
271    affectsGlobalScope: boolean;
272  };
273  type ProgramType = {
274    fileNames: string[];
275    fileInfos: (FileInfoType | string)[];
276  };
277  let buildInfoProgram: ProgramType;
278  try {
279    const content: {program: ProgramType} = JSON.parse(fs.readFileSync(buildInfoPath, 'utf-8'));
280    buildInfoProgram = content.program;
281    if (!buildInfoProgram || !buildInfoProgram.fileNames || !buildInfoProgram.fileInfos) {
282      throw new Error('.tsbuildinfo content is invalid');
283    }
284  } catch (err) {
285    fastBuildLogger.warn('\u001b[33m' + 'ArkTS: Failed to parse .tsbuildinfo file. Error message: ' + err.message.toString());
286    return;
287  }
288  const buildInfoDirectory: string = path.dirname(buildInfoPath);
289  /**
290   * For the windos and mac platform, the file path in tsbuildinfo is in lowercase, while buildInfoDirectory is the original path (including uppercase).
291   * Therefore, the path needs to be converted to lowercase, and then perform path comparison.
292   */
293  const isMacOrWin = isWindows() || isMac();
294  const fileNames: string[] = buildInfoProgram.fileNames;
295  const fileInfos: (FileInfoType | string)[] = buildInfoProgram.fileInfos;
296  fileInfos.forEach((fileInfo, index) => {
297    const version: string = typeof fileInfo === 'string' ? fileInfo : fileInfo.version;
298    const absPath: string = path.resolve(buildInfoDirectory, fileNames[index]);
299    filesBuildInfo.set(isMacOrWin ? tryToLowerCasePath(absPath) : absPath, version);
300  });
301}
302
303interface extendInfo {
304  start: number,
305  end: number,
306  compName: string
307}
308
309function createHash(str: string): string {
310  const hash = crypto.createHash('sha256');
311  hash.update(str);
312  return hash.digest('hex');
313}
314
315export function getFileContentWithHash(fileName: string): string {
316  let fileContent: string | undefined = fileCache.get(fileName);
317  if (fileContent === undefined) {
318    fileContent = fs.readFileSync(fileName).toString();
319    fileCache.set(fileName, fileContent);
320    // Provide the hash value for hvigor's remote cache, and let them handle the cleanup.
321    setHashValueByFilePath?.(fileName, createHash(fileContent));
322  }
323  return fileContent;
324}
325
326export const fileHashScriptVersion: (fileName: string) => string = (fileName: string) => {
327  if (!fs.existsSync(fileName)) {
328    return '0';
329  }
330
331  let fileContent: string = getFileContentWithHash(fileName);
332  let cacheInfo: CacheFileName = cache[path.resolve(fileName)];
333
334  // Error code corresponding to message `Cannot find module xx or its corresponding type declarations`
335  const errorCodeRequireRecheck: number = 2307;
336
337  if (cacheInfo && cacheInfo.error === true && cacheInfo.errorCodes && cacheInfo.errorCodes.includes(errorCodeRequireRecheck)) {
338    // If this file had errors that require recheck in the last compilation,
339    // mark the file as modified by modifying its hash value, thereby triggering tsc to recheck.
340    fileContent += Date.now().toString();
341    return createHash(fileContent);
342  }
343  return getHashByFilePath?.(fileName) ?? createHash(fileContent);
344};
345
346// Reuse the last language service when dependency in oh-package.json5 changes to enhance performance in incremental building.
347// Setting this to false will create a new language service on dependency changes, like a full rebuild.
348const reuseLanguageServiceForDepChange: boolean = true;
349// When dependency changes and reusing the last language service, enable this flag to recheck code dependent on those dependencies.
350export let needReCheckForChangedDepUsers: boolean = false;
351let setHashValueByFilePath: Function | undefined = undefined;
352let getHashByFilePath: Function | undefined = undefined;
353
354export function createLanguageService(rootFileNames: string[], resolveModulePaths: string[],
355  parentEvent?: CompileEvent, rollupShareObject?: Object): ts.LanguageService {
356  setHashValueByFilePath = rollupShareObject?.setHashValueByFilePath;
357  getHashByFilePath = rollupShareObject?.getHashByFilePath;
358  setCompilerOptions(resolveModulePaths);
359  const servicesHost: ts.LanguageServiceHost = {
360    getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()],
361    getScriptVersion: fileHashScriptVersion,
362    getScriptSnapshot: function(fileName) {
363      if (!fs.existsSync(fileName)) {
364        return undefined;
365      }
366      let fileContent: string = getFileContentWithHash(fileName);
367      if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
368        ts.PerformanceDotting?.startAdvanced('scriptSnapshot');
369        appComponentCollection.set(path.join(fileName), new Set());
370        let content: string = processContent(fileContent, fileName);
371        const extendFunctionInfo: extendInfo[] = [];
372        content = instanceInsteadThis(content, fileName, extendFunctionInfo, this.uiProps);
373        ts.PerformanceDotting?.stopAdvanced('scriptSnapshot');
374        return ts.ScriptSnapshot.fromString(content);
375      }
376      return ts.ScriptSnapshot.fromString(fileContent);
377    },
378    getCurrentDirectory: () => process.cwd(),
379    getCompilationSettings: () => compilerOptions,
380    getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
381    fileExists: ts.sys.fileExists,
382    readFile: ts.sys.readFile,
383    readDirectory: ts.sys.readDirectory,
384    resolveModuleNames: resolveModuleNames,
385    resolveTypeReferenceDirectives: resolveTypeReferenceDirectives,
386    directoryExists: ts.sys.directoryExists,
387    getDirectories: ts.sys.getDirectories,
388    getJsDocNodeCheckedConfig: (fileCheckedInfo: ts.FileCheckModuleInfo, sourceFileName: string) => {
389      return getJsDocNodeCheckConfig(fileCheckedInfo.currentFileName, sourceFileName);
390    },
391    getFileCheckedModuleInfo: (containFilePath: string) => {
392      return {
393        fileNeedCheck: true,
394        checkPayload: undefined,
395        currentFileName: containFilePath
396      };
397    },
398    getJsDocNodeConditionCheckedResult: (jsDocFileCheckedInfo: ts.FileCheckModuleInfo, jsDocInfos: ts.JsDocTagInfo[], jsDocs?: ts.JSDoc[]) => {
399      return getJsDocNodeConditionCheckResult(jsDocFileCheckedInfo, jsDocInfos, jsDocs);
400    },
401    uiProps: new Set(),
402    clearProps: function() {
403      dollarCollection.clear();
404      extendCollection.clear();
405      newExtendCollection.clear();
406      this.uiProps = new Set();
407    },
408    // TSC will re-do resolution if this callback return true.
409    hasInvalidatedResolutions: (filePath: string): boolean => {
410      return reuseLanguageServiceForDepChange && needReCheckForChangedDepUsers;
411    },
412    clearFileCache: function() {
413      fileCache.clear();
414    }
415  };
416  ts.PerformanceDotting?.setPerformanceSwitch(projectConfig?.perf);
417
418  if (process.env.watchMode === 'true') {
419    const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ETS_CHECKER_CREATE_LANGUAGE_SERVICE);
420    const tsLanguageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
421    MemoryMonitor.stopRecordStage(recordInfo);
422    return tsLanguageService;
423  }
424  const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ETS_CHECKER_CREATE_LANGUAGE_SERVICE);
425  const tsLanguageService = getOrCreateLanguageService(servicesHost, rootFileNames, rollupShareObject);
426  MemoryMonitor.stopRecordStage(recordInfo);
427  return tsLanguageService;
428}
429
430export let targetESVersionChanged: boolean = false;
431
432function getOrCreateLanguageService(servicesHost: ts.LanguageServiceHost, rootFileNames: string[],
433  rollupShareObject?: any): ts.LanguageService {
434  let cacheKey: string = 'service';
435  let cache: LanguageServiceCache | undefined = getRollupCache(rollupShareObject, projectConfig, cacheKey);
436
437  let service: ts.LanguageService | undefined = cache?.service;
438  const currentHash: string | undefined = rollupShareObject?.projectConfig?.pkgJsonFileHash;
439  const currentTargetESVersion: ts.ScriptTarget = compilerOptions.target;
440  const currentMaxFlowDepth: number | undefined = compilerOptions.maxFlowDepth;
441  const lastHash: string | undefined = cache?.pkgJsonFileHash;
442  const lastTargetESVersion: ts.ScriptTarget | undefined = cache?.targetESVersion;
443  const lastMaxFlowDepth: number | undefined = cache?.maxFlowDepth;
444  const hashDiffers: boolean | undefined = currentHash && lastHash && currentHash !== lastHash;
445  const shouldRebuildForDepDiffers: boolean | undefined = reuseLanguageServiceForDepChange ?
446    (hashDiffers && !rollupShareObject?.depInfo?.enableIncre) : hashDiffers;
447  const targetESVersionDiffers: boolean | undefined = lastTargetESVersion && currentTargetESVersion && lastTargetESVersion !== currentTargetESVersion;
448  const maxFlowDepthDiffers: boolean | undefined = lastMaxFlowDepth && currentMaxFlowDepth && lastMaxFlowDepth !== currentMaxFlowDepth;
449  const tsImportSendableDiff: boolean = (cache?.preTsImportSendable === undefined && !tsImportSendable) ?
450    false :
451    cache?.preTsImportSendable !== tsImportSendable;
452  const skipOhModulesLintDiff: boolean = (cache?.preSkipOhModulesLint === undefined && !skipOhModulesLint) ?
453    false : cache?.preSkipOhModulesLint !== skipOhModulesLint;
454  const mixCompileDiff: boolean = (cache?.preMixCompile === undefined && !mixCompile) ?
455    false : cache?.preMixCompile !== mixCompile;
456  const shouldRebuild: boolean | undefined = shouldRebuildForDepDiffers || targetESVersionDiffers ||
457    tsImportSendableDiff || maxFlowDepthDiffers || skipOhModulesLintDiff || mixCompileDiff;
458  if (reuseLanguageServiceForDepChange && hashDiffers && rollupShareObject?.depInfo?.enableIncre) {
459    needReCheckForChangedDepUsers = true;
460  }
461
462  if (!service || shouldRebuild) {
463    rebuildProgram(targetESVersionDiffers, tsImportSendableDiff, maxFlowDepthDiffers, skipOhModulesLintDiff, mixCompileDiff);
464    service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
465  } else {
466    // Found language service from cache, update root files
467    const updateRootFileNames = [...rootFileNames, ...readDeaclareFiles()];
468    service.updateRootFiles(updateRootFileNames);
469  }
470
471  const newCache: LanguageServiceCache = {
472    service: service,
473    pkgJsonFileHash: currentHash,
474    targetESVersion: currentTargetESVersion,
475    maxFlowDepth: currentMaxFlowDepth,
476    preTsImportSendable: tsImportSendable,
477    preSkipOhModulesLint: skipOhModulesLint,
478    preMixCompile: mixCompile
479  };
480  setRollupCache(rollupShareObject, projectConfig, cacheKey, newCache);
481  return service;
482}
483
484function rebuildProgram(targetESVersionDiffers: boolean | undefined, tsImportSendableDiff: boolean,
485  maxFlowDepthDiffers: boolean | undefined, skipOhModulesLintDiff: boolean, mixCompileDiff: boolean): void {
486  if (targetESVersionDiffers) {
487    // If the targetESVersion is changed, we need to delete the build info cahce files
488    deleteBuildInfoCache(compilerOptions.tsBuildInfoFile);
489    targetESVersionChanged = true;
490  } else if (tsImportSendableDiff || maxFlowDepthDiffers || skipOhModulesLintDiff || mixCompileDiff) {
491    // When tsImportSendable or maxFlowDepth is changed, we need to delete the build info cahce files
492    deleteBuildInfoCache(compilerOptions.tsBuildInfoFile);
493  }
494}
495
496function deleteBuildInfoCache(tsBuildInfoFilePath: string): void {
497  // The file name of tsBuildInfoLinterFile is '.tsbuildinfo.linter', so we need to add '.linter' after tsBuildInfoFilePath
498  const tsBuildInfoLinterFilePath: string = tsBuildInfoFilePath + '.linter';
499  deleteFile(tsBuildInfoFilePath);
500  deleteFile(tsBuildInfoLinterFilePath);
501}
502
503function deleteFile(filePath: string): void {
504  if (fs.existsSync(filePath)) {
505      fs.unlinkSync(filePath);
506  }
507}
508
509interface CacheFileName {
510  mtimeMs: number,
511  children: string[],
512  parent: string[],
513  error: boolean,
514  errorCodes?: number[]
515}
516interface NeedUpdateFlag {
517  flag: boolean;
518}
519interface CheckerResult {
520  count: number
521}
522
523interface WarnCheckerResult {
524  count: number
525}
526
527interface WholeCache {
528  runtimeOS: string,
529  sdkInfo: string,
530  fileList: Cache
531}
532type Cache = Record<string, CacheFileName>;
533export let cache: Cache = {};
534export const hotReloadSupportFiles: Set<string> = new Set();
535export const shouldResolvedFiles: Set<string> = new Set();
536export const appComponentCollection: Map<string, Set<string>> = new Map();
537const allResolvedModules: Set<string> = new Set();
538// all files of tsc and rollup for obfuscation scanning.
539export const allSourceFilePaths: Set<string> = new Set();
540// Used to collect file paths that have not been converted toUnixPath.
541export const allModuleIds: Set<string> = new Set();
542export let props: Set<string> = new Set();
543
544export let fastBuildLogger = null;
545
546export const checkerResult: CheckerResult = { count: 0 };
547export const warnCheckerResult: WarnCheckerResult = { count: 0 };
548export let languageService: ts.LanguageService = null;
549let tsImportSendable: boolean = false;
550let skipOhModulesLint: boolean = false;
551let mixCompile: boolean = false;
552export let maxMemoryInServiceChecker: number = 0;
553export function serviceChecker(rootFileNames: string[], newLogger: Object = null, resolveModulePaths: string[] = null,
554  parentEvent?: CompileEvent, rollupShareObject?: Object): void {
555  fastBuildLogger = newLogger;
556  let cacheFile: string = null;
557  tsImportSendable = rollupShareObject?.projectConfig.tsImportSendable;
558  skipOhModulesLint = rollupShareObject?.projectConfig.skipOhModulesLint;
559  mixCompile = rollupShareObject?.projectConfig.mixCompile;
560  if (projectConfig.xtsMode || process.env.watchMode === 'true') {
561    if (projectConfig.hotReload) {
562      rootFileNames.forEach(fileName => {
563        hotReloadSupportFiles.add(fileName);
564      });
565    }
566    const recordInfo = MemoryMonitor.recordStage(MemoryDefine.CREATE_LANGUAGE_SERVICE);
567    languageService = createLanguageService(rootFileNames, resolveModulePaths, parentEvent);
568    MemoryMonitor.stopRecordStage(recordInfo);
569    props = languageService.getProps();
570  } else {
571    cacheFile = path.resolve(projectConfig.cachePath, '../.ts_checker_cache');
572    const [isJsonObject, cacheJsonObject]: [boolean, WholeCache | undefined] = isJsonString(cacheFile);
573    const wholeCache: WholeCache = isJsonObject ? cacheJsonObject : { 'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {} };
574    if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) {
575      cache = wholeCache.fileList;
576    } else {
577      cache = {};
578    }
579    const recordInfo = MemoryMonitor.recordStage(MemoryDefine.CREATE_LANGUAGE_SERVICE);
580    languageService = createLanguageService(rootFileNames, resolveModulePaths, parentEvent, rollupShareObject);
581    MemoryMonitor.stopRecordStage(recordInfo);
582  }
583
584  const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance();
585  timePrinterInstance.setArkTSTimePrintSwitch(false);
586  timePrinterInstance.appendTime(ts.TimePhase.START);
587  ts.PerformanceDotting?.startAdvanced('createProgram');
588  const recordInfo = MemoryMonitor.recordStage(MemoryDefine.GET_BUILDER_PROGRAM);
589
590  globalProgram.builderProgram = languageService.getBuilderProgram(/*withLinterProgram*/ true);
591  globalProgram.program = globalProgram.builderProgram.getProgram();
592  traverseProgramSourceFiles(languageService.getProps());
593  props = languageService.getProps();
594  timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM);
595  MemoryMonitor.stopRecordStage(recordInfo);
596  ts.PerformanceDotting?.stopAdvanced('createProgram');
597
598  collectAllFiles(globalProgram.program, undefined, undefined, rollupShareObject);
599  collectFileToIgnoreDiagnostics(rootFileNames);
600  ts.PerformanceDotting?.startAdvanced('runArkTSLinterTime');
601  const runArkTSLinterRecordInfo = MemoryMonitor.recordStage(MemoryDefine.RUN_ARK_TS_LINTER);
602  const errorCodeLogger: Object | undefined = !!rollupShareObject?.getHvigorConsoleLogger ?
603    rollupShareObject?.getHvigorConsoleLogger(LINTER_SUBSYSTEM_CODE) : undefined;
604  runArkTSLinter(errorCodeLogger, parentEvent);
605  MemoryMonitor.stopRecordStage(runArkTSLinterRecordInfo);
606  ts.PerformanceDotting?.stopAdvanced('runArkTSLinterTime');
607
608  if (process.env.watchMode !== 'true') {
609    const processBuildHaprrecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP);
610    processBuildHap(cacheFile, rootFileNames, parentEvent, rollupShareObject);
611    MemoryMonitor.stopRecordStage(processBuildHaprrecordInfo);
612  }
613
614  maxMemoryInServiceChecker = process.memoryUsage().heapUsed;
615  // Release the typeChecker early and perform GC in the following scenarios:
616  // In memory-priority mode or default mode, when the preview mode is disabled in a full compilation scenario,
617  // and it is not a preview, hot reload, or cold reload scenario. The typeChecker is not released early in performance-priority mode.
618  let shouldReleaseTypeChecker: boolean = rollupShareObject?.projectConfig?.executionMode !== 'performance' && globalProgram.program &&
619    process.env.watchMode !== 'true' && !projectConfig.isPreview && !projectConfig.hotReload && !projectConfig.coldReload;
620  if (shouldReleaseTypeChecker) {
621    globalProgram.program.releaseTypeChecker();
622    const allowGC: boolean = global && global.gc && typeof global.gc === 'function';
623    if (allowGC) {
624      global.gc();
625    }
626  }
627}
628
629export function traverseProgramSourceFiles(props: Set<string>): void {
630  globalProgram.program.getSourceFiles().forEach((sourceFile: ts.SourceFile) => {
631    checkUISyntax(sourceFile, sourceFile.fileName, [], props);
632  });
633}
634
635function isJsonString(cacheFile: string): [boolean, WholeCache | undefined] {
636  if (fs.existsSync(cacheFile)) {
637    try {
638      return [true, JSON.parse(fs.readFileSync(cacheFile).toString())];
639    } catch (e) {
640      return [false, undefined];
641    }
642  } else {
643    return [false, undefined];
644  }
645}
646
647// collect the compiled files of tsc and rollup for obfuscation scanning.
648export function collectAllFiles(program?: ts.Program, rollupFileList?: IterableIterator<string>,
649  rollupObject?: Object, rollupShareObject: Object = null): void {
650  if (program) {
651    collectTscFiles(program, rollupShareObject);
652    return;
653  }
654  mergeRollUpFiles(rollupFileList, rollupObject);
655}
656
657export function collectTscFiles(program: ts.Program, rollupShareObject: Object = null): void {
658  const programAllFiles: readonly ts.SourceFile[] = program.getSourceFiles();
659  let projectRootPath: string = projectConfig.projectRootPath;
660  if (!projectRootPath) {
661    return;
662  }
663  projectRootPath = toUnixPath(projectRootPath);
664  const isMacOrWin = isWindows() || isMac();
665  const recordInfo = MemoryMonitor.recordStage(MemoryDefine.COLLECT_TSC_FILES_ALL_RESOLVED_MODULES);
666  programAllFiles.forEach(sourceFile => {
667    const fileName = toUnixPath(sourceFile.fileName);
668    // @ts-ignore
669    sourceFileDependencies.set(fileName, sourceFile.resolvedModules);
670    if (!(fileName.startsWith(projectRootPath + '/') || isOtherProjectResolvedModulesFilePaths(rollupShareObject, fileName))) {
671      return;
672    }
673    allSourceFilePaths.add(fileName);
674    allModuleIds.add(sourceFile.fileName);
675    // For the windos and mac platform, the file path in filesBuildInfo is in lowercase,
676    // while fileName of sourceFile is the original path (including uppercase).
677    if (filesBuildInfo.size > 0 &&
678      Reflect.get(sourceFile, 'version') !== filesBuildInfo.get(isMacOrWin ? tryToLowerCasePath(fileName) : fileName)) {
679      allResolvedModules.add(fileName);
680    }
681  });
682  MemoryMonitor.stopRecordStage(recordInfo);
683}
684
685function isOtherProjectResolvedModulesFilePaths(rollupShareObject: Object, fileName: string): boolean {
686  if (!!rollupShareObject && rollupShareObject.projectConfig && !!rollupShareObject.projectConfig.rootPathSet) {
687    const rootPathSet: string[] | Set<string> = rollupShareObject.projectConfig.rootPathSet;
688    if (Array.isArray(rootPathSet)) {
689      for (let i = 0; i < rootPathSet.length; i++) {
690        const pathNormalization: string = toUnixPath(rootPathSet[i]) + '/';
691        if (fileName.startsWith(pathNormalization)) {
692          return true;
693        }
694      }
695    } else {
696      return false;
697    }
698  }
699  return false;
700}
701
702export function mergeRollUpFiles(rollupFileList: IterableIterator<string>, rollupObject: Object): void {
703  const recordInfo = MemoryMonitor.recordStage(MemoryDefine.MERGE_ROLL_UP_FILES_LOCAL_PACKAGE_SET);
704  for (const moduleId of rollupFileList) {
705    if (fs.existsSync(moduleId)) {
706      allSourceFilePaths.add(toUnixPath(moduleId));
707      allModuleIds.add(moduleId);
708      addLocalPackageSet(moduleId, rollupObject);
709    }
710  }
711  MemoryMonitor.stopRecordStage(recordInfo);
712}
713
714// collect the modulename or pkgname of all local modules.
715export function addLocalPackageSet(moduleId: string, rollupObject: Object): void {
716  const moduleInfo: Object = rollupObject.getModuleInfo(moduleId);
717  const metaInfo: Object = moduleInfo.meta;
718  if (metaInfo.isLocalDependency) {
719    if (projectConfig.useNormalizedOHMUrl && metaInfo.pkgName) {
720      localPackageSet.add(metaInfo.pkgName);
721    }
722    if (!projectConfig.useNormalizedOHMUrl && metaInfo.moduleName) {
723      localPackageSet.add(metaInfo.moduleName);
724    }
725  }
726}
727
728export function emitBuildInfo(): void {
729  globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile);
730}
731
732function processBuildHap(cacheFile: string, rootFileNames: string[], parentEvent: CompileEvent,
733  rollupShareObject: Object): void {
734  ts.PerformanceDotting?.startAdvanced('diagnostic');
735  const semanticRecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP_GET_SEMANTIC_DIAGNOSTICS);
736  const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram
737    .getSyntacticDiagnostics()
738    .concat(globalProgram.builderProgram.getSemanticDiagnostics());
739  MemoryMonitor.stopRecordStage(semanticRecordInfo);
740  ts.PerformanceDotting?.stopAdvanced('diagnostic');
741  const emitBuildRecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP_EMIT_BUILD_INFO);
742  emitBuildInfo();
743  let errorCodeLogger: Object | undefined = rollupShareObject?.getHvigorConsoleLogger ?
744    rollupShareObject?.getHvigorConsoleLogger(TSC_SYSTEM_CODE) : undefined;
745
746  allDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
747    printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger);
748  });
749  MemoryMonitor.stopRecordStage(emitBuildRecordInfo);
750  if (!projectConfig.xtsMode) {
751    fse.ensureDirSync(projectConfig.cachePath);
752    fs.writeFileSync(cacheFile, JSON.stringify({
753      'runtimeOS': projectConfig.runtimeOS,
754      'sdkInfo': projectConfig.sdkInfo,
755      'fileList': cache
756    }, null, 2));
757  }
758  if (projectConfig.compileHar || projectConfig.compileShared) {
759    let emit: string | undefined = undefined;
760    let writeFile = (fileName: string, text: string, writeByteOrderMark: boolean): void => {
761      emit = text;
762    };
763    [...allResolvedModules, ...rootFileNames].forEach(moduleFile => {
764      if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar)) {
765        try {
766          if ((/\.d\.e?ts$/).test(moduleFile)) {
767            generateSourceFilesInHar(moduleFile, fs.readFileSync(moduleFile, 'utf-8'), path.extname(moduleFile),
768              projectConfig, projectConfig.modulePathMap);
769          } else if ((/\.e?ts$/).test(moduleFile)) {
770            emit = undefined;
771            let sourcefile = globalProgram.program.getSourceFile(moduleFile);
772            if (sourcefile) {
773              globalProgram.program.emit(sourcefile, writeFile, undefined, true, undefined, true);
774            }
775            if (emit) {
776              generateSourceFilesInHar(moduleFile, emit, '.d' + path.extname(moduleFile), projectConfig, projectConfig.modulePathMap);
777            }
778          }
779        } catch (err) { }
780      }
781    });
782    printDeclarationDiagnostics(errorCodeLogger);
783  }
784}
785
786function printDeclarationDiagnostics(errorCodeLogger?: Object | undefined): void {
787  globalProgram.builderProgram.getDeclarationDiagnostics().forEach((diagnostic: ts.Diagnostic) => {
788    printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger);
789  });
790}
791
792function containFormError(message: string): boolean {
793  if (/can't support form application./.test(message)) {
794    return true;
795  }
796  return false;
797}
798
799let fileToIgnoreDiagnostics: Set<string> | undefined = undefined;
800
801function collectFileToThrowDiagnostics(file: string, fileToThrowDiagnostics: Set<string>): void {
802  const normalizedFilePath: string = path.resolve(file);
803  const unixFilePath: string = toUnixPath(file);
804  if (fileToThrowDiagnostics.has(unixFilePath)) {
805    return;
806  }
807
808  fileToThrowDiagnostics.add(unixFilePath);
809  // Although the cache object filters JavaScript files when collecting dependency relationships, we still include the
810  // filtering of JavaScript files here to avoid potential omissions.
811  if ((/\.(c|m)?js$/).test(file) ||
812    !cache[normalizedFilePath] || cache[normalizedFilePath].children.length === 0) {
813    return;
814  }
815  cache[normalizedFilePath].children.forEach(file => {
816    collectFileToThrowDiagnostics(file, fileToThrowDiagnostics);
817  });
818}
819
820export function collectFileToIgnoreDiagnostics(rootFileNames: string[]): void {
821  if (getArkTSLinterMode() === ArkTSLinterMode.NOT_USE) {
822    return;
823  }
824
825  // In watch mode, the `beforeBuild` phase will clear the parent and children fields in the cache. For files that have
826  // not been modified, the information needs to be restored using the `resolvedModuleNames` variable.
827  if (process.env.watchMode === 'true') {
828    for (let [file, resolvedModules] of resolvedModulesCache) {
829      createOrUpdateCache(resolvedModules, file);
830    }
831  }
832
833  // With arkts linter enabled, `allowJs` option is set to true, resulting JavaScript files themselves and
834  // JavaScript-referenced files are included in the tsc program and checking process,
835  // potentially introducing new errors. For instance, in scenarios where an ets file imports js file imports ts file,
836  // it’s necessary to filter out errors from ts files.
837  let fileToThrowDiagnostics: Set<string> = new Set<string>();
838  rootFileNames.forEach(file => {
839    if (!(/\.(c|m)?js$/).test(file)) {
840      collectFileToThrowDiagnostics(file, fileToThrowDiagnostics);
841    }
842  });
843
844  let resolvedTypeReferenceDirectivesFiles: Set<string> = new Set<string>();
845  globalProgram.program.getResolvedTypeReferenceDirectives().forEach(
846    (elem: ts.ResolvedTypeReferenceDirective | undefined) => {
847      elem && elem.resolvedFileName && resolvedTypeReferenceDirectivesFiles.add(elem.resolvedFileName);
848  });
849
850  const ignoreDiagnosticsRecordInfo = MemoryMonitor.recordStage(MemoryDefine.FILE_TO_IGNORE_DIAGNOSTICS);
851  fileToIgnoreDiagnostics = new Set<string>();
852  globalProgram.program.getSourceFiles().forEach(sourceFile => {
853    // Previous projects had js libraries that were available through SDK, so need to filter js-file in SDK,
854    // like: hypium library
855    sourceFile.fileName &&
856    (!isInSDK(sourceFile.fileName) || (/\.(c|m)?js$/).test(sourceFile.fileName)) &&
857    !resolvedTypeReferenceDirectivesFiles.has(sourceFile.fileName) &&
858    fileToIgnoreDiagnostics.add(toUnixPath(sourceFile.fileName));
859  });
860
861  fileToThrowDiagnostics.forEach(file => {
862    fileToIgnoreDiagnostics.delete(file);
863  });
864  MemoryMonitor.stopRecordStage(ignoreDiagnosticsRecordInfo);
865}
866
867interface MessageCollection {
868  positionMessage: string,
869  message: string,
870  logMessage: string
871}
872
873export function printDiagnostic(diagnostic: ts.Diagnostic, flag?: ErrorCodeModule, errorCodeLogger?: Object | undefined): void {
874  if (projectConfig.ignoreWarning) {
875    return;
876  }
877
878  if (fileToIgnoreDiagnostics && diagnostic.file && diagnostic.file.fileName &&
879    fileToIgnoreDiagnostics.has(toUnixPath(diagnostic.file.fileName))) {
880    return;
881  }
882
883  const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
884  if (validateError(message)) {
885    if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
886      updateErrorFileCache(diagnostic);
887    }
888
889    if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) {
890      return;
891    }
892
893    // validate whether the message matches new Extend error, and modify its level for compatibility
894    if (validateNewExtend(message)) {
895      diagnostic.category = ts.DiagnosticCategory.warning;
896    }
897    const logPrefix: string = diagnostic.category === ts.DiagnosticCategory.Error ? 'ERROR' : 'WARN';
898    const etsCheckerLogger = fastBuildLogger || logger;
899    let logMessage: string;
900    if (logPrefix === 'ERROR') {
901      checkerResult.count += 1;
902    } else {
903      warnCheckerResult.count += 1;
904    }
905    let positionMessage: string = '';
906    if (diagnostic.file) {
907      const { line, character }: ts.LineAndCharacter =
908        diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
909      positionMessage = `File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}`;
910      logMessage = `ArkTS:${logPrefix} ${positionMessage}\n ${message}\n`;
911    } else {
912      logMessage = `ArkTS:${logPrefix}: ${message}`;
913    }
914
915    if (errorCodeLogger) {
916      const msgCollection: MessageCollection = { positionMessage, message, logMessage };
917      printErrorCode(diagnostic, etsCheckerLogger, msgCollection, errorCodeLogger, flag);
918    } else {
919      if (diagnostic.category === ts.DiagnosticCategory.Error) {
920        etsCheckerLogger.error('\u001b[31m' + logMessage);
921      } else {
922        etsCheckerLogger.warn('\u001b[33m' + logMessage);
923      }
924    }
925  }
926}
927
928function printErrorCode(diagnostic: ts.Diagnostic, etsCheckerLogger: Object,
929  msgCollection: MessageCollection, errorCodeLogger: Object, flag: ErrorCodeModule | undefined): void {
930  const { positionMessage, message, logMessage } = msgCollection;
931  // If the diagnostic is not an error, log a warning and return early.
932  if (diagnostic.category !== ts.DiagnosticCategory.Error) {
933    etsCheckerLogger.warn('\u001b[33m' + logMessage);
934    return;
935  }
936
937  // Check for TSC error codes
938  if (flag === ErrorCodeModule.TSC &&
939    validateUseErrorCodeLogger(ErrorCodeModule.TSC, diagnostic.code)) {
940    const errorCode = ts.getErrorCode(diagnostic);
941    errorCodeLogger.printError(errorCode);
942    return;
943  }
944
945  // Check for LINTER error codes
946  if (flag === ErrorCodeModule.LINTER || (flag === ErrorCodeModule.TSC &&
947    validateUseErrorCodeLogger(ErrorCodeModule.LINTER, diagnostic.code))) {
948    const linterErrorInfo: HvigorErrorInfo = transfromErrorCode(diagnostic.code, positionMessage, message);
949    errorCodeLogger.printError(linterErrorInfo);
950    return;
951  }
952
953  // Check for ArkUI error codes
954  if (flag === ErrorCodeModule.UI || (flag === ErrorCodeModule.TSC &&
955    validateUseErrorCodeLogger(ErrorCodeModule.UI, diagnostic.code))) {
956    const uiErrorInfo: HvigorErrorInfo | undefined = buildErrorInfoFromDiagnostic(
957      diagnostic.code, positionMessage, message);
958    if (!uiErrorInfo) {
959      etsCheckerLogger.error('\u001b[31m' + logMessage);
960    } else {
961      errorCodeLogger.printError(uiErrorInfo);
962    }
963    return;
964  }
965
966  // If the error is not a TSC/Linter/ArkUI error, log using etsCheckerLogger
967  etsCheckerLogger.error('\u001b[31m' + logMessage);
968}
969
970function validateUseErrorCodeLogger(flag: ErrorCodeModule, code: number): boolean {
971  if (!ts.getErrorCodeArea || !ts.getErrorCode) {
972    return false;
973  }
974  if (flag === ErrorCodeModule.TSC) {
975    return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.TSC;
976  } else if (flag === ErrorCodeModule.LINTER) {
977    return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.LINTER;
978  } else if (flag === ErrorCodeModule.UI) {
979    return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.UI;
980  }
981  return false;
982}
983
984function validateError(message: string): boolean {
985  const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/;
986  const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/;
987  if (matchMessage(message, props, propInfoReg) ||
988    matchMessage(message, props, stateInfoReg)) {
989    return false;
990  }
991  return true;
992}
993
994
995/**
996 * validate whether ets diagnostic has collected the error about new Extend function
997 * @param {string} message
998 * @return {*}  {boolean}
999 */
1000function validateNewExtend(message: string): boolean {
1001  const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/;
1002  if (matchMessage(message, newExtendCollection, stateInfoReg)) {
1003    return true;
1004  }
1005  return false;
1006}
1007
1008function matchMessage(message: string, nameArr: any, reg: RegExp): boolean {
1009  if (reg.test(message)) {
1010    const match: string[] = message.match(reg);
1011    if (match[1] && nameArr.has(match[1])) {
1012      return true;
1013    }
1014  }
1015  return false;
1016}
1017
1018function updateErrorFileCache(diagnostic: ts.Diagnostic): void {
1019  if (!diagnostic.file) {
1020    return;
1021  }
1022
1023  let cacheInfo: CacheFileName = cache[path.resolve(diagnostic.file.fileName)];
1024  if (cacheInfo) {
1025    cacheInfo.error = true;
1026    if (!cacheInfo.errorCodes) {
1027      cacheInfo.errorCodes = [];
1028    }
1029    cacheInfo.errorCodes.includes(diagnostic.code) || cacheInfo.errorCodes.push(diagnostic.code);
1030  }
1031}
1032
1033function filterInput(rootFileNames: string[]): string[] {
1034  return rootFileNames.filter((file: string) => {
1035    const needUpdate: NeedUpdateFlag = { flag: false };
1036    const alreadyCheckedFiles: Set<string> = new Set();
1037    checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles);
1038    if (!needUpdate.flag) {
1039      storedFileInfo.changeFiles.push(path.resolve(file));
1040    }
1041    return needUpdate.flag;
1042  });
1043}
1044
1045function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set<string>): void {
1046  if (alreadyCheckedFiles.has(file)) {
1047    return;
1048  } else {
1049    alreadyCheckedFiles.add(file);
1050  }
1051
1052  if (needUpdate.flag) {
1053    return;
1054  }
1055
1056  const value: CacheFileName = cache[file];
1057  const mtimeMs: number = fs.statSync(file).mtimeMs;
1058  if (value) {
1059    if (value.error || value.mtimeMs !== mtimeMs) {
1060      needUpdate.flag = true;
1061      return;
1062    }
1063    for (let i = 0; i < value.children.length; ++i) {
1064      if (fs.existsSync(value.children[i])) {
1065        checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles);
1066      } else {
1067        needUpdate.flag = true;
1068      }
1069    }
1070  } else {
1071    cache[file] = { mtimeMs, children: [], parent: [], error: false };
1072    needUpdate.flag = true;
1073  }
1074}
1075
1076const fileExistsCache: Map<string, boolean> = new Map<string, boolean>();
1077const dirExistsCache: Map<string, boolean> = new Map<string, boolean>();
1078const moduleResolutionHost: ts.ModuleResolutionHost = {
1079  fileExists: (fileName: string): boolean => {
1080    let exists = fileExistsCache.get(fileName);
1081    if (exists === undefined) {
1082      exists = ts.sys.fileExists(fileName);
1083      fileExistsCache.set(fileName, exists);
1084    }
1085    return exists;
1086  },
1087  directoryExists: (directoryName: string): boolean => {
1088    let exists = dirExistsCache.get(directoryName);
1089    if (exists === undefined) {
1090      exists = ts.sys.directoryExists(directoryName);
1091      dirExistsCache.set(directoryName, exists);
1092    }
1093    return exists;
1094  },
1095  readFile(fileName: string): string | undefined {
1096    return ts.sys.readFile(fileName);
1097  },
1098  realpath(path: string): string {
1099    return ts.sys.realpath(path);
1100  },
1101  trace(s: string): void {
1102    console.info(s);
1103  }
1104};
1105
1106//This is only for test
1107export const moduleResolutionHostTest = moduleResolutionHost;
1108
1109export function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | ts.FileReference[]): ts.ResolvedTypeReferenceDirective[] {
1110  if (typeDirectiveNames.length === 0) {
1111    return [];
1112  }
1113
1114  const resolvedTypeReferenceCache: ts.ResolvedTypeReferenceDirective[] = [];
1115  const cache: Map<string, ts.ResolvedTypeReferenceDirective> = new Map<string, ts.ResolvedTypeReferenceDirective>();
1116  const containingFile: string = path.join(projectConfig.modulePath, 'build-profile.json5');
1117
1118  for (const entry of typeDirectiveNames) {
1119    const typeName = isString(entry) ? entry : entry.fileName.toLowerCase();
1120    if (!cache.has(typeName)) {
1121      const resolvedFile = ts.resolveTypeReferenceDirective(typeName, containingFile, compilerOptions, moduleResolutionHost);
1122      if (!resolvedFile || !resolvedFile.resolvedTypeReferenceDirective) {
1123        logger.error('\u001b[31m', `ArkTS:Cannot find type definition file for: ${typeName}\n`);
1124      }
1125      const result: ts.ResolvedTypeReferenceDirective = resolvedFile.resolvedTypeReferenceDirective;
1126      cache.set(typeName, result);
1127      resolvedTypeReferenceCache.push(result);
1128    }
1129  }
1130  return resolvedTypeReferenceCache;
1131}
1132
1133// resolvedModulesCache records the files and their dependencies of program.
1134export const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map();
1135
1136export function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] {
1137  ts.PerformanceDotting?.startAdvanced('resolveModuleNames');
1138  const resolvedModules: ts.ResolvedModuleFull[] = [];
1139  const cacheFileContent: ts.ResolvedModuleFull[] = resolvedModulesCache.get(path.resolve(containingFile));
1140  if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile)) ||
1141    !(cacheFileContent && cacheFileContent.length === moduleNames.length)) {
1142    for (const moduleName of moduleNames) {
1143      const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost);
1144      if (result.resolvedModule) {
1145        if (result.resolvedModule.resolvedFileName &&
1146          path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) {
1147          const resultDETSPath: string =
1148            result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS);
1149          if (ts.sys.fileExists(resultDETSPath)) {
1150            resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS));
1151          } else {
1152            resolvedModules.push(result.resolvedModule);
1153          }
1154        } else {
1155          resolvedModules.push(result.resolvedModule);
1156        }
1157      } else if (new RegExp(`^@(${sdkConfigPrefix})\\.`, 'i').test(moduleName.trim())) {
1158        let apiFileExist: boolean = false;
1159        for (let i = 0; i < sdkConfigs.length; i++) {
1160          const sdkConfig = sdkConfigs[i];
1161          const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(sdkConfig.apiPath, moduleName, ['.d.ts', '.d.ets']);
1162          const modulePath: string = resolveModuleInfo.modulePath;
1163          const isDETS: boolean = resolveModuleInfo.isEts;
1164          if (systemModules.includes(moduleName + (isDETS ? '.d.ets' : '.d.ts')) && ts.sys.fileExists(modulePath)) {
1165            resolvedModules.push(getResolveModule(modulePath, isDETS ? '.d.ets' : '.d.ts'));
1166            apiFileExist = true;
1167            break;
1168          }
1169        }
1170        if (!apiFileExist) {
1171          resolvedModules.push(null);
1172        }
1173      } else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) {
1174        const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
1175        if (ts.sys.fileExists(modulePath)) {
1176          resolvedModules.push(getResolveModule(modulePath, '.ets'));
1177        } else {
1178          resolvedModules.push(null);
1179        }
1180      } else if (/\.ts$/.test(moduleName)) {
1181        const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
1182        if (ts.sys.fileExists(modulePath)) {
1183          resolvedModules.push(getResolveModule(modulePath, '.ts'));
1184        } else {
1185          resolvedModules.push(null);
1186        }
1187      } else {
1188        const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts');
1189        const systemDETSModulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ets');
1190        const kitModulePath: string = path.resolve(__dirname, '../../../kits', moduleName + '.d.ts');
1191        const kitSystemDETSModulePath: string = path.resolve(__dirname, '../../../kits', moduleName + '.d.ets');
1192        const suffix: string = /\.js$/.test(moduleName) ? '' : '.js';
1193        const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix);
1194        const fileModulePath: string =
1195          path.resolve(__dirname, '../node_modules', moduleName + '/index.js');
1196        const DETSModulePath: string = path.resolve(path.dirname(containingFile),
1197          /\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS);
1198        if (ts.sys.fileExists(modulePath)) {
1199          resolvedModules.push(getResolveModule(modulePath, '.d.ts'));
1200        } else if (ts.sys.fileExists(systemDETSModulePath)) {
1201          resolvedModules.push(getResolveModule(systemDETSModulePath, '.d.ets'));
1202        } else if (ts.sys.fileExists(kitModulePath)) {
1203          resolvedModules.push(getResolveModule(kitModulePath, '.d.ts'));
1204        } else if (ts.sys.fileExists(kitSystemDETSModulePath)) {
1205          resolvedModules.push(getResolveModule(kitSystemDETSModulePath, '.d.ets'));
1206        } else if (ts.sys.fileExists(jsModulePath)) {
1207          resolvedModules.push(getResolveModule(jsModulePath, '.js'));
1208        } else if (ts.sys.fileExists(fileModulePath)) {
1209          resolvedModules.push(getResolveModule(fileModulePath, '.js'));
1210        } else if (ts.sys.fileExists(DETSModulePath)) {
1211          resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets'));
1212        } else {
1213          let srcIndex: number = 0;
1214          if (!!projectConfig.projectPath) {
1215            srcIndex = projectConfig.projectPath.indexOf('src' + path.sep + 'main');
1216          }
1217          let DETSModulePathFromModule: string;
1218          if (srcIndex > 0) {
1219            DETSModulePathFromModule = path.resolve(
1220              projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS);
1221            if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) {
1222              resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets'));
1223            } else {
1224              resolvedModules.push(null);
1225            }
1226          } else {
1227            resolvedModules.push(null);
1228          }
1229        }
1230      }
1231      if (projectConfig.hotReload && resolvedModules.length &&
1232        resolvedModules[resolvedModules.length - 1]) {
1233        hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName));
1234      }
1235      if (collectShouldPackedFiles(resolvedModules)) {
1236        allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName);
1237      }
1238    }
1239    if (!projectConfig.xtsMode) {
1240      createOrUpdateCache(resolvedModules, path.resolve(containingFile));
1241    }
1242    resolvedModulesCache.set(path.resolve(containingFile), resolvedModules);
1243    ts.PerformanceDotting?.stopAdvanced('resolveModuleNames');
1244    return resolvedModules;
1245  }
1246  ts.PerformanceDotting?.stopAdvanced('resolveModuleNames');
1247  return resolvedModulesCache.get(path.resolve(containingFile));
1248}
1249
1250export interface ResolveModuleInfo {
1251  modulePath: string;
1252  isEts: boolean;
1253}
1254
1255function collectShouldPackedFiles(resolvedModules: ts.ResolvedModuleFull[]): boolean | RegExpMatchArray {
1256  return (projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] &&
1257    resolvedModules[resolvedModules.length - 1].resolvedFileName &&
1258    (path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/) ||
1259      path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/\.d\.e?ts$/) &&
1260      path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(
1261        new RegExp('\\' + path.sep + 'src' + '\\' + path.sep + 'main' + '\\' + path.sep)));
1262}
1263
1264function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void {
1265  const children: string[] = [];
1266  const error: boolean = false;
1267  resolvedModules.forEach(moduleObj => {
1268    if (moduleObj && moduleObj.resolvedFileName && /\.(ets|ts)$/.test(moduleObj.resolvedFileName)) {
1269      const file: string = path.resolve(moduleObj.resolvedFileName);
1270      const mtimeMs: number = fs.statSync(file).mtimeMs;
1271      children.push(file);
1272      const value: CacheFileName = cache[file];
1273      if (value) {
1274        value.mtimeMs = mtimeMs;
1275        value.error = error;
1276        value.parent = value.parent || [];
1277        value.parent.push(path.resolve(containingFile));
1278        value.parent = [...new Set(value.parent)];
1279      } else {
1280        cache[file] = { mtimeMs, children: [], parent: [containingFile], error };
1281      }
1282    }
1283  });
1284  cache[path.resolve(containingFile)] = {
1285    mtimeMs: fs.statSync(containingFile).mtimeMs, children,
1286    parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ?
1287      cache[path.resolve(containingFile)].parent : [], error
1288  };
1289}
1290
1291export function createWatchCompilerHost(rootFileNames: string[],
1292  reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function,
1293  isPipe: boolean = false, resolveModulePaths: string[] = null): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> {
1294  if (projectConfig.hotReload) {
1295    rootFileNames.forEach(fileName => {
1296      hotReloadSupportFiles.add(fileName);
1297    });
1298  }
1299  if (!(isPipe && process.env.compileTool === 'rollup')) {
1300    setCompilerOptions(resolveModulePaths);
1301  }
1302  // Change the buildInfo file path, or it will cover the buildInfo file created before.
1303  const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', WATCH_COMPILER_BUILD_INFO_SUFFIX);
1304  const watchCompilerOptions = {...compilerOptions, tsBuildInfoFile: buildInfoPath};
1305  const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
1306  const host = ts.createWatchCompilerHost(
1307    [...rootFileNames, ...readDeaclareFiles()], watchCompilerOptions,
1308    ts.sys, createProgram, reportDiagnostic,
1309    (diagnostic: ts.Diagnostic) => {
1310      if ([6031, 6032].includes(diagnostic.code)) {
1311        if (!isPipe) {
1312          process.env.watchTs = 'start';
1313          resetErrorCount();
1314        }
1315      }
1316      // End of compilation in watch mode flag.
1317      if ([6193, 6194].includes(diagnostic.code)) {
1318        if (!isPipe) {
1319          process.env.watchTs = 'end';
1320          if (fastBuildLogger) {
1321            fastBuildLogger.debug(TS_WATCH_END_MSG);
1322            tsWatchEmitter.emit(TS_WATCH_END_MSG);
1323          }
1324        }
1325        delayPrintLogCount();
1326      }
1327    });
1328  host.readFile = (fileName: string) => {
1329    if (!fs.existsSync(fileName)) {
1330      return undefined;
1331    }
1332    if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
1333      let content: string = processContent(fs.readFileSync(fileName).toString(), fileName);
1334      const extendFunctionInfo: extendInfo[] = [];
1335      content = instanceInsteadThis(content, fileName, extendFunctionInfo, props);
1336      return content;
1337    }
1338    return fs.readFileSync(fileName).toString();
1339  };
1340  host.resolveModuleNames = resolveModuleNames;
1341  host.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives;
1342  return host;
1343}
1344
1345export function watchChecker(rootFileNames: string[], newLogger: any = null, resolveModulePaths: string[] = null): void {
1346  fastBuildLogger = newLogger;
1347  globalProgram.watchProgram = ts.createWatchProgram(
1348    createWatchCompilerHost(rootFileNames, printDiagnostic, () => { }, () => { }, false, resolveModulePaths));
1349}
1350
1351export function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[],
1352  props: Set<string>): string {
1353  extendFunctionInfo.reverse().forEach((item) => {
1354    const subStr: string = content.substring(item.start, item.end);
1355    const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => {
1356      return item1 + item.compName + 'Instance' + item2;
1357    });
1358    content = content.slice(0, item.start) + insert + content.slice(item.end);
1359  });
1360  return content;
1361}
1362
1363export function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull {
1364  return {
1365    resolvedFileName: modulePath,
1366    isExternalLibraryImport: false,
1367    extension: type
1368  };
1369}
1370
1371export const dollarCollection: Set<string> = new Set();
1372export const extendCollection: Set<string> = new Set();
1373// a new set defined to collect @Extend functions with new format
1374const newExtendCollection: Set<string> = new Set();
1375export const importModuleCollection: Set<string> = new Set();
1376
1377function checkUISyntax(sourceFile: ts.SourceFile, fileName: string, extendFunctionInfo: extendInfo[],
1378  props: Set<string>): void {
1379  if (/\.ets$/.test(fileName) && !/\.d.ets$/.test(fileName)) {
1380    if (process.env.compileMode === 'moduleJson' ||
1381      path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) {
1382      collectComponents(sourceFile);
1383      collectionCustomizeStyles(sourceFile);
1384      parseAllNode(sourceFile, sourceFile, extendFunctionInfo);
1385      dollarCollection.forEach((item) => {
1386        props.add(item);
1387      });
1388      extendCollection.forEach((item) => {
1389        props.add(item);
1390      });
1391    }
1392  }
1393}
1394
1395function collectionCustomizeStyles(node: ts.Node): void {
1396  if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) &&
1397    (isUIDecorator(node, COMPONENT_STYLES_DECORATOR) || isUIDecorator(node, COMPONENT_EXTEND_DECORATOR)) &&
1398    node.name && ts.isIdentifier(node.name)) {
1399    BUILDIN_STYLE_NAMES.add(node.name.escapedText.toString());
1400  }
1401  if (ts.isSourceFile(node)) {
1402    node.statements.forEach((item: ts.Node) => {
1403      return collectionCustomizeStyles(item);
1404    });
1405  } else if (ts.isStructDeclaration(node)) {
1406    node.members.forEach((item: ts.Node) => {
1407      return collectionCustomizeStyles(item);
1408    });
1409  }
1410}
1411
1412function isUIDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration |
1413  ts.StructDeclaration | ts.ClassDeclaration, decortorName: string): boolean {
1414  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
1415  if (decorators && decorators.length) {
1416    for (let i = 0; i < decorators.length; i++) {
1417      const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').trim();
1418      if (originalDecortor === decortorName) {
1419        return true;
1420      } else {
1421        return false;
1422      }
1423    }
1424  }
1425  return false;
1426}
1427function collectComponents(node: ts.SourceFile): void {
1428  // @ts-ignore
1429  if (process.env.watchMode !== 'true' && node.identifiers && node.identifiers.size) {
1430    // @ts-ignore
1431    for (const key of node.identifiers.keys()) {
1432      if (JS_BIND_COMPONENTS.has(key)) {
1433        appComponentCollection.get(path.join(node.fileName)).add(key);
1434      }
1435    }
1436  }
1437}
1438
1439function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void {
1440  if (ts.isStructDeclaration(node)) {
1441    if (node.members) {
1442      node.members.forEach(item => {
1443        if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) {
1444          const propertyName: string = item.name.getText();
1445          const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item);
1446          if (decorators && decorators.length) {
1447            for (let i = 0; i < decorators.length; i++) {
1448              const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim();
1449              if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {
1450                dollarCollection.add('$' + propertyName);
1451              }
1452            }
1453          }
1454        }
1455      });
1456    }
1457  }
1458  if (process.env.watchMode !== 'true' && ts.isIfStatement(node)) {
1459    appComponentCollection.get(path.join(sourceFileNode.fileName)).add(COMPONENT_IF);
1460  }
1461  if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION ||
1462    (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) &&
1463    hasDecorator(node, COMPONENT_BUILDER_DECORATOR) ||
1464    ts.isMethodDeclaration(node) && hasDecorator(node, COMPONENT_LOCAL_BUILDER_DECORATOR)) {
1465    if (node.body && node.body.statements && node.body.statements.length) {
1466      const checkProp: ts.NodeArray<ts.Statement> = node.body.statements;
1467      checkProp.forEach((item, index) => {
1468        traverseBuild(item, index);
1469      });
1470    }
1471  }
1472  if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) {
1473    if (node.body && node.body.statements && node.body.statements.length &&
1474      !isOriginalExtend(node.body)) {
1475      extendFunctionInfo.push({
1476        start: node.pos,
1477        end: node.end,
1478        compName: isExtendFunction(node, { decoratorName: '', componentName: '' })
1479      });
1480    }
1481  }
1482  ts.forEachChild(node, (child: ts.Node) => parseAllNode(child, sourceFileNode, extendFunctionInfo));
1483}
1484
1485function isForeachAndLzayForEach(node: ts.Node): boolean {
1486  return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) &&
1487    FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] &&
1488    ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body);
1489}
1490
1491function getComponentName(node: ts.Node): string {
1492  let temp = node.expression;
1493  let name: string;
1494  while (temp) {
1495    if (ts.isIdentifier(temp) && temp.parent && (ts.isCallExpression(temp.parent) ||
1496      ts.isEtsComponentExpression(temp.parent))) {
1497      name = temp.escapedText.toString();
1498      break;
1499    }
1500    temp = temp.expression;
1501  }
1502  return name;
1503}
1504
1505function traverseBuild(node: ts.Node, index: number): void {
1506  if (ts.isExpressionStatement(node)) {
1507    const parentComponentName: string = getComponentName(node);
1508    node = node.expression;
1509    while (node) {
1510      if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) &&
1511        ts.isIdentifier(node.expression) && !DOLLAR_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) {
1512        node.body.statements.forEach((item: ts.Statement, indexBlock: number) => {
1513          traverseBuild(item, indexBlock);
1514        });
1515        break;
1516      } else if (isForeachAndLzayForEach(node)) {
1517        node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => {
1518          traverseBuild(item, indexBlock);
1519        });
1520        break;
1521      } else {
1522        loopNodeFindDoubleDollar(node, parentComponentName);
1523        if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) &&
1524          ts.isIdentifier(node.expression)) {
1525          node.body.statements.forEach((item: ts.Statement, indexBlock: number) => {
1526            traverseBuild(item, indexBlock);
1527          });
1528          break;
1529        }
1530      }
1531      node = node.expression;
1532    }
1533  } else if (ts.isIfStatement(node)) {
1534    ifInnerDollarAttribute(node);
1535  }
1536}
1537
1538function ifInnerDollarAttribute(node: ts.IfStatement): void {
1539  if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) {
1540    node.thenStatement.statements.forEach((item, indexIfBlock) => {
1541      traverseBuild(item, indexIfBlock);
1542    });
1543  }
1544  if (node.elseStatement) {
1545    elseInnerDollarAttribute(node);
1546  }
1547}
1548
1549function elseInnerDollarAttribute(node: ts.IfStatement): void {
1550  if (ts.isIfStatement(node.elseStatement) && node.elseStatement.thenStatement && ts.isBlock(node.elseStatement.thenStatement)) {
1551    traverseBuild(node.elseStatement, 0);
1552  } else if (ts.isBlock(node.elseStatement) && node.elseStatement.statements) {
1553    node.elseStatement.statements.forEach((item, indexElseBlock) => {
1554      traverseBuild(item, indexElseBlock);
1555    });
1556  }
1557}
1558
1559function isPropertiesAddDoubleDollar(node: ts.Node): boolean {
1560  if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) {
1561    return true;
1562  } else if (ts.isEtsComponentExpression(node) && ts.isIdentifier(node.expression) &&
1563    DOLLAR_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) {
1564    return true;
1565  } else {
1566    return false;
1567  }
1568}
1569function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void {
1570  if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
1571    const argument: ts.NodeArray<ts.Node> = node.arguments;
1572    const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name;
1573    if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) {
1574      argument.forEach((item: ts.Node) => {
1575        doubleDollarCollection(item);
1576      });
1577    }
1578  } else if (isPropertiesAddDoubleDollar(node)) {
1579    node.arguments.forEach((item: ts.Node) => {
1580      if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) {
1581        item.properties.forEach((param: ts.Node) => {
1582          if (isObjectPram(param, parentComponentName)) {
1583            doubleDollarCollection(param.initializer);
1584          }
1585        });
1586      } else if (ts.isPropertyAccessExpression(item) && (handleComponentDollarBlock(node as ts.CallExpression, parentComponentName) ||
1587        STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()))) {
1588        doubleDollarCollection(item);
1589      }
1590    });
1591  }
1592}
1593
1594function handleComponentDollarBlock(node: ts.CallExpression, parentComponentName: string): boolean {
1595  return ts.isCallExpression(node) && ts.isIdentifier(node.expression) &&
1596    DOLLAR_BLOCK_INTERFACE.has(parentComponentName) && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
1597    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(node.expression.escapedText.toString());
1598}
1599
1600function doubleDollarCollection(item: ts.Node): void {
1601  if (item.getText().startsWith($$)) {
1602    while (item.expression) {
1603      item = item.expression;
1604    }
1605    dollarCollection.add(item.getText());
1606  }
1607}
1608
1609function isObjectPram(param: ts.Node, parentComponentName: string): boolean {
1610  return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) &&
1611    param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
1612    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText());
1613}
1614
1615function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean {
1616  return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
1617    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) ||
1618    STYLE_ADD_DOUBLE_DOLLAR.has(propertyName);
1619}
1620
1621function processContent(source: string, id: string): string {
1622  if (fastBuildLogger) {
1623    source = visualTransform(source, id, fastBuildLogger);
1624  }
1625  source = preprocessExtend(source, extendCollection);
1626  source = preprocessNewExtend(source, newExtendCollection);
1627  return source;
1628}
1629
1630function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set<string>): void {
1631  if (shouldResolvedFiles.has(file)) {
1632    return;
1633  }
1634  shouldResolvedFiles.add(file);
1635  if (cache && cache[file] && cache[file].parent) {
1636    cache[file].parent.forEach((item) => {
1637      judgeFileShouldResolved(item, shouldResolvedFiles);
1638    });
1639    cache[file].parent = [];
1640  }
1641  if (cache && cache[file] && cache[file].children) {
1642    cache[file].children.forEach((item) => {
1643      judgeFileShouldResolved(item, shouldResolvedFiles);
1644    });
1645    cache[file].children = [];
1646  }
1647}
1648
1649export function incrementWatchFile(watchModifiedFiles: string[],
1650  watchRemovedFiles: string[]): void {
1651  const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles];
1652  if (changedFiles.length) {
1653    shouldResolvedFiles.clear();
1654  }
1655  changedFiles.forEach((file) => {
1656    judgeFileShouldResolved(file, shouldResolvedFiles);
1657  });
1658}
1659
1660export function runArkTSLinter(errorCodeLogger?: Object | undefined, parentEvent?: CompileEvent): void {
1661  const originProgram: ts.BuilderProgram = globalProgram.builderProgram;
1662
1663  const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance();
1664
1665  const arkTSLinterDiagnostics = doArkTSLinter(getArkTSVersion(),
1666    getArkTSLinterMode(),
1667    originProgram,
1668    printArkTSLinterDiagnostic,
1669    !projectConfig.xtsMode,
1670    buildInfoWriteFile,
1671    errorCodeLogger);
1672
1673  ts.PerformanceDotting?.startAdvanced('updateErrorFile');
1674  if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
1675    arkTSLinterDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
1676      updateErrorFileCache(diagnostic);
1677    });
1678    timePrinterInstance.appendTime(ts.TimePhase.UPDATE_ERROR_FILE);
1679  }
1680  ts.PerformanceDotting?.stopAdvanced('updateErrorFile');
1681  timePrinterInstance.printTimes();
1682  ts.ArkTSLinterTimePrinter.destroyInstance();
1683}
1684
1685function printArkTSLinterDiagnostic(diagnostic: ts.Diagnostic, errorCodeLogger?: Object | undefined): void {
1686  if (diagnostic.category === ts.DiagnosticCategory.Error && (isInOhModuleFile(diagnostic) || isEtsDeclFileInSdk(diagnostic))) {
1687    const originalCategory = diagnostic.category;
1688    diagnostic.category = ts.DiagnosticCategory.Warning;
1689    printDiagnostic(diagnostic);
1690    diagnostic.category = originalCategory;
1691    return;
1692  }
1693  printDiagnostic(diagnostic, ErrorCodeModule.LINTER, errorCodeLogger);
1694}
1695
1696function isEtsDeclFileInSdk(diagnostics: ts.Diagnostic): boolean {
1697  if (diagnostics.file?.fileName === undefined) {
1698    return false;
1699  }
1700  return isInSDK(diagnostics.file.fileName) && diagnostics.file.fileName.endsWith('.ets');
1701}
1702
1703function isInOhModuleFile(diagnostics: ts.Diagnostic): boolean {
1704  return (diagnostics.file !== undefined) &&
1705    ((diagnostics.file.fileName.indexOf('/oh_modules/') !== -1) || diagnostics.file.fileName.indexOf('\\oh_modules\\') !== -1);
1706}
1707
1708function isInSDK(fileName: string | undefined): boolean {
1709  if (projectConfig.etsLoaderPath === undefined || fileName === undefined) {
1710    return false;
1711  }
1712  const sdkPath = path.resolve(projectConfig.etsLoaderPath, '../../../');
1713  return path.resolve(fileName).startsWith(sdkPath);
1714}
1715
1716export function getArkTSLinterMode(): ArkTSLinterMode {
1717  if (!partialUpdateConfig.executeArkTSLinter) {
1718    return ArkTSLinterMode.NOT_USE;
1719  }
1720
1721  if (!partialUpdateConfig.standardArkTSLinter) {
1722    return ArkTSLinterMode.COMPATIBLE_MODE;
1723  }
1724
1725  if (isStandardMode()) {
1726    return ArkTSLinterMode.STANDARD_MODE;
1727  }
1728  return ArkTSLinterMode.COMPATIBLE_MODE;
1729}
1730
1731export function isStandardMode(): boolean {
1732  const STANDARD_MODE_COMPATIBLE_SDK_VERSION = 10;
1733  if (projectConfig &&
1734    projectConfig.compatibleSdkVersion &&
1735    projectConfig.compatibleSdkVersion >= STANDARD_MODE_COMPATIBLE_SDK_VERSION) {
1736    return true;
1737  }
1738  return false;
1739}
1740
1741function getArkTSVersion(): ArkTSVersion {
1742  if (projectConfig.arkTSVersion === '1.0') {
1743    return ArkTSVersion.ArkTS_1_0;
1744  } else if (projectConfig.arkTSVersion === '1.1') {
1745    return ArkTSVersion.ArkTS_1_1;
1746  } else if (projectConfig.arkTSVersion !== undefined) {
1747    const arkTSVersionLogger = fastBuildLogger || logger;
1748    arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version\n');
1749  }
1750
1751  if (partialUpdateConfig.arkTSVersion === '1.0') {
1752    return ArkTSVersion.ArkTS_1_0;
1753  } else if (partialUpdateConfig.arkTSVersion === '1.1') {
1754    return ArkTSVersion.ArkTS_1_1;
1755  } else if (partialUpdateConfig.arkTSVersion !== undefined) {
1756    const arkTSVersionLogger = fastBuildLogger || logger;
1757    arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version in metadata\n');
1758  }
1759
1760  return ArkTSVersion.ArkTS_1_1;
1761}
1762
1763enum TargetESVersion {
1764  ES2017 = 'ES2017',
1765  ES2021 = 'ES2021',
1766}
1767
1768function getTargetESVersion(): TargetESVersion {
1769  const targetESVersion = projectConfig?.projectArkOption?.tscConfig?.targetESVersion;
1770  if (targetESVersion === 'ES2017') {
1771    return TargetESVersion.ES2017;
1772  } else if (targetESVersion === 'ES2021') {
1773    return TargetESVersion.ES2021;
1774  } else if (targetESVersion !== undefined) {
1775    const targetESVersionLogger = fastBuildLogger || logger;
1776    targetESVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid Target ES version\n');
1777  }
1778  return TargetESVersion.ES2021;
1779}
1780
1781export function getMaxFlowDepth(): number {
1782  // The value of maxFlowDepth ranges from 2000 to 65535.
1783  let maxFlowDepth: number | undefined = projectConfig?.projectArkOption?.tscConfig?.maxFlowDepth;
1784
1785  if (maxFlowDepth === undefined) {
1786    maxFlowDepth = MAX_FLOW_DEPTH_DEFAULT_VALUE;
1787  } else if (maxFlowDepth < MAX_FLOW_DEPTH_DEFAULT_VALUE || maxFlowDepth > MAX_FLOW_DEPTH_MAXIMUM_VALUE) {
1788    const maxFlowDepthLogger = fastBuildLogger || logger;
1789    maxFlowDepth = MAX_FLOW_DEPTH_DEFAULT_VALUE;
1790    maxFlowDepthLogger.warn('\u001b[33m' + 'ArkTS: Invalid maxFlowDepth for control flow analysis.' +
1791      `The value of maxFlowDepth ranges from ${MAX_FLOW_DEPTH_DEFAULT_VALUE} to ${MAX_FLOW_DEPTH_MAXIMUM_VALUE}.\n` +
1792      'If the modification does not take effect, set maxFlowDepth to the default value.');
1793  }
1794  return maxFlowDepth;
1795}
1796
1797interface TargetESVersionLib {
1798  ES2017: string[],
1799  ES2021: string[],
1800}
1801
1802const targetESVersionLib: TargetESVersionLib = {
1803  // When target is es2017, the lib is es2020.
1804  ES2017: ['ES2020'],
1805  ES2021: ['ES2021'],
1806};
1807
1808function getTargetESVersionLib(): string[] {
1809  const targetESVersion = projectConfig?.projectArkOption?.tscConfig?.targetESVersion;
1810  if (targetESVersion === 'ES2017') {
1811    return targetESVersionLib.ES2017;
1812  } else if (targetESVersion === 'ES2021') {
1813    return targetESVersionLib.ES2021;
1814  } else if (targetESVersion !== undefined) {
1815    const targetESVersionLogger = fastBuildLogger || logger;
1816    targetESVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid Target ES version\n');
1817  }
1818  return targetESVersionLib.ES2021;
1819}
1820
1821function initEtsStandaloneCheckerConfig(logger, config): void {
1822  fastBuildLogger = logger;
1823  if (config.packageManagerType === 'ohpm') {
1824    config.packageDir = 'oh_modules';
1825    config.packageJson = 'oh-package.json5';
1826  } else {
1827    config.packageDir = 'node_modules';
1828    config.packageJson = 'package.json';
1829  }
1830  if (config.aceModuleJsonPath && fs.existsSync(config.aceModuleJsonPath)) {
1831    process.env.compileMode = 'moduleJson';
1832  }
1833  Object.assign(projectConfig, config);
1834}
1835
1836function resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode: string): void {
1837  resetProjectConfig();
1838  resetEtsCheck();
1839  fastBuildLogger = beforeInitFastBuildLogger;
1840  process.env.compileMode = beforeInitCompileMode;
1841}
1842
1843export function etsStandaloneChecker(entryObj, logger, projectConfig): void {
1844  const beforeInitFastBuildLogger = fastBuildLogger;
1845  const beforeInitCompileMode = process.env.compileMode;
1846  initEtsStandaloneCheckerConfig(logger, projectConfig);
1847  const rootFileNames: string[] = [];
1848  const resolveModulePaths: string[] = [];
1849  Object.values(entryObj).forEach((fileName: string) => {
1850    rootFileNames.push(path.resolve(fileName));
1851  });
1852  if (projectConfig.resolveModulePaths && Array.isArray(projectConfig.resolveModulePaths)) {
1853    resolveModulePaths.push(...projectConfig.resolveModulePaths);
1854  }
1855  const filterFiles: string[] = filterInput(rootFileNames);
1856  languageService = createLanguageService(filterFiles, resolveModulePaths);
1857  const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance();
1858  timePrinterInstance.setArkTSTimePrintSwitch(false);
1859  timePrinterInstance.appendTime(ts.TimePhase.START);
1860  globalProgram.builderProgram = languageService.getBuilderProgram(/*withLinterProgram*/ true);
1861  globalProgram.program = globalProgram.builderProgram.getProgram();
1862  props = languageService.getProps();
1863  timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM);
1864  collectFileToIgnoreDiagnostics(filterFiles);
1865  runArkTSLinter();
1866  const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram
1867    .getSyntacticDiagnostics()
1868    .concat(globalProgram.builderProgram.getSemanticDiagnostics());
1869  globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile);
1870
1871  allDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
1872    printDiagnostic(diagnostic);
1873  });
1874  resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode);
1875}
1876
1877export function resetEtsCheckTypeScript(): void {
1878  if (globalProgram.program) {
1879    globalProgram.program.releaseTypeChecker();
1880  } else if (languageService) {
1881    languageService.getProgram().releaseTypeChecker();
1882  }
1883  resetGlobalProgram();
1884  languageService = null;
1885}
1886
1887export function resetEtsCheck(): void {
1888  cache = {};
1889  props = new Set();
1890  needReCheckForChangedDepUsers = false;
1891  resetEtsCheckTypeScript();
1892  allResolvedModules.clear();
1893  checkerResult.count = 0;
1894  warnCheckerResult.count = 0;
1895  resolvedModulesCache.clear();
1896  dollarCollection.clear();
1897  extendCollection.clear();
1898  newExtendCollection.clear();
1899  allSourceFilePaths.clear();
1900  allModuleIds.clear();
1901  filesBuildInfo.clear();
1902  fileExistsCache.clear();
1903  dirExistsCache.clear();
1904  targetESVersionChanged = false;
1905  fileToIgnoreDiagnostics = undefined;
1906  maxMemoryInServiceChecker = 0;
1907}
1908