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