• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use rollupObject 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 * as ts from 'typescript';
17import fs from 'fs';
18import path from 'path';
19import MagicString from 'magic-string';
20import {
21  GEN_ABC_PLUGIN_NAME,
22  PACKAGES
23} from '../common/ark_define';
24import {
25  getNormalizedOhmUrlByFilepath,
26  getOhmUrlByByteCodeHar,
27  getOhmUrlByFilepath,
28  getOhmUrlByExternalPackage,
29  getOhmUrlBySystemApiOrLibRequest,
30  mangleDeclarationFileName,
31  compileToolIsRollUp
32} from '../../../ark_utils';
33import { writeFileSyncByNode } from '../../../process_module_files';
34import {
35  isDebug,
36  isJsonSourceFile,
37  isJsSourceFile,
38  updateSourceMap,
39  writeFileContentToTempDir
40} from '../utils';
41import { toUnixPath } from '../../../utils';
42import {
43  createAndStartEvent,
44  stopEvent
45} from '../../../ark_utils';
46import { SourceMapGenerator } from '../generate_sourcemap';
47import {
48  MergedConfig,
49  handleKeepFilesAndGetDependencies,
50  writeObfuscationNameCache,
51  handleUniversalPathInObf
52} from '../common/ob_config_resolver';
53import { ORIGIN_EXTENTION } from '../process_mock';
54import {
55  ESMODULE,
56  TRANSFORMED_MOCK_CONFIG,
57  USER_DEFINE_MOCK_CONFIG
58} from '../../../pre_define';
59import { readProjectAndLibsSource } from '../common/process_ark_config';
60import {
61  allSourceFilePaths,
62  collectAllFiles,
63  resolvedModulesCache,
64  localPackageSet
65} from '../../../ets_checker';
66import { projectConfig } from '../../../../main';
67import { performancePrinter } from 'arkguard/lib/ArkObfuscator';
68import { EventList } from 'arkguard/lib/utils/PrinterUtils';
69const ROLLUP_IMPORT_NODE: string = 'ImportDeclaration';
70const ROLLUP_EXPORTNAME_NODE: string = 'ExportNamedDeclaration';
71const ROLLUP_EXPORTALL_NODE: string = 'ExportAllDeclaration';
72const ROLLUP_DYNAMICIMPORT_NODE: string = 'ImportExpression';
73const ROLLUP_LITERAL_NODE: string = 'Literal';
74export const sourceFileBelongProject = new Map<string, string>();
75
76export class ModuleSourceFile {
77  private static sourceFiles: ModuleSourceFile[] = [];
78  private moduleId: string;
79  private source: string | ts.SourceFile;
80  private metaInfo: Object;
81  private isSourceNode: boolean = false;
82  private static projectConfig: Object;
83  private static logger: Object;
84  private static mockConfigInfo: Object = {};
85  private static mockFiles: string[] = [];
86  private static newMockConfigInfo: Object = {};
87  private static transformedHarOrHspMockConfigInfo: Object = {};
88  private static mockConfigKeyToModuleInfo: Object = {};
89  private static needProcessMock: boolean = false;
90
91  constructor(moduleId: string, source: string | ts.SourceFile, metaInfo: Object) {
92    this.moduleId = moduleId;
93    this.source = source;
94    this.metaInfo = metaInfo;
95    if (typeof this.source !== 'string') {
96      this.isSourceNode = true;
97    }
98  }
99
100  static setProcessMock(rollupObject: Object): void {
101    // only processing mock-config.json5 in preview, OhosTest, or LocalTest mode
102    if (!(rollupObject.share.projectConfig.isPreview || rollupObject.share.projectConfig.isOhosTest || rollupObject.share.projectConfig.isLocalTest)) {
103      ModuleSourceFile.needProcessMock = false;
104      return;
105    }
106
107    // mockParams is essential, and etsSourceRootPath && mockConfigPath need to be defined in mockParams
108    // mockParams = {
109    //   "decorator": "name of mock decorator",
110    //   "packageName": "name of mock package",
111    //   "etsSourceRootPath": "path of ets source root",
112    //   "mockConfigPath": "path of mock configuration file"
113    //   "mockConfigKey2ModuleInfo": "moduleInfo of mock-config key"
114    // }
115    ModuleSourceFile.needProcessMock = (rollupObject.share.projectConfig.mockParams &&
116                                        rollupObject.share.projectConfig.mockParams.etsSourceRootPath &&
117                                        rollupObject.share.projectConfig.mockParams.mockConfigPath) ? true : false;
118  }
119
120  static collectMockConfigInfo(rollupObject: Object): void {
121    if (!!rollupObject.share.projectConfig.mockParams.mockConfigKey2ModuleInfo) {
122      ModuleSourceFile.mockConfigKeyToModuleInfo = rollupObject.share.projectConfig.mockParams.mockConfigKey2ModuleInfo;
123    }
124    ModuleSourceFile.mockConfigInfo = require('json5').parse(
125      fs.readFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, 'utf-8'));
126    for (let mockedTarget in ModuleSourceFile.mockConfigInfo) {
127      if (ModuleSourceFile.mockConfigInfo[mockedTarget].source) {
128        ModuleSourceFile.mockFiles.push(ModuleSourceFile.mockConfigInfo[mockedTarget].source);
129        if (ModuleSourceFile.mockConfigKeyToModuleInfo && ModuleSourceFile.mockConfigKeyToModuleInfo[mockedTarget]) {
130          ModuleSourceFile.generateTransformedMockInfo(ModuleSourceFile.mockConfigKeyToModuleInfo[mockedTarget],
131            ModuleSourceFile.mockConfigInfo[mockedTarget].source, mockedTarget, rollupObject);
132        }
133      }
134    }
135  }
136
137  static addMockConfig(mockConfigInfo: Object, key: string, src: string): void {
138    if (Object.prototype.hasOwnProperty.call(mockConfigInfo, key)) {
139      return;
140    }
141
142    mockConfigInfo[key] = {'source': src};
143  }
144
145  static generateTransformedMockInfo(mockModuleInfo: Object, src: string, originKey: string, rollupObject: Object): void {
146    let useNormalizedOHMUrl: boolean = false;
147    if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) {
148      useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl;
149    }
150    let transformedMockTarget: string | undefined = getOhmUrlByExternalPackage(originKey, ModuleSourceFile.projectConfig,
151                                                                       ModuleSourceFile.logger, useNormalizedOHMUrl);
152    if (transformedMockTarget !== undefined) {
153      ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src);
154      return;
155    }
156    const red: string = '\u001b[31m';
157    const reset: string = '\u001b[39m';
158    if (mockModuleInfo.filePath) {
159      if (useNormalizedOHMUrl) {
160        transformedMockTarget = ModuleSourceFile.spliceNormalizedOhmurl(mockModuleInfo, mockModuleInfo.filePath, undefined);
161        ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src);
162        return;
163      }
164      transformedMockTarget = getOhmUrlByFilepath(mockModuleInfo.filePath, ModuleSourceFile.projectConfig,
165                                                  ModuleSourceFile.logger, originKey);
166      transformedMockTarget = transformedMockTarget.startsWith(PACKAGES) ? `@package:${transformedMockTarget}` :
167                              `@bundle:${transformedMockTarget}`;
168      ModuleSourceFile.addMockConfig(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transformedMockTarget, src);
169      return;
170    } else {
171      ModuleSourceFile.logger.error(red, 'ArkTS:INTERNAL ERROR: Failed to convert the key in mock-config to ohmurl, ' +
172                                    'because the file path corresponding to the key in mock-config is empty.', reset);
173    }
174  }
175
176  static generateNewMockInfo(originKey: string, transKey: string, rollupObject: Object, importerFile?: string): void {
177    if (!Object.prototype.hasOwnProperty.call(ModuleSourceFile.transformedHarOrHspMockConfigInfo, transKey) &&
178      !Object.prototype.hasOwnProperty.call(ModuleSourceFile.mockConfigInfo, originKey)) {
179      return;
180    }
181
182    let useNormalizedOHMUrl = false;
183    if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) {
184      useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl;
185    }
186    let mockFile: string = ModuleSourceFile.transformedHarOrHspMockConfigInfo[transKey] ?
187      ModuleSourceFile.transformedHarOrHspMockConfigInfo[transKey].source :
188      ModuleSourceFile.mockConfigInfo[originKey].source;
189    let mockFilePath: string = `${toUnixPath(rollupObject.share.projectConfig.modulePath)}/${mockFile}`;
190    let mockFileOhmUrl: string = '';
191    if (useNormalizedOHMUrl) {
192      // For file A that imports file B, the mock file of file B will be located in the same package of file A. So the
193      // moduleInfo for mock file should be the same with file A.
194      const targetModuleInfo: Object = rollupObject.getModuleInfo(importerFile);
195      mockFileOhmUrl = ModuleSourceFile.spliceNormalizedOhmurl(targetModuleInfo, mockFilePath, importerFile);
196    } else {
197      mockFileOhmUrl = getOhmUrlByFilepath(mockFilePath,
198                                           ModuleSourceFile.projectConfig,
199                                           ModuleSourceFile.logger,
200                                           rollupObject.share.projectConfig.entryModuleName,
201                                           importerFile);
202      mockFileOhmUrl = mockFileOhmUrl.startsWith(PACKAGES) ? `@package:${mockFileOhmUrl}` : `@bundle:${mockFileOhmUrl}`;
203    }
204
205    // record mock target mapping for incremental compilation
206    ModuleSourceFile.addMockConfig(ModuleSourceFile.newMockConfigInfo, transKey, mockFileOhmUrl);
207  }
208
209  static isMockFile(file: string, rollupObject: Object): boolean {
210    if (!ModuleSourceFile.needProcessMock) {
211      return false;
212    }
213
214    for (let mockFile of ModuleSourceFile.mockFiles) {
215      let absoluteMockFilePath: string = `${toUnixPath(rollupObject.share.projectConfig.modulePath)}/${mockFile}`;
216      if (toUnixPath(absoluteMockFilePath) === toUnixPath(file)) {
217        return true;
218      }
219    }
220
221    return false;
222  }
223
224  static generateMockConfigFile(rollupObject: Object): void {
225    let transformedMockConfigCache: string =
226      path.resolve(rollupObject.share.projectConfig.cachePath, `./${TRANSFORMED_MOCK_CONFIG}`);
227    let transformedMockConfig: string =
228      path.resolve(rollupObject.share.projectConfig.aceModuleJsonPath, `../${TRANSFORMED_MOCK_CONFIG}`);
229    let userDefinedMockConfigCache: string =
230      path.resolve(rollupObject.share.projectConfig.cachePath, `./${USER_DEFINE_MOCK_CONFIG}`);
231    // full compilation
232    if (!fs.existsSync(transformedMockConfigCache) || !fs.existsSync(userDefinedMockConfigCache)) {
233      fs.writeFileSync(transformedMockConfig, JSON.stringify(ModuleSourceFile.newMockConfigInfo));
234      fs.copyFileSync(transformedMockConfig, transformedMockConfigCache);
235      fs.copyFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, userDefinedMockConfigCache);
236      return;
237    }
238
239    // incremental compilation
240    const cachedMockConfigInfo: Object =
241      require('json5').parse(fs.readFileSync(userDefinedMockConfigCache, 'utf-8'));
242    // If mock-config.json5 is modified, incremental compilation will be disabled
243    if (JSON.stringify(ModuleSourceFile.mockConfigInfo) !== JSON.stringify(cachedMockConfigInfo)) {
244      fs.writeFileSync(transformedMockConfig, JSON.stringify(ModuleSourceFile.newMockConfigInfo));
245      fs.copyFileSync(transformedMockConfig, transformedMockConfigCache);
246      fs.copyFileSync(rollupObject.share.projectConfig.mockParams.mockConfigPath, userDefinedMockConfigCache);
247      return;
248    }
249    // During incremental compilation, only at this point is the mocked file imported.
250    // At this time, the newMockConfigInfo does not match the mockConfig in the cache,
251    // so the mockConfig in the cache needs to be updated.
252    const cachedTransformedMockConfigInfo: Object =
253      require('json5').parse(fs.readFileSync(transformedMockConfigCache, 'utf-8'));
254    if (JSON.stringify(ModuleSourceFile.newMockConfigInfo) !== JSON.stringify(cachedTransformedMockConfigInfo)) {
255      ModuleSourceFile.updataCachedTransformedMockConfigInfo(ModuleSourceFile.newMockConfigInfo, cachedTransformedMockConfigInfo,
256        transformedMockConfigCache, transformedMockConfig);
257      return;
258    }
259
260    // if mock-config.json5 is not modified, use the cached mock config mapping file
261    fs.copyFileSync(transformedMockConfigCache, transformedMockConfig);
262  }
263
264  static updataCachedTransformedMockConfigInfo(newMockConfig: Object, cachedTransMockConfigInfo: Object,
265    transMockConfigCachePath: string, transMockConfigPath: string): void {
266    for (const key in newMockConfig) {
267      if (!Object.prototype.hasOwnProperty.call(cachedTransMockConfigInfo, key)) {
268        cachedTransMockConfigInfo[key] = newMockConfig[key];
269      }
270    }
271    fs.writeFileSync(transMockConfigPath, JSON.stringify(cachedTransMockConfigInfo));
272    fs.copyFileSync(transMockConfigPath, transMockConfigCachePath);
273  }
274
275  static removePotentialMockConfigCache(rollupObject: Object): void {
276    const transformedMockConfigCache: string =
277      path.resolve(rollupObject.share.projectConfig.cachePath, `./${TRANSFORMED_MOCK_CONFIG}`);
278    const userDefinedMockConfigCache: string =
279      path.resolve(rollupObject.share.projectConfig.cachePath, `./${USER_DEFINE_MOCK_CONFIG}`);
280    if (fs.existsSync(transformedMockConfigCache)) {
281      fs.rmSync(transformedMockConfigCache);
282    }
283
284    if (fs.existsSync(userDefinedMockConfigCache)) {
285      fs.rmSync(userDefinedMockConfigCache);
286    }
287  }
288
289  static newSourceFile(moduleId: string, source: string | ts.SourceFile, metaInfo: Object): void {
290    ModuleSourceFile.sourceFiles.push(new ModuleSourceFile(moduleId, source, metaInfo));
291  }
292
293  static getSourceFiles(): ModuleSourceFile[] {
294    return ModuleSourceFile.sourceFiles;
295  }
296
297  static async processModuleSourceFiles(rollupObject: Object, parentEvent: Object): Promise<void> {
298    this.initPluginEnv(rollupObject);
299
300    // collect mockConfigInfo
301    ModuleSourceFile.setProcessMock(rollupObject);
302    if (ModuleSourceFile.needProcessMock) {
303      ModuleSourceFile.collectMockConfigInfo(rollupObject);
304    } else {
305      ModuleSourceFile.removePotentialMockConfigCache(rollupObject);
306    }
307
308    collectAllFiles(undefined, rollupObject.getModuleIds(), rollupObject);
309    performancePrinter?.iniPrinter?.startEvent('Scan source files');
310    let sourceProjectConfig: Object = ModuleSourceFile.projectConfig;
311    // obfuscation initialization, include collect file, resolve denpendency, read source
312    if (compileToolIsRollUp()) {
313      const obfuscationConfig: MergedConfig = sourceProjectConfig.obfuscationMergedObConfig;
314      handleUniversalPathInObf(obfuscationConfig, allSourceFilePaths);
315      const keepFilesAndDependencies = handleKeepFilesAndGetDependencies(resolvedModulesCache, obfuscationConfig,
316        sourceProjectConfig.projectRootPath, sourceProjectConfig.arkObfuscator, sourceProjectConfig);
317      readProjectAndLibsSource(allSourceFilePaths, obfuscationConfig, sourceProjectConfig.arkObfuscator,
318        sourceProjectConfig.compileHar, keepFilesAndDependencies);
319    }
320    performancePrinter?.iniPrinter?.endEvent('Scan source files');
321
322    performancePrinter?.filesPrinter?.startEvent(EventList.ALL_FILES_OBFUSCATION);
323    let byteCodeHar = false;
324    if (Object.prototype.hasOwnProperty.call(sourceProjectConfig, 'byteCodeHar')) {
325      byteCodeHar = sourceProjectConfig.byteCodeHar;
326    }
327    // Sort the collection by file name to ensure binary consistency.
328    ModuleSourceFile.sortSourceFilesByModuleId();
329    sourceProjectConfig.localPackageSet = localPackageSet;
330    for (const source of ModuleSourceFile.sourceFiles) {
331      sourceFileBelongProject.set(toUnixPath(source.moduleId), source.metaInfo?.belongProjectPath);
332      if (!rollupObject.share.projectConfig.compileHar || byteCodeHar) {
333        // compileHar: compile closed source har of project, which convert .ets to .d.ts and js, doesn't transform module request.
334        const eventBuildModuleSourceFile = createAndStartEvent(parentEvent, 'build module source files');
335        await source.processModuleRequest(rollupObject, eventBuildModuleSourceFile);
336        stopEvent(eventBuildModuleSourceFile);
337      }
338      const eventWriteSourceFile = createAndStartEvent(parentEvent, 'write source file');
339      await source.writeSourceFile(eventWriteSourceFile);
340      stopEvent(eventWriteSourceFile);
341    }
342
343    if (compileToolIsRollUp() && rollupObject.share.arkProjectConfig.compileMode === ESMODULE) {
344      await mangleDeclarationFileName(ModuleSourceFile.logger, rollupObject.share.arkProjectConfig, sourceFileBelongProject);
345    }
346    performancePrinter?.filesPrinter?.endEvent(EventList.ALL_FILES_OBFUSCATION);
347    performancePrinter?.timeSumPrinter?.print('Sum up time cost of processes');
348    performancePrinter?.timeSumPrinter?.summarizeEventDuration();
349
350    const eventObfuscatedCode = createAndStartEvent(parentEvent, 'write obfuscation name cache');
351    if (compileToolIsRollUp() && sourceProjectConfig.arkObfuscator && sourceProjectConfig.obfuscationOptions) {
352      writeObfuscationNameCache(sourceProjectConfig, sourceProjectConfig.entryPackageInfo, sourceProjectConfig.obfuscationOptions.obfuscationCacheDir,
353        sourceProjectConfig.obfuscationMergedObConfig.options?.printNameCache);
354    }
355    stopEvent(eventObfuscatedCode);
356
357    const eventGenerateMockConfigFile = createAndStartEvent(parentEvent, 'generate mock config file');
358    if (ModuleSourceFile.needProcessMock) {
359      ModuleSourceFile.generateMockConfigFile(rollupObject);
360    }
361    stopEvent(eventGenerateMockConfigFile);
362
363    ModuleSourceFile.sourceFiles = [];
364  }
365
366  getModuleId(): string {
367    return this.moduleId;
368  }
369
370  private async writeSourceFile(parentEvent: Object): Promise<void> {
371    if (this.isSourceNode && !isJsSourceFile(this.moduleId)) {
372      await writeFileSyncByNode(<ts.SourceFile> this.source, ModuleSourceFile.projectConfig, this.metaInfo,
373        this.moduleId, parentEvent, ModuleSourceFile.logger);
374    } else {
375      await writeFileContentToTempDir(this.moduleId, <string> this.source, ModuleSourceFile.projectConfig,
376        ModuleSourceFile.logger, parentEvent, this.metaInfo);
377    }
378  }
379
380  private getOhmUrl(rollupObject: Object, moduleRequest: string, filePath: string | undefined,
381    importerFile?: string): string | undefined {
382    let useNormalizedOHMUrl = false;
383    if (!!rollupObject.share.projectConfig.useNormalizedOHMUrl) {
384      useNormalizedOHMUrl = rollupObject.share.projectConfig.useNormalizedOHMUrl;
385    }
386    let systemOrLibOhmUrl = getOhmUrlBySystemApiOrLibRequest(moduleRequest, ModuleSourceFile.projectConfig,
387      ModuleSourceFile.logger, importerFile, useNormalizedOHMUrl);
388    if (systemOrLibOhmUrl !== undefined) {
389      if (ModuleSourceFile.needProcessMock) {
390        ModuleSourceFile.generateNewMockInfo(moduleRequest, systemOrLibOhmUrl, rollupObject, importerFile);
391      }
392      return systemOrLibOhmUrl;
393    }
394    const externalPkgOhmurl: string | undefined = getOhmUrlByExternalPackage(moduleRequest,
395      ModuleSourceFile.projectConfig, ModuleSourceFile.logger, useNormalizedOHMUrl);
396    if (externalPkgOhmurl !== undefined) {
397      if (ModuleSourceFile.needProcessMock) {
398        ModuleSourceFile.generateNewMockInfo(moduleRequest, externalPkgOhmurl, rollupObject, importerFile);
399      }
400      return externalPkgOhmurl;
401    }
402    const byteCodeHarOhmurl: string | undefined = getOhmUrlByByteCodeHar(moduleRequest, ModuleSourceFile.projectConfig,
403      ModuleSourceFile.logger);
404    if (byteCodeHarOhmurl !== undefined) {
405      if (ModuleSourceFile.needProcessMock) {
406        ModuleSourceFile.generateNewMockInfo(moduleRequest, byteCodeHarOhmurl, rollupObject, importerFile);
407      }
408      return byteCodeHarOhmurl;
409    }
410    if (filePath) {
411      const targetModuleInfo: Object = rollupObject.getModuleInfo(filePath);
412      let res: string = '';
413      if (useNormalizedOHMUrl) {
414        res = ModuleSourceFile.spliceNormalizedOhmurl(targetModuleInfo, filePath, importerFile);
415      } else {
416        const moduleName: string = targetModuleInfo.meta.moduleName;
417        const ohmUrl: string =
418          getOhmUrlByFilepath(filePath, ModuleSourceFile.projectConfig, ModuleSourceFile.logger, moduleName, importerFile);
419        res = ohmUrl.startsWith(PACKAGES) ? `@package:${ohmUrl}` : `@bundle:${ohmUrl}`;
420      }
421      if (ModuleSourceFile.needProcessMock) {
422        // processing cases of har or lib mock targets
423        ModuleSourceFile.generateNewMockInfo(moduleRequest, res, rollupObject, importerFile);
424        // processing cases of user-defined mock targets
425        let mockedTarget: string = toUnixPath(filePath).
426            replace(toUnixPath(rollupObject.share.projectConfig.modulePath), '').
427            replace(`/${rollupObject.share.projectConfig.mockParams.etsSourceRootPath}/`, '');
428        ModuleSourceFile.generateNewMockInfo(mockedTarget, res, rollupObject, importerFile);
429      }
430      return res;
431    }
432    return undefined;
433  }
434
435  private static spliceNormalizedOhmurl(moduleInfo: Object, filePath: string, importerFile?: string): string {
436    const pkgParams = {
437      pkgName: moduleInfo.meta.pkgName,
438      pkgPath: moduleInfo.meta.pkgPath,
439      isRecordName: false
440    };
441    const ohmUrl: string =
442      getNormalizedOhmUrlByFilepath(filePath, ModuleSourceFile.projectConfig, ModuleSourceFile.logger, pkgParams,
443        importerFile);
444    return `@normalized:${ohmUrl}`;
445  }
446
447  private processJsModuleRequest(rollupObject: Object): void {
448    const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId);
449    const importMap: Object = moduleInfo.importedIdMaps;
450    const REG_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^'"]+)['"]|(?:import)(?:\s*)\(['"]([^'"]+)['"]\)/g;
451    this.source = (<string> this.source).replace(REG_DEPENDENCY, (item, staticModuleRequest, dynamicModuleRequest) => {
452      const moduleRequest: string = staticModuleRequest || dynamicModuleRequest;
453      const ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId);
454      if (ohmUrl !== undefined) {
455        item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
456          return quotation + ohmUrl + quotation;
457        });
458      }
459      return item;
460    });
461    this.processJsResourceRequest();
462  }
463
464  private processJsResourceRequest(): void {
465    this.source = (this.source as string)
466      .replace(/\b__harDefaultBundleName__\b/gi, projectConfig.integratedHsp ? '' : projectConfig.bundleName)
467      .replace(/\b__harDefaultModuleName__\b/gi, projectConfig.moduleName)
468      .replace(/\b__harDefaultIntegratedHspType__\b/gi, projectConfig.integratedHsp ? 'true' : 'false')
469      .replace(/\b__harDefaultPagePath__\b/gi, path.relative(projectConfig.projectPath || '', this.moduleId).replace(/\\/g, '/').replace(/\.js$/, ''));
470  }
471
472  private async processTransformedJsModuleRequest(rollupObject: Object): Promise<void> {
473    const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId);
474    const importMap: Object = moduleInfo.importedIdMaps;
475    const code: MagicString = new MagicString(<string> this.source);
476    // The data collected by moduleNodeMap represents the node dataset of related types.
477    // The data is processed based on the AST collected during the transform stage.
478    const moduleNodeMap: Map<string, any> =
479      moduleInfo.getNodeByType(ROLLUP_IMPORT_NODE, ROLLUP_EXPORTNAME_NODE, ROLLUP_EXPORTALL_NODE,
480        ROLLUP_DYNAMICIMPORT_NODE);
481
482    let hasDynamicImport: boolean = false;
483    if (rollupObject.share.projectConfig.needCoverageInsert && moduleInfo.ast.program) {
484      // In coverage instrumentation scenario,
485      // ast from rollup because the data of ast and moduleNodeMap are inconsistent.
486      moduleInfo.ast.program.body.forEach((node) => {
487        if (!hasDynamicImport && node.type === ROLLUP_DYNAMICIMPORT_NODE) {
488          hasDynamicImport = true;
489        }
490        if ((node.type === ROLLUP_IMPORT_NODE || node.type === ROLLUP_EXPORTNAME_NODE ||
491        node.type === ROLLUP_EXPORTALL_NODE) && node.source) {
492          const ohmUrl: string | undefined =
493            this.getOhmUrl(rollupObject, node.source.value, importMap[node.source.value], this.moduleId);
494          if (ohmUrl !== undefined) {
495            code.update(node.source.start, node.source.end, `'${ohmUrl}'`);
496          }
497        }
498      });
499    } else {
500      for (let nodeSet of moduleNodeMap.values()) {
501        nodeSet.forEach(node => {
502          if (!hasDynamicImport && node.type === ROLLUP_DYNAMICIMPORT_NODE) {
503            hasDynamicImport = true;
504          }
505          if (node.source) {
506            if (node.source.type === ROLLUP_LITERAL_NODE) {
507              const ohmUrl: string | undefined =
508                this.getOhmUrl(rollupObject, node.source.value, importMap[node.source.value], this.moduleId);
509              if (ohmUrl !== undefined) {
510                code.update(node.source.start, node.source.end, `'${ohmUrl}'`);
511              }
512            }
513          }
514        });
515      }
516    }
517
518    if (hasDynamicImport) {
519      // update sourceMap
520      const relativeSourceFilePath: string = this.moduleId.startsWith(ModuleSourceFile.projectConfig.projectRootPath) ?
521        toUnixPath(this.moduleId.replace(ModuleSourceFile.projectConfig.projectRootPath + path.sep, '')) :
522        toUnixPath(this.moduleId.replace(this.metaInfo.belongProjectPath, ''));
523      const updatedMap: Object = code.generateMap({
524        source: relativeSourceFilePath,
525        file: `${path.basename(this.moduleId)}`,
526        includeContent: false,
527        hires: true
528      });
529      const sourceMapGenerator = SourceMapGenerator.getInstance();
530      const key = sourceMapGenerator.isNewSourceMaps() ? this.moduleId : relativeSourceFilePath;
531      const sourcemap = await updateSourceMap(sourceMapGenerator.getSourceMap(key), updatedMap);
532      sourceMapGenerator.fillSourceMapPackageInfo(this.moduleId, sourcemap);
533      sourceMapGenerator.updateSourceMap(key, sourcemap);
534    }
535
536    this.source = code.toString();
537  }
538
539  private processTransformedTsModuleRequest(rollupObject: Object): void {
540    const moduleInfo: Object = rollupObject.getModuleInfo(this.moduleId);
541    const importMap: Object = moduleInfo.importedIdMaps;
542    let isMockFile: boolean = ModuleSourceFile.isMockFile(this.moduleId, rollupObject);
543
544    const moduleNodeTransformer: ts.TransformerFactory<ts.SourceFile> = context => {
545      const visitor: ts.Visitor = node => {
546        node = ts.visitEachChild(node, visitor, context);
547        // staticImport node
548        if (ts.isImportDeclaration(node) || (ts.isExportDeclaration(node) && node.moduleSpecifier)) {
549          // moduleSpecifier.getText() returns string carrying on quotation marks which the importMap's key does not,
550          // so we need to remove the quotation marks from moduleRequest.
551          const moduleRequest: string = (node.moduleSpecifier! as ts.StringLiteral).text.replace(/'|"/g, '');
552          let ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId);
553          if (ohmUrl !== undefined) {
554            // the import module are added with ".origin" at the end of the ohm url in every mock file.
555            const realOhmUrl: string = isMockFile ? `${ohmUrl}${ORIGIN_EXTENTION}` : ohmUrl;
556            if (isMockFile) {
557              ModuleSourceFile.addMockConfig(ModuleSourceFile.newMockConfigInfo, realOhmUrl, ohmUrl);
558            }
559            const modifiers: readonly ts.Modifier[] = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
560            if (ts.isImportDeclaration(node)) {
561              return ts.factory.createImportDeclaration(modifiers,
562                node.importClause, ts.factory.createStringLiteral(realOhmUrl));
563            } else {
564              return ts.factory.createExportDeclaration(modifiers,
565                node.isTypeOnly, node.exportClause, ts.factory.createStringLiteral(realOhmUrl));
566            }
567          }
568        }
569        // dynamicImport node
570        if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
571          const moduleRequest: string = node.arguments[0].getText().replace(/'|"/g, '');
572          const ohmUrl: string | undefined = this.getOhmUrl(rollupObject, moduleRequest, importMap[moduleRequest], this.moduleId);
573          if (ohmUrl !== undefined) {
574            const args: ts.Expression[] = [...node.arguments];
575            args[0] = ts.factory.createStringLiteral(ohmUrl);
576            return ts.factory.createCallExpression(node.expression, node.typeArguments, args);
577          }
578        }
579        return node;
580      };
581      return node => ts.visitNode(node, visitor);
582    };
583
584    const result: ts.TransformationResult<ts.SourceFile> =
585      ts.transform(<ts.SourceFile> this.source!, [moduleNodeTransformer]);
586
587    this.source = result.transformed[0];
588  }
589
590  // Replace each module request in source file to a unique representation which is called 'ohmUrl'.
591  // This 'ohmUrl' will be the same as the record name for each file, to make sure runtime can find the corresponding
592  // record based on each module request.
593  async processModuleRequest(rollupObject: Object, parentEvent: Object): Promise<void> {
594    if (isJsonSourceFile(this.moduleId)) {
595      return;
596    }
597    if (isJsSourceFile(this.moduleId)) {
598      const eventProcessJsModuleRequest = createAndStartEvent(parentEvent, 'process Js module request');
599      this.processJsModuleRequest(rollupObject);
600      stopEvent(eventProcessJsModuleRequest);
601      return;
602    }
603
604
605    // Only when files were transformed to ts, the corresponding ModuleSourceFile were initialized with sourceFile node,
606    // if files were transformed to js, ModuleSourceFile were initialized with srouce string.
607    if (this.isSourceNode) {
608      const eventProcessTransformedTsModuleRequest = createAndStartEvent(parentEvent, 'process transformed Ts module request');
609      this.processTransformedTsModuleRequest(rollupObject);
610      stopEvent(eventProcessTransformedTsModuleRequest);
611    } else {
612      const eventProcessTransformedJsModuleRequest = createAndStartEvent(parentEvent, 'process transformed Js module request');
613      await this.processTransformedJsModuleRequest(rollupObject);
614      stopEvent(eventProcessTransformedJsModuleRequest);
615    }
616  }
617
618  private static initPluginEnv(rollupObject: Object): void {
619    this.projectConfig = Object.assign(rollupObject.share.arkProjectConfig, rollupObject.share.projectConfig);
620    this.logger = rollupObject.share.getLogger(GEN_ABC_PLUGIN_NAME);
621  }
622
623  public static sortSourceFilesByModuleId(): void {
624    ModuleSourceFile.sourceFiles.sort((a, b) => a.moduleId.localeCompare(b.moduleId));
625  }
626
627  public static cleanUpObjects(): void {
628    ModuleSourceFile.sourceFiles = [];
629    ModuleSourceFile.projectConfig = undefined;
630    ModuleSourceFile.logger = undefined;
631    ModuleSourceFile.mockConfigInfo = {};
632    ModuleSourceFile.mockFiles = [];
633    ModuleSourceFile.newMockConfigInfo = {};
634    ModuleSourceFile.transformedHarOrHspMockConfigInfo = {};
635    ModuleSourceFile.mockConfigKeyToModuleInfo = {};
636    ModuleSourceFile.needProcessMock = false;
637  }
638}
639