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