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