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