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