• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 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 ts from 'typescript';
17import path from 'path';
18import fs from 'fs';
19import { createFilter } from '@rollup/pluginutils';
20import MagicString from 'magic-string';
21import nodeEvents from 'node:events';
22
23import {
24  LogInfo,
25  componentInfo,
26  emitLogInfo,
27  getTransformLog,
28  genTemporaryPath,
29  writeFileSync,
30  getAllComponentsOrModules,
31  writeCollectionFile,
32  storedFileInfo,
33  fileInfo,
34  resourcesRawfile,
35  differenceResourcesRawfile,
36  CacheFile,
37  startTimeStatisticsLocation,
38  stopTimeStatisticsLocation,
39  CompilationTimeStatistics,
40  genLoaderOutPathOfHar,
41  harFilesRecord,
42  resetUtils,
43  getResolveModules,
44  toUnixPath
45} from '../../utils';
46import {
47  preprocessExtend,
48  preprocessNewExtend,
49  validateUISyntax,
50  propertyCollection,
51  linkCollection,
52  resetComponentCollection,
53  componentCollection,
54  resetValidateUiSyntax
55} from '../../validate_ui_syntax';
56import {
57  processUISyntax,
58  resetLog,
59  transformLog,
60  resetProcessUiSyntax
61} from '../../process_ui_syntax';
62import {
63  projectConfig,
64  abilityPagesFullPath,
65  globalProgram,
66  resetMain,
67  globalModulePaths
68} from '../../../main';
69import {
70  appComponentCollection,
71  compilerOptions as etsCheckerCompilerOptions,
72  resolveModuleNames,
73  resolveTypeReferenceDirectives,
74  resetEtsCheck,
75  collectAllFiles,
76  allModuleIds,
77  resetEtsCheckTypeScript
78} from '../../ets_checker';
79import {
80  CUSTOM_BUILDER_METHOD,
81  GLOBAL_CUSTOM_BUILDER_METHOD,
82  INNER_CUSTOM_BUILDER_METHOD,
83  resetComponentMap,
84  INNER_CUSTOM_LOCALBUILDER_METHOD
85} from '../../component_map';
86import {
87  kitTransformLog,
88  processKitImport,
89  checkHasKeepTs,
90  resetKitImportLog
91} from '../../process_kit_import';
92import { resetProcessComponentMember } from '../../process_component_member';
93import { mangleFilePath, resetObfuscation } from '../ark_compiler/common/ob_config_resolver';
94import arkoalaProgramTransform, { ArkoalaPluginOptions } from './arkoala-plugin';
95import processStructComponentV2 from '../../process_struct_componentV2';
96import { resetlogMessageCollection } from '../../log_message_collection';
97
98const filter:any = createFilter(/(?<!\.d)\.(ets|ts)$/);
99
100let shouldDisableCache: boolean = false;
101interface ShouldEnableDebugLineType {
102  enableDebugLine: boolean;
103}
104
105export const ShouldEnableDebugLine: ShouldEnableDebugLineType = {
106  enableDebugLine: false
107};
108
109let disableCacheOptions = {
110  bundleName: 'default',
111  entryModuleName: 'default',
112  runtimeOS: 'default',
113  resourceTableHash: 'default',
114  etsLoaderVersion: 'default'
115};
116
117export function etsTransform() {
118  const allFilesInHar: Map<string, string> = new Map();
119  let cacheFile: { [fileName: string]: CacheFile };
120  return {
121    name: 'etsTransform',
122    transform: transform,
123    buildStart() {
124      const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'buildStart');
125      startTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformBuildStartTime : undefined);
126      judgeCacheShouldDisabled.call(this);
127      if (process.env.compileMode === 'moduleJson') {
128        cacheFile = this.cache.get('transformCacheFiles');
129        storedFileInfo.addGlobalCacheInfo(this.cache.get('resourceListCacheInfo'),
130          this.cache.get('resourceToFileCacheInfo'), cacheFile);
131        if (this.cache.get('lastResourcesArr')) {
132          storedFileInfo.lastResourcesSet = new Set([...this.cache.get('lastResourcesArr')]);
133        }
134        if (process.env.rawFileResource) {
135          resourcesRawfile(process.env.rawFileResource, storedFileInfo.resourcesArr);
136          this.share.rawfilechanged = differenceResourcesRawfile(storedFileInfo.lastResourcesSet, storedFileInfo.resourcesArr);
137        }
138      }
139      if (!!this.cache.get('enableDebugLine') !== projectConfig.enableDebugLine) {
140        ShouldEnableDebugLine.enableDebugLine = true;
141      }
142      stopTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformBuildStartTime : undefined);
143    },
144    load(id: string) {
145      let fileCacheInfo: fileInfo;
146      const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'load');
147      startTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformLoadTime : undefined);
148      if (this.cache.get('fileCacheInfo')) {
149        fileCacheInfo = this.cache.get('fileCacheInfo')[path.resolve(id)];
150      }
151      // Exclude Component Preview page
152      if (projectConfig.isPreview && !projectConfig.checkEntry && id.match(/(?<!\.d)\.(ets)$/)) {
153        abilityPagesFullPath.add(path.resolve(id).toLowerCase());
154        storedFileInfo.judgeShouldHaveEntryFiles(abilityPagesFullPath);
155      }
156      storedFileInfo.addFileCacheInfo(path.resolve(id), fileCacheInfo);
157      storedFileInfo.setCurrentArkTsFile();
158      stopTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformLoadTime : undefined);
159    },
160    shouldInvalidCache(options) {
161      const fileName: string = path.resolve(options.id);
162      let shouldDisable: boolean = shouldDisableCache || disableNonEntryFileCache(fileName) || ShouldEnableDebugLine.enableDebugLine;
163      if (process.env.compileMode === 'moduleJson') {
164        shouldDisable = shouldDisable || storedFileInfo.shouldInvalidFiles.has(fileName) || this.share.rawfilechanged;
165        if (cacheFile && cacheFile[fileName] && cacheFile[fileName].children.length) {
166          for (let child of cacheFile[fileName].children) {
167            const newTimeMs: number = fs.existsSync(child.fileName) ? fs.statSync(child.fileName).mtimeMs : -1;
168            if (newTimeMs !== child.mtimeMs) {
169              shouldDisable = true;
170              break;
171            }
172          }
173        }
174      }
175      // If a file import an const enum object from other changed file, this file also need to be transformed.
176      shouldDisable = shouldDisable || checkRelateToConstEnum(options.id);
177      if (!shouldDisable) {
178        storedFileInfo.collectCachedFiles(fileName);
179      }
180      return shouldDisable;
181    },
182    buildEnd(): void {
183      if (process.env.watchMode !== 'true' && !projectConfig.hotReload && !projectConfig.isPreview) {
184        resetEtsCheckTypeScript();
185      }
186    },
187    afterBuildEnd() {
188      // Copy the cache files in the compileArkTS directory to the loader_out directory
189      if (projectConfig.compileHar && !projectConfig.byteCodeHar) {
190        for (let moduleInfoId of allModuleIds.keys()) {
191          const moduleInfo: Object = this.getModuleInfo(moduleInfoId);
192          if (!moduleInfo) {
193            continue;
194          }
195          if (moduleInfoId && !moduleInfoId.match(new RegExp(projectConfig.packageDir)) &&
196            !moduleInfoId.startsWith('\x00') &&
197            path.resolve(moduleInfoId).startsWith(projectConfig.moduleRootPath + path.sep)) {
198            let filePath: string = moduleInfoId;
199            const metaInfo: Object = moduleInfo.meta;
200            if (this.share.arkProjectConfig?.obfuscationMergedObConfig?.options?.enableFileNameObfuscation) {
201              filePath = mangleFilePath(filePath);
202            }
203
204            const cacheFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath,
205              process.env.cachePath, projectConfig, metaInfo);
206            const buildFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath,
207              projectConfig.buildPath, projectConfig, metaInfo, true);
208            if (filePath.match(/\.e?ts$/)) {
209              setIncrementalFileInHar(cacheFilePath, buildFilePath, allFilesInHar);
210            } else {
211              allFilesInHar.set(cacheFilePath, buildFilePath);
212            }
213          }
214        }
215
216        allFilesInHar.forEach((buildFilePath, cacheFilePath) => {
217          // if the ts or ets file code only contain interface, it doesn't have js file.
218          if (fs.existsSync(cacheFilePath)) {
219            const sourceCode: string = fs.readFileSync(cacheFilePath, 'utf-8');
220            writeFileSync(buildFilePath, sourceCode);
221          }
222        });
223      }
224      if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
225        let widgetPath: string;
226        if (projectConfig.widgetCompile) {
227          widgetPath = path.resolve(projectConfig.aceModuleBuild, 'widget');
228        }
229        writeCollectionFile(projectConfig.cachePath, appComponentCollection,
230          this.share.allComponents, 'component_collection.json', this.share.allFiles, widgetPath);
231      }
232      shouldDisableCache = false;
233      this.cache.set('disableCacheOptions', disableCacheOptions);
234      this.cache.set('lastResourcesArr', [...storedFileInfo.resourcesArr]);
235      if (projectConfig.enableDebugLine) {
236        this.cache.set('enableDebugLine', true);
237      } else {
238        this.cache.set('enableDebugLine', false);
239      }
240      storedFileInfo.clearCollectedInfo(this.cache);
241      this.cache.set('transformCacheFiles', storedFileInfo.transformCacheFiles);
242    },
243    cleanUp(): void {
244      resetMain();
245      resetComponentMap();
246      resetEtsCheck();
247      resetEtsTransform();
248      resetProcessComponentMember();
249      resetProcessUiSyntax();
250      resetUtils();
251      resetValidateUiSyntax();
252      resetObfuscation();
253      resetlogMessageCollection();
254    }
255  };
256}
257
258// If a ArkTS file don't have @Entry decorator but it is entry file this time
259function disableNonEntryFileCache(filePath: string): boolean {
260  return storedFileInfo.buildStart && filePath.match(/(?<!\.d)\.(ets)$/) &&
261    !storedFileInfo.wholeFileInfo[filePath].hasEntry &&
262    storedFileInfo.shouldHaveEntry.includes(filePath);
263}
264
265function judgeCacheShouldDisabled(): void {
266  for (const key in disableCacheOptions) {
267    if (this.cache.get('disableCacheOptions') && this.share &&
268      this.share.projectConfig && this.share.projectConfig[key] &&
269      this.cache.get('disableCacheOptions')[key] !== this.share.projectConfig[key]) {
270      if (key === 'resourceTableHash' && process.env.compileMode === 'moduleJson') {
271        storedFileInfo.resourceTableChanged = true;
272      } else if (!shouldDisableCache) {
273        shouldDisableCache = true;
274      }
275    }
276    if (this.share && this.share.projectConfig && this.share.projectConfig[key]) {
277      disableCacheOptions[key] = this.share.projectConfig[key];
278    }
279    storedFileInfo.judgeShouldHaveEntryFiles(abilityPagesFullPath);
280  }
281}
282
283interface EmitResult {
284  outputText: string,
285  sourceMapText: string,
286}
287
288function getCompilerHost(): ts.CompilerHost {
289  const compilerHost: ts.CompilerHost = ts.createCompilerHost(etsCheckerCompilerOptions);
290  compilerHost.writeFile = () => {};
291  compilerHost.resolveModuleNames = resolveModuleNames;
292  compilerHost.getCurrentDirectory = () => process.cwd();
293  compilerHost.getDefaultLibFileName = options => ts.getDefaultLibFilePath(options);
294  compilerHost.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives;
295  return compilerHost;
296}
297
298let compilerHost: ts.CompilerHost = null;
299
300if (!compilerHost) {
301  compilerHost = getCompilerHost();
302}
303
304const arkoalaTsProgramCache: WeakMap<ts.Program, ts.Program> = new WeakMap();
305
306function getArkoalaTsProgram(program: ts.Program): ts.Program {
307  let extendedProgram = arkoalaTsProgramCache.get(program);
308  if (!extendedProgram) {
309    const pluginOptions: ArkoalaPluginOptions = {};
310    // This is a stub for the interface generated by ts-patch.
311    // Probably we can use the reported diagnostics in the output
312    const pluginTransformExtras: Object = {
313      diagnostics: [],
314      addDiagnostic(): number {return 0},
315      removeDiagnostic(): void {},
316      ts: ts,
317      library: 'typescript',
318    };
319    extendedProgram = arkoalaProgramTransform(program, compilerHost, pluginOptions, pluginTransformExtras);
320    arkoalaTsProgramCache.set(program, extendedProgram);
321  }
322  return extendedProgram;
323}
324
325async function transform(code: string, id: string) {
326  const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'transform');
327  if (!filter(id)) {
328    return null;
329  }
330
331  storedFileInfo.collectTransformedFiles(path.resolve(id));
332
333  const logger = this.share.getLogger('etsTransform');
334
335  if (projectConfig.compileMode !== 'esmodule') {
336    const compilerOptions = ts.readConfigFile(
337      path.resolve(__dirname, '../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions;
338    compilerOptions.moduleResolution = 'nodenext';
339    compilerOptions.module = 'es2020';
340    const newContent: string = jsBundlePreProcess(code, id, this.getModuleInfo(id).isEntry, logger);
341    const result: ts.TranspileOutput = ts.transpileModule(newContent, {
342      compilerOptions: compilerOptions,
343      fileName: id,
344      transformers: { before: [processUISyntax(null)] }
345    });
346
347    resetCollection();
348    if (transformLog && transformLog.errors.length && !projectConfig.ignoreWarning) {
349      emitLogInfo(logger, getTransformLog(transformLog), true, id);
350      resetLog();
351    }
352
353    return {
354      code: result.outputText,
355      map: result.sourceMapText ? JSON.parse(result.sourceMapText) : new MagicString(code).generateMap()
356    };
357  }
358
359  let tsProgram: ts.Program = globalProgram.program;
360  let targetSourceFile: ts.SourceFile | undefined = tsProgram.getSourceFile(id);
361
362  // createProgram from the file which does not have corresponding ast from ets-checker's program
363  // by those following cases:
364  // 1. .ets/.ts imported by .js file with tsc's `allowJS` option is false.
365  // 2. .ets/.ts imported by .js file with same name '.d.ts' file which is prior to .js by tsc default resolving
366  if (!targetSourceFile) {
367    startTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined);
368    if (storedFileInfo.isFirstBuild && storedFileInfo.changeFiles) {
369      storedFileInfo.newTsProgram = ts.createProgram(storedFileInfo.changeFiles, etsCheckerCompilerOptions, compilerHost);
370      storedFileInfo.isFirstBuild = false;
371    }
372    stopTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined);
373    if (storedFileInfo.newTsProgram && storedFileInfo.newTsProgram.getSourceFile(id)) {
374      tsProgram = storedFileInfo.newTsProgram;
375    } else {
376      await CreateProgramMoment.block(id);
377      CreateProgramMoment.release(id);
378      if (storedFileInfo.newTsProgram && storedFileInfo.newTsProgram.getSourceFile(id)) {
379        tsProgram = storedFileInfo.newTsProgram;
380      } else {
381        startTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined);
382        tsProgram = ts.createProgram(CreateProgramMoment.getRoots(id), etsCheckerCompilerOptions, compilerHost);
383        storedFileInfo.newTsProgram = tsProgram;
384        stopTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined);
385      }
386    }
387
388    // init TypeChecker to run binding
389    globalProgram.checker = tsProgram.getTypeChecker();
390    globalProgram.strictChecker = tsProgram.getLinterTypeChecker();
391    targetSourceFile = tsProgram.getSourceFile(id)!;
392    storedFileInfo.reUseProgram = false;
393    collectAllFiles(tsProgram);
394  } else {
395    if (!storedFileInfo.reUseProgram) {
396      globalProgram.checker = globalProgram.program.getTypeChecker();
397      globalProgram.strictChecker = globalProgram.program.getLinterTypeChecker();
398    }
399    storedFileInfo.reUseProgram = true;
400  }
401  setPkgNameForFile(this.getModuleInfo(id));
402  startTimeStatisticsLocation(compilationTime ? compilationTime.validateEtsTime : undefined);
403  validateEts(code, id, this.getModuleInfo(id).isEntry, logger, targetSourceFile);
404  stopTimeStatisticsLocation(compilationTime ? compilationTime.validateEtsTime : undefined);
405  const emitResult: EmitResult = { outputText: '', sourceMapText: '' };
406  const writeFile: ts.WriteFileCallback = (fileName: string, data: string) => {
407    if (/.map$/.test(fileName)) {
408      emitResult.sourceMapText = data;
409    } else {
410      emitResult.outputText = data;
411    }
412  };
413
414  // close `noEmit` to make invoking emit() effective.
415  tsProgram.getCompilerOptions().noEmit = false;
416  const metaInfo: Object = this.getModuleInfo(id).meta;
417  // use `try finally` to restore `noEmit` when error thrown by `processUISyntax` in preview mode
418  startTimeStatisticsLocation(compilationTime ? compilationTime.shouldEmitJsTime : undefined);
419  const shouldEmitJsFlag: boolean = getShouldEmitJs(projectConfig.shouldEmitJs, targetSourceFile);
420  stopTimeStatisticsLocation(compilationTime ? compilationTime.shouldEmitJsTime : undefined);
421  let transformResult: ts.TransformationResult<ts.SourceFile> = null;
422  try {
423    startTimeStatisticsLocation(compilationTime ? compilationTime.tsProgramEmitTime : undefined);
424    if (projectConfig.useArkoala) {
425      tsProgram = getArkoalaTsProgram(tsProgram);
426      targetSourceFile = tsProgram.getSourceFile(id);
427    }
428    if (shouldEmitJsFlag) {
429      startTimeStatisticsLocation(compilationTime ? compilationTime.emitTime : undefined);
430      tsProgram.emit(targetSourceFile, writeFile, undefined, undefined,
431        {
432          before: [
433            processUISyntax(null, false, compilationTime, id),
434            processKitImport(id, metaInfo, compilationTime)
435          ]
436        }
437      );
438      stopTimeStatisticsLocation(compilationTime ? compilationTime.emitTime : undefined);
439    } else {
440      startTimeStatisticsLocation(compilationTime ? compilationTime.transformNodesTime : undefined);
441      const emitResolver: ts.EmitResolver = globalProgram.checker.getEmitResolver(outFile(tsProgram.getCompilerOptions()) ?
442        undefined : targetSourceFile, undefined);
443      transformResult = ts.transformNodes(emitResolver, tsProgram.getEmitHost?.(), ts.factory,
444        tsProgram.getCompilerOptions(), [targetSourceFile],
445        [processUISyntax(null, false, compilationTime, id),
446        processKitImport(id, metaInfo, compilationTime, false)], false);
447      stopTimeStatisticsLocation(compilationTime ? compilationTime.transformNodesTime : undefined);
448    }
449    stopTimeStatisticsLocation(compilationTime ? compilationTime.tsProgramEmitTime : undefined);
450  } finally {
451    // restore `noEmit` to prevent tsc's watchService emitting automatically.
452    tsProgram.getCompilerOptions().noEmit = true;
453  }
454
455  resetCollection();
456  processStructComponentV2.resetStructMapInEts();
457  if (((transformLog && transformLog.errors.length) || (kitTransformLog && kitTransformLog.errors.length)) &&
458    !projectConfig.ignoreWarning) {
459    emitLogInfo(logger, [...getTransformLog(kitTransformLog), ...getTransformLog(transformLog)], true, id);
460    resetLog();
461    resetKitImportLog();
462  }
463
464  return shouldEmitJsFlag ? {
465    code: emitResult.outputText,
466    // Use magicString to generate sourceMap because of Typescript do not emit sourceMap in some cases
467    map: emitResult.sourceMapText ? JSON.parse(emitResult.sourceMapText) : new MagicString(code).generateMap(),
468    meta: {
469      shouldEmitJs: true
470    }
471  } : printSourceFile(transformResult.transformed[0], compilationTime);
472}
473
474function printSourceFile(sourceFile: ts.SourceFile, compilationTime: CompilationTimeStatistics): string | null {
475  if (sourceFile) {
476    startTimeStatisticsLocation(compilationTime ? compilationTime.printNodeTime : undefined);
477    const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
478    const sourceCode: string = printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile);
479    stopTimeStatisticsLocation(compilationTime ? compilationTime.printNodeTime : undefined);
480    return sourceCode;
481  }
482  return null;
483}
484
485function outFile(options: ts.CompilerOptions): string {
486  return options.outFile || options.out;
487}
488
489function getShouldEmitJs(shouldEmitJs: boolean, targetSourceFile: ts.SourceFile): boolean {
490  let shouldEmitJsFlag: boolean = true;
491  let hasKeepTs: boolean = false;
492  if (!projectConfig.processTs) {
493    return shouldEmitJsFlag;
494  }
495  if (projectConfig.complieHar) {
496    if (!projectConfig.UseTsHar && !projectConfig.byteCodeHar) {
497      return shouldEmitJsFlag;
498    }
499  } else {
500    hasKeepTs = checkHasKeepTs(targetSourceFile);
501  }
502  // FA model/code coverage instrumentation/default situation
503  // These three situations require calling the emit interface, while in other cases 'shouldEmitJs' be false.
504  // The 'shouldEmitJS' variable is obtained through 'this.share.sprojectConfig'.
505  if (shouldEmitJs !== undefined) {
506    // ark es2abc
507    shouldEmitJsFlag = shouldEmitJs || ts.hasTsNoCheckOrTsIgnoreFlag(targetSourceFile) && !hasKeepTs;
508  }
509  return shouldEmitJsFlag;
510}
511
512function setPkgNameForFile(moduleInfo: Object): void {
513  if (moduleInfo && moduleInfo.meta && moduleInfo.meta.pkgName) {
514    storedFileInfo.getCurrentArkTsFile().pkgName = moduleInfo.meta.pkgName;
515  }
516}
517
518function validateEts(code: string, id: string, isEntry: boolean, logger: any, sourceFile: ts.SourceFile) {
519  if (/\.ets$/.test(id)) {
520    clearCollection();
521    const fileQuery: string = isEntry && !abilityPagesFullPath.has(path.resolve(id).toLowerCase()) ? '?entry' : '';
522    const log: LogInfo[] = validateUISyntax(code, code, id, fileQuery, sourceFile);
523    if (log.length && !projectConfig.ignoreWarning) {
524      emitLogInfo(logger, log, true, id);
525    }
526  }
527}
528
529function jsBundlePreProcess(code: string, id: string, isEntry: boolean, logger: any): string {
530  if (/\.ets$/.test(id)) {
531    clearCollection();
532    let content = preprocessExtend(code);
533    content = preprocessNewExtend(content);
534    const fileQuery: string = isEntry && !abilityPagesFullPath.has(path.resolve(id).toLowerCase()) ? '?entry' : '';
535    const log: LogInfo[] = validateUISyntax(code, content, id, fileQuery);
536    if (log.length && !projectConfig.ignoreWarning) {
537      emitLogInfo(logger, log, true, id);
538    }
539    return content;
540  }
541  return code;
542}
543
544function clearCollection(): void {
545  componentCollection.customComponents.clear();
546  CUSTOM_BUILDER_METHOD.clear();
547  INNER_CUSTOM_LOCALBUILDER_METHOD.clear();
548  GLOBAL_CUSTOM_BUILDER_METHOD.clear();
549  INNER_CUSTOM_BUILDER_METHOD.clear();
550  storedFileInfo.getCurrentArkTsFile().compFromDETS.clear();
551}
552
553function resetCollection() {
554  componentInfo.id = 0;
555  propertyCollection.clear();
556  linkCollection.clear();
557  resetComponentCollection();
558  storedFileInfo.hasLocalBuilderInFile = false;
559}
560
561function resetEtsTransform(): void {
562  ShouldEnableDebugLine.enableDebugLine = false;
563  projectConfig.ignoreWarning = false;
564  projectConfig.widgetCompile = false;
565  compilerHost = null;
566  disableCacheOptions = {
567    bundleName: 'default',
568    entryModuleName: 'default',
569    runtimeOS: 'default',
570    resourceTableHash: 'default',
571    etsLoaderVersion: 'default'
572  };
573}
574
575function findArkoalaRoot(): string {
576  let arkoalaSdkRoot: string;
577  if (process.env.ARKOALA_SDK_ROOT) {
578    arkoalaSdkRoot = process.env.ARKOALA_SDK_ROOT;
579    if (!isDir(arkoalaSdkRoot)) {
580      throw new Error('Arkoala SDK not found in ' + arkoalaSdkRoot);
581    }
582  } else {
583    const arkoalaPossiblePaths: string[] = globalModulePaths.map(dir => path.join(dir, '../../arkoala'));
584    arkoalaSdkRoot = arkoalaPossiblePaths.find(possibleRootDir => isDir(possibleRootDir)) ?? '';
585    if (!arkoalaSdkRoot) {
586      throw new Error('Arkoala SDK not found in ' + arkoalaPossiblePaths.join(';'));
587    }
588  }
589
590  return arkoalaSdkRoot;
591}
592
593function isDir(filePath: string): boolean {
594  try {
595    let stat: fs.Stats = fs.statSync(filePath);
596    return stat.isDirectory();
597  } catch (e) {
598    return false;
599  }
600}
601
602function setIncrementalFileInHar(cacheFilePath: string, buildFilePath: string, allFilesInHar: Map<string, string>): void {
603  if (cacheFilePath.match(/\.d.e?ts$/)) {
604    allFilesInHar.set(cacheFilePath, buildFilePath);
605    return;
606  }
607  let extName = projectConfig.useTsHar ? '.ts' : '.js';
608  allFilesInHar.set(cacheFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts'),
609    buildFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts'));
610  allFilesInHar.set(cacheFilePath.replace(/\.e?ts$/, extName), buildFilePath.replace(/\.e?ts$/, extName));
611}
612
613function checkRelateToConstEnum(id: string): boolean {
614  let tsProgram: ts.Program = globalProgram.builderProgram;
615  let targetSourceFile: ts.SourceFile | undefined = tsProgram ? tsProgram.getSourceFile(id) : undefined;
616  if (!targetSourceFile) {
617    return false;
618  }
619  if (!tsProgram.isFileUpdateInConstEnumCache) {
620    return false;
621  }
622  return tsProgram.isFileUpdateInConstEnumCache(targetSourceFile);
623}
624
625interface moduleInfoType {
626  id: string;
627};
628interface optionsType {
629  id: string;
630};
631class CreateProgramMoment {
632  static transFileCollect: Set<string> = new Set();
633  static awaitFileCollect: Set<string> = new Set();
634  static moduleParsedFileCollect: Set<string> = new Set();
635  static promise: Promise<void> = undefined;
636  static emitter = undefined;
637  static roots: Set<string> = new Set();
638
639  static init(): void {
640    if (CreateProgramMoment.promise) {
641      return;
642    }
643    CreateProgramMoment.roots.add(path.resolve(__dirname, '../../../declarations/common.d.ts'));
644    CreateProgramMoment.emitter = new nodeEvents.EventEmitter();
645    CreateProgramMoment.promise = new Promise<void>(resolve => {
646      CreateProgramMoment.emitter.on('checkPrefCreateProgramId', () => {
647        if (CreateProgramMoment.awaitFileCollect.size + CreateProgramMoment.moduleParsedFileCollect.size ===
648          CreateProgramMoment.transFileCollect.size) {
649          resolve();
650        }
651      });
652    });
653  }
654
655  static getPlugin() {
656    return {
657      name: 'createProgramPlugin',
658      load: {
659        order: 'pre',
660        handler(id: string) {
661          CreateProgramMoment.transFileCollect.add(id);
662        }
663      },
664
665      moduleParsed(moduleInfo: moduleInfoType): void {
666        CreateProgramMoment.moduleParsedFileCollect.add(moduleInfo.id);
667        CreateProgramMoment.emitter?.emit('checkPrefCreateProgramId');
668      },
669      cleanUp(): void {
670        CreateProgramMoment.reset();
671      }
672    };
673  }
674
675  static async block(id: string): Promise<void> {
676    CreateProgramMoment.init();
677    CreateProgramMoment.awaitFileCollect.add(id);
678    CreateProgramMoment.roots.add(id);
679    CreateProgramMoment.emitter.emit('checkPrefCreateProgramId');
680    return CreateProgramMoment.promise;
681  }
682
683  static release(id: string): void {
684    CreateProgramMoment.awaitFileCollect.delete(id);
685  }
686
687  static reset(): void {
688    CreateProgramMoment.transFileCollect.clear();
689    CreateProgramMoment.awaitFileCollect.clear();
690    CreateProgramMoment.moduleParsedFileCollect.clear();
691    CreateProgramMoment.promise = undefined;
692    CreateProgramMoment.emitter = undefined;
693    CreateProgramMoment.roots.clear();
694  }
695
696  static getRoots(id: string): string[] {
697    const res: string[] = [];
698    CreateProgramMoment.roots.forEach(id => res.push(id));
699    CreateProgramMoment.promise = undefined;
700    CreateProgramMoment.emitter = undefined;
701    CreateProgramMoment.roots.clear();
702    if (res.length === 0) {
703      return [id];
704    }
705    return res;
706  }
707}
708
709exports.createProgramPlugin = CreateProgramMoment.getPlugin;
710