• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021 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 path from 'path';
17import ts from 'typescript';
18import fs from 'fs';
19import os from 'os';
20import uglifyJS from 'uglify-js';
21
22import {
23  partialUpdateConfig,
24  projectConfig,
25  globalProgram
26} from '../main';
27import { createHash } from 'crypto';
28import type { Hash } from 'crypto';
29import {
30  AUXILIARY,
31  EXTNAME_ETS,
32  EXTNAME_JS,
33  MAIN,
34  FAIL,
35  TEMPORARY,
36  ESMODULE,
37  $$,
38  EXTEND_DECORATORS,
39  COMPONENT_EXTEND_DECORATOR,
40  COMPONENT_ANIMATABLE_EXTEND_DECORATOR,
41  COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU,
42  GET_SHARED,
43  COMPONENT_CONSTRUCTOR_UNDEFINED,
44  USE_SHARED_STORAGE,
45  STORAGE
46} from './pre_define';
47import {
48  ERROR_DESCRIPTION,
49  HvigorLogInfo,
50  HvigorErrorInfo
51} from './hvigor_error_code/hvigor_error_info';
52
53export enum LogType {
54  ERROR = 'ERROR',
55  WARN = 'WARN',
56  NOTE = 'NOTE'
57}
58export const TEMPORARYS: string = 'temporarys';
59export const BUILD: string = 'build';
60export const SRC_MAIN: string = 'src/main';
61
62const red: string = '\u001b[31m';
63const reset: string = '\u001b[39m';
64
65const WINDOWS: string = 'Windows_NT';
66const LINUX: string = 'Linux';
67const MAC: string = 'Darwin';
68const HARMONYOS: string = 'HarmonyOS';
69
70export interface LogInfo extends HvigorLogInfo {
71  type: LogType,
72  message: string,
73  pos?: number,
74  line?: number,
75  column?: number,
76  fileName?: string
77}
78
79export const repeatLog: Map<string, LogInfo> = new Map();
80
81export interface IFileLog {
82  sourceFile: ts.SourceFile | undefined;
83  errors: LogInfo[];
84  cleanUp(): void
85}
86
87export function buildErrorInfoFromLogInfo(info: LogInfo, fileName: string): HvigorErrorInfo {
88  const positionMessage: string = info.line && info.column ? `${fileName}:${info.line}:${info.column}` : fileName;
89  return new HvigorErrorInfo()
90    .setCode(info.code)
91    .setDescription(info.description ?? ERROR_DESCRIPTION)
92    .setCause(info.cause ?? info.message)
93    .setPosition(`File: ${positionMessage}`)
94    .setSolutions(info.solutions ?? []);
95}
96
97export function isHvigorLogInfo(logInfo: LogInfo): boolean {
98  // Hvigor logger requires code as mendatory attribute.
99  // ArkUI warnings or info are not using Hvigor logger to emit.
100  return logInfo.code !== undefined;
101}
102
103export function emitLogInfo(loader: Object, infos: LogInfo[], fastBuild: boolean = false,
104  resourcePath: string | null = null, hvigorLogger: Object | undefined = undefined): void {
105  if (infos && infos.length) {
106    infos.forEach((item) => {
107      hvigorLogger ? emitLogInfoFromHvigorLogger(hvigorLogger, item, loader, fastBuild, resourcePath)
108        : emitLogInfoFromLoader(loader, item, fastBuild, resourcePath);
109    });
110  }
111}
112
113function emitLogInfoFromHvigorLogger(hvigorLogger: Object, info: LogInfo, loader: Object,
114  fastBuild: boolean, resourcePath: string | null): void {
115  if (!isHvigorLogInfo(info)) {
116    emitLogInfoFromLoader(loader, info, fastBuild, resourcePath);
117    return;
118  }
119  const errorInfo: HvigorErrorInfo = buildErrorInfoFromLogInfo(info, info.fileName || resourcePath);
120  switch (info.type) {
121    case LogType.ERROR:
122      hvigorLogger.printError(errorInfo);
123      break;
124    case LogType.WARN:
125      hvigorLogger.printWarn(errorInfo.cause);
126      break;
127    case LogType.NOTE:
128      hvigorLogger.printInfo(errorInfo.cause);
129      break;
130  }
131}
132
133function emitLogInfoFromLoader(loader: Object, info: LogInfo, fastBuild: boolean = false,
134  resourcePath: string | null = null): void {
135  const message: string = fastBuild ? getMessage(info.fileName || resourcePath, info, true)
136    : getMessage(info.fileName || loader.resourcePath, info);
137  switch (info.type) {
138    case LogType.ERROR:
139      fastBuild ? loader.error(message) : loader.emitError(message);
140      break;
141    case LogType.WARN:
142      fastBuild ? loader.warn(message) : loader.emitWarning(message);
143      break;
144    case LogType.NOTE:
145      fastBuild ? loader.info(message) : loader.emitWarning(message);
146      break;
147  }
148}
149
150export function addLog(
151  type: LogType,
152  message: string,
153  pos: number,
154  log: LogInfo[],
155  sourceFile: ts.SourceFile,
156  hvigorLogInfo?: HvigorLogInfo
157): void {
158  const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(pos);
159  log.push({
160    type: type,
161    message: message,
162    line: posOfNode.line + 1,
163    column: posOfNode.character + 1,
164    fileName: sourceFile.fileName,
165    ...hvigorLogInfo
166  });
167}
168
169export function getMessage(fileName: string, info: LogInfo, fastBuild: boolean = false): string {
170  let message: string;
171  if (info.line && info.column) {
172    message = `BUILD${info.type} File: ${fileName}:${info.line}:${info.column}\n ${info.message}`;
173  } else {
174    message = `BUILD${info.type} File: ${fileName}\n ${info.message}`;
175  }
176  if (fastBuild) {
177    message = message.replace(/^BUILD/, 'ArkTS:');
178  }
179  return message;
180}
181
182export function getTransformLog(transformLog: IFileLog): LogInfo[] {
183  const sourceFile: ts.SourceFile = transformLog.sourceFile;
184  const logInfos: LogInfo[] = transformLog.errors.map((item) => {
185    if (item.pos || item.pos === 0) {
186      if (!item.column || !item.line) {
187        const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(item.pos);
188        item.line = posOfNode.line + 1;
189        item.column = posOfNode.character + 1;
190      }
191    } else {
192      item.line = item.line || undefined;
193      item.column = item.column || undefined;
194    }
195    if (!item.fileName) {
196      item.fileName = sourceFile.fileName;
197    }
198    return item;
199  });
200  return logInfos;
201}
202
203class ComponentInfo {
204  private _id: number = 0;
205  private _componentNames: Set<string> = new Set(['ForEach']);
206  public set id(id: number) {
207    this._id = id;
208  }
209  public get id(): number {
210    return this._id;
211  }
212  public set componentNames(componentNames: Set<string>) {
213    this._componentNames = componentNames;
214  }
215  public get componentNames(): Set<string> {
216    return this._componentNames;
217  }
218}
219
220export let componentInfo: ComponentInfo = new ComponentInfo();
221
222export function hasDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration |
223  ts.StructDeclaration | ts.ClassDeclaration, decortorName: string,
224  customBuilder: ts.Decorator[] = null, log: LogInfo[] = null): boolean {
225  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
226  if (decorators && decorators.length) {
227    const extendResult = {
228      Extend: false,
229      AnimatableExtend: false
230    };
231    for (let i = 0; i < decorators.length; i++) {
232      const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim();
233      if (log && EXTEND_DECORATORS.includes(decortorName)) {
234        if (originalDecortor === COMPONENT_EXTEND_DECORATOR) {
235          extendResult.Extend = true;
236        }
237        if (originalDecortor === COMPONENT_ANIMATABLE_EXTEND_DECORATOR) {
238          extendResult.AnimatableExtend = true;
239        }
240      } else {
241        if (originalDecortor === decortorName) {
242          if (customBuilder) {
243            customBuilder.push(...decorators.slice(i + 1), ...decorators.slice(0, i));
244          }
245          return true;
246        }
247      }
248    }
249    if (log && extendResult.Extend && extendResult.AnimatableExtend) {
250      log.push({
251        type: LogType.ERROR,
252        message: `The function can not be decorated by '@Extend' and '@AnimatableExtend' at the same time.`,
253        pos: node.getStart(),
254        code: '10905111'
255      });
256    }
257    return (decortorName === COMPONENT_EXTEND_DECORATOR && extendResult.Extend) ||
258      (decortorName === COMPONENT_ANIMATABLE_EXTEND_DECORATOR && extendResult.AnimatableExtend);
259  }
260  return false;
261}
262
263const STATEMENT_EXPECT: number = 1128;
264const SEMICOLON_EXPECT: number = 1005;
265const STATESTYLES_EXPECT: number = 1003;
266export const IGNORE_ERROR_CODE: number[] = [STATEMENT_EXPECT, SEMICOLON_EXPECT, STATESTYLES_EXPECT];
267
268export function readFile(dir: string, utFiles: string[]): void {
269  try {
270    const files: string[] = fs.readdirSync(dir);
271    files.forEach((element) => {
272      const filePath: string = path.join(dir, element);
273      const status: fs.Stats = fs.statSync(filePath);
274      if (status.isDirectory()) {
275        readFile(filePath, utFiles);
276      } else {
277        utFiles.push(filePath);
278      }
279    });
280  } catch (e) {
281    console.error(red, 'ArkTS ERROR: ' + e, reset);
282  }
283}
284
285export function circularFile(inputPath: string, outputPath: string): void {
286  if (!inputPath || !outputPath) {
287    return;
288  }
289  fs.readdir(inputPath, function (err, files) {
290    if (!files) {
291      return;
292    }
293    files.forEach(file => {
294      const inputFile: string = path.resolve(inputPath, file);
295      const outputFile: string = path.resolve(outputPath, file);
296      const fileStat: fs.Stats = fs.statSync(inputFile);
297      if (fileStat.isFile()) {
298        copyFile(inputFile, outputFile);
299      } else {
300        circularFile(inputFile, outputFile);
301      }
302    });
303  });
304}
305
306function copyFile(inputFile: string, outputFile: string): void {
307  try {
308    const parent: string = path.join(outputFile, '..');
309    if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) {
310      mkDir(parent);
311    }
312    if (fs.existsSync(outputFile)) {
313      return;
314    }
315    const readStream: fs.ReadStream = fs.createReadStream(inputFile);
316    const writeStream: fs.WriteStream = fs.createWriteStream(outputFile);
317    readStream.pipe(writeStream);
318    readStream.on('close', function () {
319      writeStream.end();
320    });
321  } catch (err) {
322    throw err.message;
323  }
324}
325
326export function mkDir(path_: string): void {
327  const parent: string = path.join(path_, '..');
328  if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) {
329    mkDir(parent);
330  }
331  fs.mkdirSync(path_);
332}
333
334export function toUnixPath(data: string): string {
335  if (/^win/.test(require('os').platform())) {
336    const fileTmps: string[] = data.split(path.sep);
337    const newData: string = path.posix.join(...fileTmps);
338    return newData;
339  }
340  return data;
341}
342
343export function tryToLowerCasePath(filePath: string): string {
344  return toUnixPath(filePath).toLowerCase();
345}
346
347export function toHashData(path: string): string {
348  const content: string = fs.readFileSync(path).toString();
349  const hash: Hash = createHash('sha256');
350  hash.update(content);
351  return hash.digest('hex');
352}
353
354export function writeFileSync(filePath: string, content: string): void {
355  if (!fs.existsSync(filePath)) {
356    const parent: string = path.join(filePath, '..');
357    if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) {
358      mkDir(parent);
359    }
360  }
361  fs.writeFileSync(filePath, content);
362}
363export function genLoaderOutPathOfHar(filePath: string, cachePath: string, buildPath: string, moduleRootPath: string, projectRootPath): string {
364  filePath = toUnixPath(filePath);
365  buildPath = toUnixPath(buildPath);
366  const cacheRootPath: string = toUnixPath(cachePath);
367  const moduleName = toUnixPath(moduleRootPath).replace(toUnixPath(projectRootPath), '');
368  const relativeFilePath: string = filePath.replace(cacheRootPath, '').replace(moduleName, '');
369  const output: string = path.join(buildPath, relativeFilePath);
370  return output;
371}
372
373export function genTemporaryPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object,
374  metaInfo: Object, buildInHar: boolean = false): string {
375  filePath = toUnixPath(filePath).replace(/\.[cm]js$/, EXTNAME_JS);
376  projectPath = toUnixPath(projectPath);
377
378  if (process.env.compileTool === 'rollup') {
379    let relativeFilePath: string = '';
380    if (metaInfo) {
381      if (metaInfo.isLocalDependency) {
382        // When buildInHar and compileHar are both True,
383        // this is the path under the PackageHar directory being spliced ​​together.
384        // Here, only the relative path based on moduleRootPath needs to be retained.
385        // eg. moduleA/index.js --> index.js --> PackageHar/index.js
386        // eg. moduleA/src/main/ets/test.js --> src/main/ets/test.js --> PackageHar/src/main/ets/test.js
387        const moduleName: string = buildInHar && projectConfig.compileHar ? '' : metaInfo.moduleName;
388        relativeFilePath = filePath.replace(toUnixPath(metaInfo.belongModulePath), moduleName);
389      } else {
390        relativeFilePath = filePath.replace(toUnixPath(metaInfo.belongProjectPath), '');
391      }
392    } else {
393      relativeFilePath = filePath.replace(toUnixPath(projectConfig.projectRootPath), '');
394    }
395    const output: string = path.join(buildPath, relativeFilePath);
396    return output;
397  }
398
399  if (isPackageModulesFile(filePath, projectConfig)) {
400    const packageDir: string = projectConfig.packageDir;
401    const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir));
402    let output: string = '';
403    if (filePath.indexOf(fakePkgModulesPath) === -1) {
404      const hapPath: string = toUnixPath(projectConfig.projectRootPath);
405      const tempFilePath: string = filePath.replace(hapPath, '');
406      const relativeFilePath: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1);
407      output = path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, MAIN, relativeFilePath);
408    } else {
409      output = filePath.replace(fakePkgModulesPath,
410        path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, AUXILIARY));
411    }
412    return output;
413  }
414
415  if (filePath.indexOf(projectPath) !== -1) {
416    const relativeFilePath: string = filePath.replace(projectPath, '');
417    const output: string = path.join(buildPath, buildInHar ? '' : TEMPORARY, relativeFilePath);
418    return output;
419  }
420
421  return '';
422}
423
424export function isPackageModulesFile(filePath: string, projectConfig: Object): boolean {
425  filePath = toUnixPath(filePath);
426  const hapPath: string = toUnixPath(projectConfig.projectRootPath);
427  const tempFilePath: string = filePath.replace(hapPath, '');
428  const packageDir: string = projectConfig.packageDir;
429  if (tempFilePath.indexOf(packageDir) !== -1) {
430    const fakePkgModulesPath: string = toUnixPath(path.resolve(projectConfig.projectRootPath, packageDir));
431    if (filePath.indexOf(fakePkgModulesPath) !== -1) {
432      return true;
433    }
434    if (projectConfig.modulePathMap) {
435      for (const key in projectConfig.modulePathMap) {
436        const value: string = projectConfig.modulePathMap[key];
437        const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir));
438        if (filePath.indexOf(fakeModulePkgModulesPath) !== -1) {
439          return true;
440        }
441      }
442    }
443  }
444
445  return false;
446}
447
448export interface GeneratedFileInHar {
449  sourcePath: string;
450  sourceCachePath?: string;
451  obfuscatedSourceCachePath?: string;
452  originalDeclarationCachePath?: string;
453  originalDeclarationContent?: string;
454  obfuscatedDeclarationCachePath?: string;
455}
456
457export const harFilesRecord: Map<string, GeneratedFileInHar> = new Map();
458
459export function generateSourceFilesInHar(sourcePath: string, sourceContent: string, suffix: string,
460  projectConfig: Object, modulePathMap?: Object): void {
461  const belongModuleInfo: Object = getBelongModuleInfo(sourcePath, modulePathMap, projectConfig.projectRootPath);
462  // compileShared: compile shared har of project
463  let jsFilePath: string = genTemporaryPath(sourcePath,
464    projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath,
465    projectConfig.compileShared || projectConfig.byteCodeHar ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath,
466    projectConfig, belongModuleInfo, projectConfig.compileShared);
467  if (!jsFilePath.match(new RegExp(projectConfig.packageDir))) {
468    jsFilePath = jsFilePath.replace(/\.ets$/, suffix).replace(/\.ts$/, suffix);
469    if (projectConfig.obfuscateHarType === 'uglify' && suffix === '.js') {
470      sourceContent = uglifyJS.minify(sourceContent).code;
471    }
472    // collect the declaration files for obfuscation
473    if (projectConfig.compileMode === ESMODULE && (/\.d\.e?ts$/).test(jsFilePath)) {
474      sourcePath = toUnixPath(sourcePath);
475      const genFilesInHar: GeneratedFileInHar = {
476        sourcePath: sourcePath,
477        originalDeclarationCachePath: jsFilePath,
478        originalDeclarationContent: sourceContent
479      };
480      harFilesRecord.set(sourcePath, genFilesInHar);
481      return;
482    } else {
483      mkdirsSync(path.dirname(jsFilePath));
484    }
485    fs.writeFileSync(jsFilePath, sourceContent);
486  }
487}
488
489export function mkdirsSync(dirname: string): boolean {
490  if (fs.existsSync(dirname)) {
491    return true;
492  } else if (mkdirsSync(path.dirname(dirname))) {
493    fs.mkdirSync(dirname);
494    return true;
495  }
496
497  return false;
498}
499
500export function nodeLargeOrEqualTargetVersion(targetVersion: number): boolean {
501  const currentNodeVersion: number = parseInt(process.versions.node.split('.')[0]);
502  if (currentNodeVersion >= targetVersion) {
503    return true;
504  }
505
506  return false;
507}
508
509export function removeDir(dirName: string): void {
510  if (fs.existsSync(dirName)) {
511    if (nodeLargeOrEqualTargetVersion(16)) {
512      fs.rmSync(dirName, { recursive: true });
513    } else {
514      fs.rmdirSync(dirName, { recursive: true });
515    }
516  }
517}
518
519export function parseErrorMessage(message: string): string {
520  const messageArrary: string[] = message.split('\n');
521  let logContent: string = '';
522  messageArrary.forEach(element => {
523    if (!(/^at/.test(element.trim()))) {
524      logContent = logContent + element + '\n';
525    }
526  });
527  return logContent;
528}
529
530export function isWindows(): boolean {
531  return os.type() === WINDOWS;
532}
533
534export function isLinux(): boolean {
535  return os.type() === LINUX;
536}
537
538export function isMac(): boolean {
539  return os.type() === MAC;
540}
541
542export function isHarmonyOs(): boolean {
543  return os.type() === HARMONYOS;
544}
545
546export function maxFilePathLength(): number {
547  if (isWindows()) {
548    return 32766;
549  } else if (isLinux() || isHarmonyOs()) {
550    return 4095;
551  } else if (isMac()) {
552    return 1016;
553  } else {
554    return -1;
555  }
556}
557
558export function validateFilePathLength(filePath: string, logger: Object): boolean {
559  if (maxFilePathLength() < 0) {
560    logger.error(red, 'Unknown OS platform', reset);
561    process.exitCode = FAIL;
562    return false;
563  } else if (filePath.length > 0 && filePath.length <= maxFilePathLength()) {
564    return true;
565  } else if (filePath.length > maxFilePathLength()) {
566    logger.error(red, `The length of ${filePath} exceeds the limitation of current platform, which is ` +
567      `${maxFilePathLength()}. Please try moving the project folder to avoid deeply nested file path and try again`,
568    reset);
569    process.exitCode = FAIL;
570    return false;
571  } else {
572    logger.error(red, 'Validate file path failed', reset);
573    process.exitCode = FAIL;
574    return false;
575  }
576}
577
578export function validateFilePathLengths(filePaths: Array<string>, logger: any): boolean {
579  filePaths.forEach((filePath) => {
580    if (!validateFilePathLength(filePath, logger)) {
581      return false;
582    }
583    return true;
584  });
585  return true;
586}
587
588export function unlinkSync(filePath: string): void {
589  if (fs.existsSync(filePath)) {
590    fs.unlinkSync(filePath);
591  }
592}
593
594export function getExtensionIfUnfullySpecifiedFilepath(filePath: string): string {
595  if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
596    return '';
597  }
598
599  let extension: string = EXTNAME_ETS;
600  if (fs.existsSync(filePath + '.ts') && fs.statSync(filePath + '.ts').isFile()) {
601    extension = '.ts';
602  } else if (fs.existsSync(filePath + '.d.ts') && fs.statSync(filePath + '.d.ts').isFile()) {
603    extension = '.d.ts';
604  } else if (fs.existsSync(filePath + '.d.ets') && fs.statSync(filePath + '.d.ets').isFile()) {
605    extension = '.d.ets';
606  } else if (fs.existsSync(filePath + '.js') && fs.statSync(filePath + '.js').isFile()) {
607    extension = '.js';
608  } else if (fs.existsSync(filePath + '.json') && fs.statSync(filePath + '.json').isFile()) {
609    extension = '.json';
610  }
611
612  return extension;
613}
614
615export function shouldWriteChangedList(watchModifiedFiles: string[],
616  watchRemovedFiles: string[]): boolean {
617  if (projectConfig.compileMode === ESMODULE && process.env.watchMode === 'true' && !projectConfig.isPreview &&
618    projectConfig.changedFileList && (watchRemovedFiles.length + watchModifiedFiles.length)) {
619    if (process.env.compileTool !== 'rollup') {
620      if (!(watchModifiedFiles.length === 1 &&
621        watchModifiedFiles[0] === projectConfig.projectPath && !watchRemovedFiles.length)) {
622        return true;
623      } else {
624        return false;
625      }
626    }
627    return true;
628  }
629  return false;
630}
631
632interface HotReloadIncrementalTime {
633  hotReloadIncrementalStartTime: string;
634  hotReloadIncrementalEndTime: string;
635}
636
637export const hotReloadIncrementalTime: HotReloadIncrementalTime = {
638  hotReloadIncrementalStartTime: '',
639  hotReloadIncrementalEndTime: ''
640};
641
642interface FilesObj {
643  modifiedFiles: string[],
644  removedFiles: string[]
645}
646
647let allModifiedFiles: Set<string> = new Set();
648
649export function getHotReloadFiles(watchModifiedFiles: string[],
650  watchRemovedFiles: string[], hotReloadSupportFiles: Set<string>): FilesObj {
651  hotReloadIncrementalTime.hotReloadIncrementalStartTime = new Date().getTime().toString();
652  watchRemovedFiles = watchRemovedFiles.map(file => path.relative(projectConfig.projectPath, file));
653  allModifiedFiles = new Set([...allModifiedFiles, ...watchModifiedFiles
654    .filter(file => fs.statSync(file).isFile() &&
655      (hotReloadSupportFiles.has(file) || !['.ets', '.ts', '.js'].includes(path.extname(file))))
656    .map(file => path.relative(projectConfig.projectPath, file))]
657    .filter(file => !watchRemovedFiles.includes(file)));
658  return {
659    modifiedFiles: [...allModifiedFiles],
660    removedFiles: [...watchRemovedFiles]
661  };
662}
663
664export function getResolveModules(projectPath: string, faMode: boolean): string[] {
665  if (faMode) {
666    return [
667      path.resolve(projectPath, '../../../../../'),
668      path.resolve(projectPath, '../../../../' + projectConfig.packageDir),
669      path.resolve(projectPath, '../../../../../' + projectConfig.packageDir),
670      path.resolve(projectPath, '../../')
671    ];
672  } else {
673    return [
674      path.resolve(projectPath, '../../../../'),
675      path.resolve(projectPath, '../../../' + projectConfig.packageDir),
676      path.resolve(projectPath, '../../../../' + projectConfig.packageDir),
677      path.resolve(projectPath, '../')
678    ];
679  }
680}
681
682export function writeUseOSFiles(useOSFiles: Set<string>): void {
683  let info: string = '';
684  if (!fs.existsSync(projectConfig.aceSoPath)) {
685    const parent: string = path.resolve(projectConfig.aceSoPath, '..');
686    if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) {
687      mkDir(parent);
688    }
689  } else {
690    info = fs.readFileSync(projectConfig.aceSoPath, 'utf-8') + '\n';
691  }
692  fs.writeFileSync(projectConfig.aceSoPath, info + Array.from(useOSFiles).join('\n'));
693}
694
695
696export function writeCollectionFile(cachePath: string, appCollection: Map<string, Set<string>>,
697  allComponentsOrModules: Map<string, Array<string>>, fileName: string, allFiles: Set<string> = null,
698  widgetPath: string = undefined): void {
699  for (let key of appCollection.keys()) {
700    if (appCollection.get(key).size === 0) {
701      allComponentsOrModules.delete(key);
702      continue;
703    }
704    if (allFiles && !allFiles.has(key)) {
705      continue;
706    }
707    const newKey: string = projectConfig.projectRootPath ? path.relative(projectConfig.projectRootPath, key) : key;
708    allComponentsOrModules.set(newKey, Array.from(appCollection.get(key)));
709  }
710  const content: string = JSON.stringify(Object.fromEntries(allComponentsOrModules), null, 2);
711  writeFileSync(path.resolve(cachePath, fileName), content);
712  if (widgetPath) {
713    writeFileSync(path.resolve(widgetPath, fileName), content);
714  }
715}
716
717export function getAllComponentsOrModules(allFiles: Set<string>,
718  cacheCollectionFileName: string): Map<string, Array<string>> {
719  const cacheCollectionFilePath: string = path.resolve(projectConfig.cachePath, cacheCollectionFileName);
720  const allComponentsOrModules: Map<string, Array<string>> = new Map();
721  if (!fs.existsSync(cacheCollectionFilePath)) {
722    return allComponentsOrModules;
723  }
724  const lastComponentsOrModules = require(cacheCollectionFilePath);
725  for (let key in lastComponentsOrModules) {
726    if (allFiles.has(key)) {
727      allComponentsOrModules.set(key, lastComponentsOrModules[key]);
728    }
729  }
730  return allComponentsOrModules;
731}
732
733export function getPossibleBuilderTypeParameter(parameters: ts.ParameterDeclaration[]): string[] {
734  const parameterNames: string[] = [];
735  if (!partialUpdateConfig.builderCheck) {
736    if (isDollarParameter(parameters)) {
737      parameters[0].type.members.forEach((member) => {
738        if (member.name && ts.isIdentifier(member.name)) {
739          parameterNames.push(member.name.escapedText.toString());
740        }
741      });
742    } else {
743      parameters.forEach((parameter) => {
744        if (parameter.name && ts.isIdentifier(parameter.name)) {
745          parameterNames.push(parameter.name.escapedText.toString());
746        }
747      });
748    }
749  }
750  return parameterNames;
751}
752
753function isDollarParameter(parameters: ts.ParameterDeclaration[]): boolean {
754  return parameters.length === 1 && parameters[0].name && ts.isIdentifier(parameters[0].name) &&
755    parameters[0].name.escapedText.toString() === $$ && parameters[0].type && ts.isTypeLiteralNode(parameters[0].type) &&
756    parameters[0].type.members && parameters[0].type.members.length > 0;
757}
758
759interface ChildrenCacheFile {
760  fileName: string,
761  mtimeMs: number,
762  hash: string
763}
764
765export interface CacheFile {
766  mtimeMs: number,
767  hash: string,
768  children: Array<ChildrenCacheFile>,
769}
770
771export interface RouterInfo {
772  name: string,
773  buildFunction: string,
774}
775
776// Global Information & Method
777export class ProcessFileInfo {
778  buildStart: boolean = true;
779  wholeFileInfo: { [id: string]: SpecialArkTSFileInfo | TSFileInfo } = {}; // Save ArkTS & TS file's infomation
780  transformedFiles: Set<string> = new Set(); // ArkTS & TS Files which should be transformed in this compilation
781  cachedFiles: string[] = []; // ArkTS & TS Files which should not be transformed in this compilation
782  shouldHaveEntry: string[] = []; // Which file should have @Entry decorator
783  resourceToFile: { [resource: string]: Set<string> } = {}; // Resource is used by which file
784  lastResourceList: Set<string> = new Set();
785  resourceList: Set<string> = new Set(); // Whole project resource
786  shouldInvalidFiles: Set<string> = new Set();
787  resourceTableChanged: boolean = false;
788  currentArkTsFile: SpecialArkTSFileInfo;
789  reUseProgram: boolean = false;
790  resourcesArr: Set<string> = new Set();
791  lastResourcesSet: Set<string> = new Set();
792  transformCacheFiles: { [fileName: string]: CacheFile } = {};
793  processBuilder: boolean = false;
794  processGlobalBuilder: boolean = false;
795  processLocalBuilder: boolean = false;
796  builderLikeCollection: Set<string> = new Set();
797  newTsProgram: ts.Program;
798  changeFiles: string[] = [];
799  isFirstBuild: boolean = true;
800  processForEach: number = 0;
801  processLazyForEach: number = 0;
802  processRepeat: boolean = false;
803  isAsPageImport: boolean = false;
804  overallObjectLinkCollection: Map<string, Set<string>> = new Map();
805  overallLinkCollection: Map<string, Set<string>> = new Map();
806  overallBuilderParamCollection: Map<string, Set<string>> = new Map();
807  lazyForEachInfo: {
808    forEachParameters: ts.ParameterDeclaration,
809    isDependItem: boolean
810  } = {
811      forEachParameters: null,
812      isDependItem: false
813    };
814  routerInfo: Map<string, Array<RouterInfo>> = new Map();
815  hasLocalBuilderInFile: boolean = false;
816
817  addGlobalCacheInfo(resourceListCacheInfo: string[],
818    resourceToFileCacheInfo: { [resource: string]: Set<string> },
819    cacheFile: { [fileName: string]: CacheFile }): void {
820    if (this.buildStart) {
821      for (const element in resourceToFileCacheInfo) {
822        this.resourceToFile[element] = new Set(resourceToFileCacheInfo[element]);
823      }
824      this.lastResourceList = new Set(resourceListCacheInfo);
825    }
826    if (this.resourceTableChanged) {
827      this.compareResourceDiff();
828    }
829    if (cacheFile) {
830      this.transformCacheFiles = cacheFile;
831    }
832  }
833
834  addFileCacheInfo(id: string, fileCacheInfo: fileInfo): void {
835    if (fileCacheInfo && process.env.compileMode === 'moduleJson') {
836      if (Array.isArray(fileCacheInfo.fileToResourceList)) {
837        fileCacheInfo.fileToResourceList = new Set(fileCacheInfo.fileToResourceList);
838      } else {
839        fileCacheInfo.fileToResourceList = new Set();
840      }
841    }
842    if (id.match(/(?<!\.d)\.(ets)$/)) {
843      this.wholeFileInfo[id] = new SpecialArkTSFileInfo(fileCacheInfo);
844    } else if (id.match(/(?<!\.d)\.(ts)$/) && process.env.compileMode === 'moduleJson') {
845      this.wholeFileInfo[id] = new TSFileInfo(fileCacheInfo);
846    }
847  }
848
849  collectTransformedFiles(id: string): void {
850    if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) {
851      this.transformedFiles.add(id);
852    }
853  }
854
855  collectCachedFiles(id: string): void {
856    if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) {
857      this.cachedFiles.push(id);
858    }
859  }
860
861  judgeShouldHaveEntryFiles(entryFileWithoutEntryDecorator: Set<string>): void {
862    this.shouldHaveEntry = Object.values(projectConfig.entryObj as string[]).filter((item) => {
863      return !entryFileWithoutEntryDecorator.has(item.toLowerCase()) && item.match(/(?<!\.d)\.(ets)$/);
864    });
865  }
866
867  saveCacheFileInfo(cache): void {
868    if (process.env.compileMode === 'moduleJson') {
869      const fileCacheInfo: { [id: string]: fileInfo | tsFileInfo } = cache.get('fileCacheInfo') || {};
870      const resourceToFileCacheInfo = cache.get('resourceToFileCacheInfo') || {};
871      for (const i in resourceToFileCacheInfo) {
872        resourceToFileCacheInfo[i] = new Set(resourceToFileCacheInfo[i]);
873      }
874      const resourceToFile: { [resource: string]: Set<string> | string[] } = Object.assign(resourceToFileCacheInfo, this.resourceToFile);
875      for (const id of this.transformedFiles) {
876        fileCacheInfo[id] = this.wholeFileInfo[id].fileInfo;
877        for (const resource of this.wholeFileInfo[id].newFileToResourceList) {
878          if (!(fileCacheInfo[id].fileToResourceList as Set<string>).has(resource)) {
879            this.resourceToFileBecomeSet(resourceToFile, resource, id);
880          }
881        }
882        for (const resource of fileCacheInfo[id].fileToResourceList) {
883          if (!this.wholeFileInfo[id].newFileToResourceList.has(resource)) {
884            (resourceToFile[resource] as Set<string>).delete(id);
885          }
886        }
887        fileCacheInfo[id].fileToResourceList = [...this.wholeFileInfo[id].newFileToResourceList];
888      }
889      for (const id of this.cachedFiles) {
890        fileCacheInfo[id].fileToResourceList = [...fileCacheInfo[id].fileToResourceList];
891      }
892      this.resourceToFile = resourceToFile as { [resource: string]: Set<string> };
893      for (const resource in resourceToFile) {
894        resourceToFile[resource] = [...resourceToFile[resource]];
895      }
896      cache.set('fileCacheInfo', fileCacheInfo);
897      cache.set('resourceListCacheInfo', [...this.resourceList]);
898      cache.set('resourceToFileCacheInfo', resourceToFile);
899    } else {
900      const cacheInfo: { [id: string]: fileInfo } = cache.get('fileCacheInfo') || {};
901      for (const id of this.transformedFiles) {
902        cacheInfo[id] = this.wholeFileInfo[id].fileInfo;
903      }
904      cache.set('fileCacheInfo', cacheInfo);
905    }
906  }
907
908  resourceToFileBecomeSet(resourceToFile: { [resource: string]: Set<string> | string[] }, resource: string, id: string): void {
909    if (!resourceToFile[resource]) {
910      resourceToFile[resource] = new Set();
911    }
912    if (resourceToFile[resource] instanceof Set) {
913      resourceToFile[resource].add(id);
914    } else if (Array.isArray(resourceToFile[resource])) {
915      resourceToFile[resource] = new Set(resourceToFile[resource]);
916      resourceToFile[resource].add(id);
917    } else {
918      return;
919    }
920  }
921
922  updateResourceList(resource: string): void {
923    this.resourceList.add(resource);
924  }
925
926  compareResourceDiff(): void {
927    // delete resource
928    for (const resource of this.lastResourceList) {
929      if (!this.resourceList.has(resource) && this.resourceToFile[resource]) {
930        this.resourceToFile[resource].forEach(file => {
931          this.shouldInvalidFiles.add(file);
932        });
933      }
934    }
935    // create resource
936    for (const resource of this.resourceList) {
937      if (!this.resourceToFile[resource]) {
938        this.resourceToFile[resource] = new Set();
939      }
940      if (!this.lastResourceList.has(resource)) {
941        this.resourceToFile[resource].forEach(file => {
942          this.shouldInvalidFiles.add(file);
943        });
944      }
945    }
946  }
947
948  collectResourceInFile(resource: string, file: string): void {
949    if (this.wholeFileInfo[file]) {
950      this.wholeFileInfo[file].newFileToResourceList.add(resource);
951    }
952  }
953
954  clearCollectedInfo(cache): void {
955    this.buildStart = false;
956    this.resourceTableChanged = false;
957    this.isAsPageImport = false;
958    this.saveCacheFileInfo(cache);
959    this.transformedFiles = new Set();
960    this.cachedFiles = [];
961    this.lastResourceList = new Set([...this.resourceList]);
962    this.shouldInvalidFiles.clear();
963    this.resourcesArr.clear();
964  }
965  setCurrentArkTsFile(): void {
966    this.currentArkTsFile = new SpecialArkTSFileInfo();
967  }
968  getCurrentArkTsFile(): SpecialArkTSFileInfo {
969    return this.currentArkTsFile;
970  }
971}
972
973export let storedFileInfo: ProcessFileInfo = new ProcessFileInfo();
974
975export interface fileInfo extends tsFileInfo {
976  hasEntry: boolean; // Has @Entry decorator or not
977}
978
979export interface tsFileInfo {
980  fileToResourceList: Set<string> | string[]; // How much Resource is used
981}
982
983// Save single TS file information
984class TSFileInfo {
985  fileInfo: tsFileInfo = {
986    fileToResourceList: new Set()
987  };
988  newFileToResourceList: Set<string> = new Set();
989  constructor(cacheInfo: fileInfo, etsFile?: boolean) {
990    if (!etsFile) {
991      this.fileInfo = cacheInfo || this.fileInfo;
992    }
993  }
994}
995
996// Save single ArkTS file information
997class SpecialArkTSFileInfo extends TSFileInfo {
998  fileInfo: fileInfo = {
999    hasEntry: false,
1000    fileToResourceList: new Set()
1001  };
1002  recycleComponents: Set<string> = new Set([]);
1003  reuseComponentsV2: Set<string> = new Set([]);
1004  compFromDETS: Set<string> = new Set();
1005  animatableExtendAttribute: Map<string, Set<string>> = new Map();
1006  pkgName: string;
1007
1008  constructor(cacheInfo?: fileInfo) {
1009    super(cacheInfo, true);
1010    this.fileInfo = cacheInfo || this.fileInfo;
1011  }
1012
1013  get hasEntry(): boolean {
1014    return this.fileInfo.hasEntry;
1015  }
1016  set hasEntry(value: boolean) {
1017    this.fileInfo.hasEntry = value;
1018  }
1019}
1020
1021export function setChecker(): void {
1022  if (globalProgram.program) {
1023    globalProgram.checker = globalProgram.program.getTypeChecker();
1024    globalProgram.strictChecker = globalProgram.program.getLinterTypeChecker();
1025  } else if (globalProgram.watchProgram) {
1026    globalProgram.checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker();
1027  }
1028}
1029export interface ExtendResult {
1030  decoratorName: string;
1031  componentName: string;
1032}
1033
1034export function resourcesRawfile(rawfilePath: string, resourcesArr: Set<string>, resourceName: string = ''): void {
1035  if (fs.existsSync(process.env.rawFileResource) && fs.statSync(rawfilePath).isDirectory()) {
1036    const files: string[] = fs.readdirSync(rawfilePath);
1037    files.forEach((file: string) => {
1038      if (fs.statSync(path.join(rawfilePath, file)).isDirectory()) {
1039        resourcesRawfile(path.join(rawfilePath, file), resourcesArr, resourceName ? resourceName + '/' + file : file);
1040      } else {
1041        if (resourceName) {
1042          resourcesArr.add(resourceName + '/' + file);
1043        } else {
1044          resourcesArr.add(file);
1045        }
1046      }
1047    });
1048  }
1049}
1050
1051export function differenceResourcesRawfile(oldRawfile: Set<string>, newRawfile: Set<string>): boolean {
1052  if (oldRawfile.size !== 0 && oldRawfile.size === newRawfile.size) {
1053    for (const singleRawfiles of oldRawfile.values()) {
1054      if (!newRawfile.has(singleRawfiles)) {
1055        return true;
1056      }
1057    }
1058    return false;
1059  } else if (oldRawfile.size === 0 && oldRawfile.size === newRawfile.size) {
1060    return false;
1061  } else {
1062    return true;
1063  }
1064}
1065
1066export function isString(text: unknown): text is string {
1067  return typeof text === 'string';
1068}
1069
1070function getRollupCacheStoreKey(projectConfig: object): string {
1071  let keyInfo: string[] = [projectConfig.compileSdkVersion, projectConfig.compatibleSdkVersion, projectConfig.runtimeOS,
1072    projectConfig.etsLoaderPath];
1073  return keyInfo.join('#');
1074}
1075
1076function getRollupCacheKey(projectConfig: object): string {
1077  let isWidget: string = projectConfig.widgetCompile ? 'widget' : 'non-widget';
1078  let ohosTestInfo: string = 'non-ohosTest';
1079  if (projectConfig.testFrameworkPar) {
1080    ohosTestInfo = JSON.stringify(projectConfig.testFrameworkPar);
1081  }
1082
1083  let keyInfo: string[] = [projectConfig.entryModuleName, projectConfig.targetName, isWidget, ohosTestInfo,
1084    projectConfig.needCoverageInsert, projectConfig.isOhosTest];
1085  return keyInfo.join('#');
1086}
1087
1088function clearRollupCacheStore(cacheStoreManager: object, currentKey: string): void {
1089  if (!cacheStoreManager) {
1090    return;
1091  }
1092
1093  for (let key of cacheStoreManager.keys()) {
1094    if (key !== currentKey) {
1095      cacheStoreManager.unmount(key);
1096    }
1097  }
1098}
1099
1100export function startTimeStatisticsLocation(startTimeEvent: CompileEvent): void {
1101  if (startTimeEvent) {
1102    startTimeEvent.start();
1103  }
1104}
1105
1106export function stopTimeStatisticsLocation(stopTimeEvent: CompileEvent): void {
1107  if (stopTimeEvent) {
1108    stopTimeEvent.stop();
1109  }
1110}
1111export let resolveModuleNamesTime: CompileEvent;
1112export class CompilationTimeStatistics {
1113  hookEventFactory: HookEventFactoryType;
1114  createProgramTime: CompileEvent;
1115  runArkTSLinterTime: CompileEvent;
1116  diagnosticTime: CompileEvent;
1117  scriptSnapshotTime: CompileEvent;
1118  processImportTime: CompileEvent;
1119  processComponentClassTime: CompileEvent;
1120  validateEtsTime: CompileEvent;
1121  tsProgramEmitTime: CompileEvent;
1122  shouldEmitJsTime: CompileEvent;
1123  transformNodesTime: CompileEvent;
1124  emitTime: CompileEvent;
1125  printNodeTime: CompileEvent;
1126  noSourceFileRebuildProgramTime: CompileEvent;
1127  etsTransformBuildStartTime: CompileEvent;
1128  etsTransformLoadTime: CompileEvent;
1129  processKitImportTime: CompileEvent;
1130  processUISyntaxTime: CompileEvent;
1131  constructor(share, pluginName: string, hookName: string) {
1132    if (share && share.getHookEventFactory) {
1133      if (pluginName === 'etsChecker' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) {
1134        this.hookEventFactory = share.getHookEventFactory(pluginName, hookName);
1135        this.createProgramTime = this.hookEventFactory.createEvent('createProgram');
1136        this.runArkTSLinterTime = this.hookEventFactory.createEvent('arkTSLinter');
1137        this.diagnosticTime = this.hookEventFactory.createEvent('diagnostic');
1138        this.scriptSnapshotTime = this.createProgramTime.createSubEvent('scriptSnapshot');
1139        resolveModuleNamesTime = this.hookEventFactory.createEvent('resolveModuleNames');
1140      } else if (pluginName === 'etsTransform' && hookName === 'transform' && share.getHookEventFactory(pluginName, hookName)) {
1141        this.hookEventFactory = share.getHookEventFactory(pluginName, hookName);
1142        this.validateEtsTime = this.hookEventFactory.createEvent('validateEts');
1143        this.tsProgramEmitTime = this.hookEventFactory.createEvent('tsProgramEmit');
1144        this.shouldEmitJsTime = this.hookEventFactory.createEvent('shouldEmitJs');
1145        this.transformNodesTime = this.tsProgramEmitTime.createSubEvent('transformNodes');
1146        this.emitTime = this.tsProgramEmitTime.createSubEvent('emit');
1147        this.printNodeTime = this.hookEventFactory.createEvent('printNode');
1148        this.noSourceFileRebuildProgramTime = this.hookEventFactory.createEvent('noSourceFileRebuildProgram');
1149        this.processKitImportTime = this.tsProgramEmitTime.createSubEvent('processKitImport');
1150        this.processUISyntaxTime = this.tsProgramEmitTime.createSubEvent('processUISyntax');
1151        this.processImportTime = this.processUISyntaxTime.createSubEvent('processImport');
1152        this.processComponentClassTime = this.processUISyntaxTime.createSubEvent('processComponentClass');
1153      } else if (pluginName === 'etsTransform' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) {
1154        this.hookEventFactory = share.getHookEventFactory(pluginName, hookName);
1155        this.etsTransformBuildStartTime = this.hookEventFactory.createEvent('etsTransformBuildStart');
1156      } else if (pluginName === 'etsTransform' && hookName === 'load' && share.getHookEventFactory(pluginName, hookName)) {
1157        this.hookEventFactory = share.getHookEventFactory(pluginName, hookName);
1158        this.etsTransformLoadTime = this.hookEventFactory.createEvent('etsTransformLoad');
1159      }
1160    }
1161  }
1162}
1163
1164interface HookEventFactoryType {
1165  createEvent(name: string): CompileEvent | undefined;
1166}
1167
1168type CompileEventState = 'created' | 'beginning' | 'running' | 'failed' | 'success' | 'warn';
1169interface CompileEvent {
1170  start(time?: number): CompileEvent;
1171  stop(state?: CompileEventState, time?: number): void;
1172  startAsyncEvent(time: number): CompileEvent;
1173  stopAsyncEvent(state?: CompileEventState, TIME?: number): void;
1174  createSubEvent(name: string): CompileEvent;
1175}
1176
1177export function resetUtils(): void {
1178  componentInfo = new ComponentInfo();
1179  harFilesRecord.clear();
1180  storedFileInfo = new ProcessFileInfo();
1181}
1182
1183export function getStoredFileInfo(): ProcessFileInfo {
1184  return storedFileInfo;
1185}
1186
1187export class EntryOptionValue {
1188  routeName: ts.Expression;
1189  storage: ts.Expression;
1190  useSharedStorage: ts.Expression;
1191}
1192
1193function sharedNode(): ts.CallExpression {
1194  return ts.factory.createCallExpression(
1195    ts.factory.createPropertyAccessExpression(
1196      ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU),
1197      ts.factory.createIdentifier(GET_SHARED)
1198    ),
1199    undefined,
1200    []
1201  );
1202}
1203
1204export function createGetShared(entryOptionValue: EntryOptionValue): ts.ConditionalExpression {
1205  return ts.factory.createConditionalExpression(
1206    entryOptionValue.useSharedStorage,
1207    ts.factory.createToken(ts.SyntaxKind.QuestionToken),
1208    sharedNode(),
1209    ts.factory.createToken(ts.SyntaxKind.ColonToken),
1210    entryOptionValue.storage || ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)
1211  );
1212}
1213
1214export function createGetSharedForVariable(entryOptionNode: ts.Expression, isShare: boolean = true): ts.ConditionalExpression {
1215  return ts.factory.createConditionalExpression(
1216    ts.factory.createPropertyAccessExpression(
1217      entryOptionNode,
1218      ts.factory.createIdentifier(USE_SHARED_STORAGE)
1219    ),
1220    ts.factory.createToken(ts.SyntaxKind.QuestionToken),
1221    sharedNode(),
1222    ts.factory.createToken(ts.SyntaxKind.ColonToken),
1223    isShare ? ts.factory.createPropertyAccessExpression(
1224      entryOptionNode, ts.factory.createIdentifier(STORAGE)) :
1225      ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)
1226  );
1227}
1228
1229export function judgeUseSharedStorageForExpresion(entryOptionNode: ts.Expression): ts.BinaryExpression {
1230  return ts.factory.createBinaryExpression(
1231    entryOptionNode,
1232    ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
1233    ts.factory.createBinaryExpression(
1234      ts.factory.createPropertyAccessExpression(
1235        entryOptionNode,
1236        ts.factory.createIdentifier(USE_SHARED_STORAGE)
1237      ),
1238      ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsToken),
1239      ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)
1240    )
1241  );
1242}
1243
1244export function getRollupCache(rollupShareObject: object, projectConfig: object, key: string): object | undefined {
1245  if (!rollupShareObject) {
1246    return undefined;
1247  }
1248
1249  // Preferentially get cache object from the rollup’s cache interface.
1250  if (rollupShareObject.cache) {
1251    // Only the cache object’s name as the cache key is required.
1252    return rollupShareObject.cache.get(key);
1253  }
1254
1255  // Try to get cache object from the rollup's cacheStoreManager interface.
1256  if (rollupShareObject.cacheStoreManager) {
1257    // The cache under cacheStoreManager is divided into two layers. The key for the first layer of cache is determined
1258    // by the SDK and runtimeOS, accessed through the `mount` interface. The key for the second layer of cache is
1259    // determined by the compilation task and the name of the cache object,
1260    // accessed through `getCache` or `setCache` interface.
1261    const cacheStoreKey: string = getRollupCacheStoreKey(projectConfig);
1262    const cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + key;
1263
1264    // Clear the cache if the SDK path or runtimeOS changed
1265    clearRollupCacheStore(rollupShareObject.cacheStoreManager, cacheStoreKey);
1266    return rollupShareObject.cacheStoreManager.mount(cacheStoreKey).getCache(cacheServiceKey);
1267  }
1268
1269  return undefined;
1270}
1271
1272export function setRollupCache(rollupShareObject: object, projectConfig: object, key: string, value: object): void {
1273  if (!rollupShareObject) {
1274    return;
1275  }
1276
1277  // Preferentially set cache object to the rollup’s cache interface.
1278  if (rollupShareObject.cache) {
1279    // Only the cache object’s name as the cache key is required.
1280    rollupShareObject.cache.set(key, value);
1281    return;
1282  }
1283
1284  // Try to set cache object to the rollup's cacheStoreManager interface.
1285  if (rollupShareObject.cacheStoreManager) {
1286    const cacheStoreKey: string = getRollupCacheStoreKey(projectConfig);
1287    const cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + key;
1288
1289    rollupShareObject.cacheStoreManager.mount(cacheStoreKey).setCache(cacheServiceKey, value);
1290  }
1291}
1292
1293export function removeDecorator(decorators: readonly ts.Decorator[], decoratorName: string): readonly ts.Decorator[] {
1294  return decorators.filter((item: ts.Node) => {
1295    if (ts.isDecorator(item) && ts.isIdentifier(item.expression) &&
1296      item.expression.escapedText.toString() === decoratorName) {
1297      return false;
1298    }
1299    return true;
1300  });
1301}
1302
1303export function isFileInProject(filePath: string, projectRootPath: string): boolean {
1304  const relativeFilePath: string = toUnixPath(path.relative(toUnixPath(projectRootPath), toUnixPath(filePath)));
1305  // When processing ohmurl, hsp's filePath is consistent with moduleRequest
1306  return fs.existsSync(filePath) && fs.statSync(filePath).isFile() && !relativeFilePath.startsWith('../');
1307}
1308
1309export function getProjectRootPath(filePath: string, projectConfig: Object, rootPathSet: Object): string {
1310  if (rootPathSet) {
1311    for (const rootPath of rootPathSet) {
1312      if (isFileInProject(filePath, rootPath)) {
1313        return rootPath;
1314      }
1315    }
1316  }
1317  return projectConfig.projectRootPath;
1318}
1319
1320export function getBelongModuleInfo(filePath: string, modulePathMap: Object, projectRootPath: string): Object {
1321  for (const moduleName of Object.keys(modulePathMap)) {
1322    if (toUnixPath(filePath).startsWith(toUnixPath(modulePathMap[moduleName]) + '/')) {
1323      return {
1324        isLocalDependency: true,
1325        moduleName: moduleName,
1326        belongModulePath: modulePathMap[moduleName]
1327      };
1328    }
1329  }
1330  return {
1331    isLocalDependency: false,
1332    moduleName: '',
1333    belongModulePath: projectRootPath
1334  };
1335}
1336
1337/**
1338 * Eliminate null in type
1339 *
1340 * @param { ts.Type } type
1341 * @returns { ts.Type | ts.Type[] }
1342 */
1343export function findNonNullType(type: ts.Type): ts.Type | ts.Type[] {
1344  if (!type.isUnion() || !(type.types && type.types.length)) {
1345    return type;
1346  }
1347  const filteredTypes: ts.Type[] = type.types.filter((t) => {
1348    return t.flags && !(t.flags & ts.TypeFlags.Nullable);
1349  });
1350  if (filteredTypes.length === 1) {
1351    return filteredTypes[0];
1352  }
1353  return filteredTypes;
1354}
1355
1356/**
1357 * Manage File type and checker
1358 */
1359export class CurrentProcessFile {
1360  private static isProcessingFileETS: boolean = false;
1361
1362  /**
1363   * Get checker according to file type
1364   *
1365   * @param { ts.Program? } program
1366   * @returns { ts.TypeChecker | undefined }
1367   */
1368  static getChecker(program?: ts.Program): ts.TypeChecker | undefined {
1369    if (CurrentProcessFile.isProcessingFileETS) {
1370      return (program ?? globalProgram.program)?.getLinterTypeChecker();
1371    }
1372    return (program ?? globalProgram.program)?.getTypeChecker();
1373  }
1374
1375  /**
1376   * Record current file type
1377   *
1378   * @param { string } id
1379   */
1380  static setIsProcessingFileETS(id: string): void {
1381    CurrentProcessFile.isProcessingFileETS = CurrentProcessFile.isETSFile(id);
1382  }
1383
1384  private static isETSFile(id: string): boolean {
1385    return !!(path.extname(id)?.endsWith('ets'));
1386  }
1387}