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