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