• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 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 path from 'path';
17import fs from 'fs';
18import {
19  EXTNAME_ETS,
20  EXTNAME_JS,
21  EXTNAME_TS,
22  EXTNAME_MJS,
23  EXTNAME_CJS,
24  SOURCEMAPS,
25  SOURCEMAPS_JSON,
26  yellow,
27  reset
28} from "./common/ark_define";
29import {
30  changeFileExtension,
31  isCommonJsPluginVirtualFile,
32  isCurrentProjectFiles,
33  isDebug,
34  shouldETSOrTSFileTransformToJS
35} from "./utils";
36import {
37  toUnixPath,
38  isPackageModulesFile,
39  getProjectRootPath
40} from "../../utils";
41import {
42  handleObfuscatedFilePath,
43  mangleFilePath,
44  enableObfuscateFileName
45} from './common/ob_config_resolver';
46import { MemoryMonitor } from '../meomry_monitor/rollup-plugin-memory-monitor';
47import { MemoryDefine } from '../meomry_monitor/memory_define';
48import {
49  ArkTSInternalErrorDescription,
50  ErrorCode
51} from './error_code';
52import {
53  CommonLogger,
54  LogData,
55  LogDataFactory
56} from './logger';
57import {
58  createAndStartEvent,
59  CompileEvent,
60  stopEvent
61} from '../../performance';
62import { BytecodeObfuscator } from './bytecode_obfuscator';
63
64let isShouldSourceMap: boolean = true;
65
66export class SourceMapGenerator {
67  private static instance: SourceMapGenerator | undefined = undefined;
68  private static rollupObject: Object | undefined;
69
70  private projectConfig: Object;
71  private sourceMapPath: string;
72  private sourceMapPathTmp: string;
73  private cacheSourceMapPath: string;
74  private sourceMapForMergePath: string;
75  private triggerAsync: Object;
76  private triggerEndSignal: Object;
77  private sourceMaps: Object = {};
78  private sourceMapKeys: Set<string> = new Set([]);
79  private isNewSourceMap: boolean = true;
80  private keyCache: Map<string, string> = new Map();
81  private logger: CommonLogger;
82  private isFirstAppend: boolean = true;
83  private isCompileSingle: boolean = false;
84  private originFileEdited: boolean = false;
85  private tempFileEdited: boolean = false;
86
87  public sourceMapKeyMappingForObf: Map<string, string> = new Map();
88
89  constructor(rollupObject: Object) {
90    this.projectConfig = Object.assign(rollupObject.share.arkProjectConfig, rollupObject.share.projectConfig);
91    this.sourceMapPath = this.getSourceMapSavePath();
92    this.sourceMapPathTmp = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON + '.tmp');
93    this.cacheSourceMapPath = path.join(this.projectConfig.cachePath, SOURCEMAPS_JSON);
94    this.sourceMapForMergePath = this.cacheSourceMapPath.replace('.json', 'Merge.json');
95    this.triggerAsync = rollupObject.async;
96    this.triggerEndSignal = rollupObject.signal;
97    this.logger = CommonLogger.getInstance(rollupObject);
98  }
99
100  static init(rollupObject: Object): void {
101    SourceMapGenerator.rollupObject = rollupObject;
102    SourceMapGenerator.instance = new SourceMapGenerator(SourceMapGenerator.rollupObject);
103
104    // adapt compatibility with hvigor
105    if (!SourceMapGenerator.instance.projectConfig.entryPackageName ||
106      !SourceMapGenerator.instance.projectConfig.entryModuleVersion) {
107      SourceMapGenerator.instance.isNewSourceMap = false;
108    }
109
110    if ((SourceMapGenerator.instance.projectConfig.hotReload &&
111      SourceMapGenerator.instance.projectConfig.watchMode !== 'true') ||
112      (SourceMapGenerator.instance.projectConfig.coldReload)) {
113      isShouldSourceMap = SourceMapGenerator.instance.projectConfig.isFirstBuild;
114    }
115
116    SourceMapGenerator.instance.isCompileSingle = SourceMapGenerator.instance.isNewSourceMap &&
117      SourceMapGenerator.instance.projectConfig.singleFileEmit && isShouldSourceMap;
118
119    if (SourceMapGenerator.instance.projectConfig.hotReload) {
120      isShouldSourceMap = false;
121    }
122  }
123
124  static getInstance(): SourceMapGenerator {
125    if (!SourceMapGenerator.instance) {
126      SourceMapGenerator.instance = new SourceMapGenerator(SourceMapGenerator.rollupObject);
127    }
128    return SourceMapGenerator.instance;
129  }
130
131  //In window plateform, if receive path join by '/', should transform '/' to '\'
132  private getAdaptedModuleId(moduleId: string): string {
133    return moduleId.replace(/\//g, path.sep);
134  }
135
136  private getPkgInfoByModuleId(moduleId: string, shouldObfuscateFileName: boolean = false): Object {
137    moduleId = this.getAdaptedModuleId(moduleId);
138
139    const moduleInfo: Object = SourceMapGenerator.rollupObject.getModuleInfo(moduleId);
140    if (!moduleInfo) {
141      const errInfo: LogData = LogDataFactory.newInstance(
142        ErrorCode.ETS2BUNDLE_INTERNAL_GET_MODULE_INFO_FAILED,
143        ArkTSInternalErrorDescription,
144        `Failed to get ModuleInfo, moduleId: ${moduleId}`
145      );
146      this.logger.printErrorAndExit(errInfo);
147    }
148    const metaInfo: Object = moduleInfo['meta'];
149    if (!metaInfo) {
150      const errInfo: LogData = LogDataFactory.newInstance(
151        ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META,
152        ArkTSInternalErrorDescription,
153        `Failed to get ModuleInfo properties 'meta', moduleId: ${moduleId}`
154      );
155      this.logger.printErrorAndExit(errInfo);
156    }
157    const pkgPath = metaInfo['pkgPath'];
158    if (!pkgPath) {
159      const errInfo: LogData = LogDataFactory.newInstance(
160        ErrorCode.ETS2BUNDLE_INTERNAL_UNABLE_TO_GET_MODULE_INFO_META_PKG_PATH,
161        ArkTSInternalErrorDescription,
162        `Failed to get ModuleInfo properties 'meta.pkgPath', moduleId: ${moduleId}`
163      );
164      this.logger.printErrorAndExit(errInfo);
165    }
166
167    const dependencyPkgInfo = metaInfo['dependencyPkgInfo'];
168    let middlePath = this.getIntermediateModuleId(moduleId, metaInfo).replace(pkgPath + path.sep, '');
169    if (shouldObfuscateFileName) {
170      middlePath = mangleFilePath(middlePath);
171    }
172    return {
173      entry: {
174        name: this.projectConfig.entryPackageName,
175        version: this.projectConfig.entryModuleVersion
176      },
177      dependency: dependencyPkgInfo ? {
178        name: dependencyPkgInfo['pkgName'],
179        version: dependencyPkgInfo['pkgVersion']
180      } : undefined,
181      modulePath: toUnixPath(middlePath)
182    };
183  }
184
185  public setNewSoureMaps(isNewSourceMap: boolean): void {
186    this.isNewSourceMap = isNewSourceMap;
187  }
188
189  public isNewSourceMaps(): boolean {
190    return this.isNewSourceMap;
191  }
192
193  //generate sourcemap key, notice: moduleId is absolute path
194  public genKey(moduleId: string, shouldObfuscateFileName: boolean = false): string {
195    moduleId = this.getAdaptedModuleId(moduleId);
196
197    let key: string = this.keyCache.get(moduleId);
198    if (key && !shouldObfuscateFileName) {
199      return key;
200    }
201    const pkgInfo = this.getPkgInfoByModuleId(moduleId, shouldObfuscateFileName);
202    if (pkgInfo.dependency) {
203      key = `${pkgInfo.entry.name}|${pkgInfo.dependency.name}|${pkgInfo.dependency.version}|${pkgInfo.modulePath}`;
204    } else {
205      key = `${pkgInfo.entry.name}|${pkgInfo.entry.name}|${pkgInfo.entry.version}|${pkgInfo.modulePath}`;
206    }
207    if (key && !shouldObfuscateFileName) {
208      this.keyCache.set(moduleId, key);
209    }
210    return key;
211  }
212
213  private getSourceMapSavePath(): string {
214    if (this.projectConfig.compileHar && this.projectConfig.sourceMapDir && !this.projectConfig.byteCodeHar) {
215      return path.join(this.projectConfig.sourceMapDir, SOURCEMAPS);
216    }
217    return isDebug(this.projectConfig) ? path.join(this.projectConfig.aceModuleBuild, SOURCEMAPS) :
218      path.join(this.projectConfig.cachePath, SOURCEMAPS);
219  }
220
221  public formatOrigin(key: string, val: object): string {
222    return `  "${key}": ${JSON.stringify(val, null, 2).replace(/\n/g, '\n  ')}`;
223  }
224
225  public formatTemp(key: string, val: object): string {
226    return `{"key": "${key}", "val": ${JSON.stringify(val)}}`;
227  }
228
229  public writeOrigin(content: string): void {
230    if (!this.originFileEdited) {
231      if (fs.existsSync(this.sourceMapPath)) {
232        fs.unlinkSync(this.sourceMapPath);
233      }
234      const sourceMapPathDir: string = path.dirname(this.sourceMapPath);
235      if (!fs.existsSync(sourceMapPathDir)) {
236        fs.mkdirSync(sourceMapPathDir, { recursive: true });
237      }
238      this.originFileEdited = true;
239    }
240    fs.appendFileSync(this.sourceMapPath, content, 'utf8');
241  }
242
243  public writeTemp(content: string): void {
244    if (!this.tempFileEdited) {
245      if (fs.existsSync(this.sourceMapPathTmp)) {
246        fs.unlinkSync(this.sourceMapPathTmp);
247      }
248      const sourceMapPathTmpDir: string = path.dirname(this.sourceMapPathTmp);
249      if (!fs.existsSync(sourceMapPathTmpDir)) {
250        fs.mkdirSync(sourceMapPathTmpDir, { recursive: true });
251      }
252      this.tempFileEdited = true;
253    }
254    fs.appendFileSync(this.sourceMapPathTmp, content, 'utf8');
255  }
256
257  public resetFileEdited(): void {
258    this.originFileEdited = false;
259    this.tempFileEdited = false;
260  }
261
262  public convertSourceMapToCache(maps: Object): string {
263    let isFirstLine: boolean = true;
264    let cacheContent: string = '';
265    Object.keys(maps).forEach(key => {
266      let contentTmp: string = this.formatTemp(key, maps[key]);
267      if (isFirstLine) {
268        isFirstLine = false;
269        cacheContent = contentTmp;
270      } else {
271        cacheContent += `\n${contentTmp}`;
272      }
273    });
274    return cacheContent;
275  }
276
277  public writeModifiedSourceMapToFile(parentEvent: CompileEvent | undefined): void {
278    const eventWriteCachedSourceMaps = createAndStartEvent(parentEvent, 'write source maps');
279    Object.keys(this.sourceMaps).forEach(key => {
280      let keyChanged: string = '';
281      if (this.sourceMapKeyMappingForObf.has(key)) {
282        keyChanged = this.sourceMapKeyMappingForObf.get(key);
283      } else {
284        keyChanged = key;
285      }
286      this.sourceMapKeys.add(keyChanged);
287
288      let contentJson: string = this.formatOrigin(keyChanged, this.sourceMaps[key]);
289      let contentTmp: string = this.formatTemp(keyChanged, this.sourceMaps[key]);
290      if (this.isFirstAppend) {
291        this.isFirstAppend = false;
292        this.writeOrigin(`{\n${contentJson}`);
293        this.writeTemp(`${contentTmp}`);
294      } else {
295        this.writeOrigin(`,\n${contentJson}`);
296        this.writeTemp(`\n${contentTmp}`);
297      }
298    });
299    this.sourceMaps = {};
300    this.sourceMapKeyMappingForObf.clear();
301    stopEvent(eventWriteCachedSourceMaps);
302  }
303
304  public checkAndWriteEnd(): void {
305    if (!this.isFirstAppend) {
306      this.writeOrigin('\n}');
307    }
308    //no collect sourcemap
309    if (!fs.existsSync(this.sourceMapPath)) {
310      this.writeOrigin('{}');
311    }
312    if (!fs.existsSync(this.sourceMapPathTmp)) {
313      this.writeTemp('');
314    }
315    this.resetFileEdited();
316    if (fs.existsSync(this.cacheSourceMapPath)) {
317      fs.unlinkSync(this.cacheSourceMapPath);
318    }
319    fs.renameSync(this.sourceMapPathTmp, this.cacheSourceMapPath);
320  }
321
322  public writeUsedAndUnmodifiedSourceMapToFile(parentEvent: CompileEvent | undefined): void {
323    const eventMergeCachedSourceMaps = createAndStartEvent(parentEvent, 'merge cached source maps');
324    let cacheSourceMapInfo: Object = this.getCacheSourceMapInfo();
325    if (!cacheSourceMapInfo.exist) {
326      this.checkAndWriteEnd();
327      stopEvent(eventMergeCachedSourceMaps);
328      return;
329    }
330
331    const cacheFileContent: string = fs.readFileSync(toUnixPath(cacheSourceMapInfo.path)).toString();
332    let lines: string[] = cacheFileContent.split(/\r?\n/);
333    if (lines.length > 0) {
334      let compileFileList: Set<string> = this.getCompileFileList();
335      for (const line of lines) {
336        if (line.trim() === '') {
337          continue;
338        }
339        let smObj: Object = JSON.parse(line.trim());
340        if (!compileFileList.has(smObj.key) || this.sourceMapKeys.has(smObj.key)) {
341          // skip unuse or uncompile in cache
342          continue;
343        }
344        if (this.isFirstAppend) {
345          this.isFirstAppend = false;
346          this.writeOrigin(`{\n${this.formatOrigin(smObj.key, smObj.val)}`);
347          this.writeTemp(`${this.formatTemp(smObj.key, smObj.val)}`);
348        } else {
349          this.writeOrigin(`,\n${this.formatOrigin(smObj.key, smObj.val)}`);
350          this.writeTemp(`\n${this.formatTemp(smObj.key, smObj.val)}`);
351        }
352      }
353    }
354    this.checkAndWriteEnd();
355    stopEvent(eventMergeCachedSourceMaps);
356  }
357
358  public buildModuleSourceMapInfoSingle(parentEvent: CompileEvent | undefined): void {
359    if (this.projectConfig.widgetCompile) {
360      return;
361    }
362    if (!this.isCompileSingle) {
363      return;
364    }
365    if (Object.keys(this.sourceMaps).length === 0) {
366      return;
367    }
368    this.writeModifiedSourceMapToFile(parentEvent);
369  }
370
371  public buildModuleSourceMapInfo(parentEvent: CompileEvent | undefined): void {
372    if (this.projectConfig.widgetCompile) {
373      return;
374    }
375
376    const eventUpdateCachedSourceMaps = createAndStartEvent(parentEvent, 'update cached source maps');
377    // If hap/hsp depends on bytecode har under debug mode, the source map of bytecode har need to be merged with
378    // source map of hap/hsp.
379    if (isDebug(this.projectConfig) && !this.projectConfig.byteCodeHar && !!this.projectConfig.byteCodeHarInfo) {
380      Object.keys(this.projectConfig.byteCodeHarInfo).forEach((packageName) => {
381        const sourceMapsPath = this.projectConfig.byteCodeHarInfo[packageName].sourceMapsPath;
382        if (!sourceMapsPath && !!this.logger && !!this.logger.warn) {
383          this.logger.warn(yellow, `ArkTS:WARN Property 'sourceMapsPath' not found in '${packageName}'.`, reset);
384        }
385        if (!!sourceMapsPath) {
386          const bytecodeHarSourceMap = JSON.parse(fs.readFileSync(toUnixPath(sourceMapsPath)).toString());
387          Object.assign(this.sourceMaps, bytecodeHarSourceMap);
388        }
389      });
390    }
391
392    if (this.isNewSourceMap) {
393      this.writeModifiedSourceMapToFile(parentEvent);
394      this.writeUsedAndUnmodifiedSourceMapToFile(parentEvent);
395      this.checkSourceMapFormat();
396      return;
397    }
398
399    const updateSourceRecordInfo = MemoryMonitor.recordStage(MemoryDefine.UPDATE_SOURCE_MAPS);
400    const cacheSourceMapObject: Object = this.updateCachedSourceMaps();
401    MemoryMonitor.stopRecordStage(updateSourceRecordInfo);
402    stopEvent(eventUpdateCachedSourceMaps);
403
404    this.triggerAsync(() => {
405      const eventWriteFile = createAndStartEvent(parentEvent, 'write source map (async)', true);
406      fs.writeFile(this.sourceMapPath, JSON.stringify(cacheSourceMapObject, null, 2), 'utf-8', (err) => {
407        if (err) {
408          const errInfo: LogData = LogDataFactory.newInstance(
409            ErrorCode.ETS2BUNDLE_INTERNAL_WRITE_SOURCE_MAP_FAILED,
410            ArkTSInternalErrorDescription,
411            `Failed to write sourceMaps. ${err.message}`,
412            this.sourceMapPath
413          );
414          this.logger.printErrorAndExit(errInfo);
415        }
416        fs.copyFileSync(this.sourceMapPath, this.cacheSourceMapPath);
417        stopEvent(eventWriteFile, true);
418        this.triggerEndSignal();
419      });
420    });
421  }
422
423  public isEmptyFile(filePath: string): boolean {
424    const stats = fs.statSync(filePath);
425    return stats.size === 0;
426  }
427
428  public getCacheSourceMapInfo(): Object {
429    let existCacheSourceMap: boolean = false;
430    let cacheSourceMapPath: string = '';
431    let cacheSourceMapPathTmp: string = '';
432    /**
433     * bytecode obfuscation requires that the input sourceMap must be unobfuscated,
434     * the sourceMap will be saved in the cache directory before the first bytecode obfuscation,
435     * and it will as the input for merging sourceMap during incremental compilation.
436     */
437    if (BytecodeObfuscator.enable) {
438      cacheSourceMapPathTmp = BytecodeObfuscator.getInstance().getBackupSourceMapPath();
439      if (fs.existsSync(cacheSourceMapPathTmp) && !this.isEmptyFile(cacheSourceMapPathTmp)) {
440        existCacheSourceMap = true;
441        cacheSourceMapPath = cacheSourceMapPathTmp;
442      }
443    }
444
445    cacheSourceMapPathTmp = this.cacheSourceMapPath;
446    if (!existCacheSourceMap && fs.existsSync(cacheSourceMapPathTmp) && !this.isEmptyFile(cacheSourceMapPathTmp)) {
447      existCacheSourceMap = true;
448      cacheSourceMapPath = cacheSourceMapPathTmp;
449    }
450
451    return { exist: existCacheSourceMap, path: cacheSourceMapPath };
452  }
453
454  //update cache sourcemap object
455  public updateCachedSourceMaps(): Object {
456    if (!this.isNewSourceMap) {
457      this.modifySourceMapKeyToCachePath(this.sourceMaps);
458    }
459
460    let cacheSourceMapObject: Object = null;
461    let cacheSourceMapInfo: Object = this.getCacheSourceMapInfo();
462    if (!cacheSourceMapInfo.exist) {
463      cacheSourceMapObject = this.sourceMaps;
464    } else {
465      cacheSourceMapObject = JSON.parse(fs.readFileSync(cacheSourceMapInfo.path).toString().trim());
466      // remove unused source files's sourceMap
467      let unusedFiles = [];
468      let compileFileList: Set<string> = this.getCompileFileList();
469
470      Object.keys(cacheSourceMapObject).forEach(key => {
471        let newkeyOrOldCachePath = key;
472        if (!this.isNewSourceMap) {
473          newkeyOrOldCachePath = toUnixPath(path.join(this.projectConfig.projectRootPath, key));
474        }
475        if (!compileFileList.has(newkeyOrOldCachePath)) {
476          unusedFiles.push(key);
477        }
478      });
479      unusedFiles.forEach(file => {
480        delete cacheSourceMapObject[file];
481      })
482
483      // update sourceMap
484      Object.keys(this.sourceMaps).forEach(key => {
485        cacheSourceMapObject[key] = this.sourceMaps[key];
486      });
487    }
488    // update the key for filename obfuscation
489    for (let [key, newKey] of this.sourceMapKeyMappingForObf) {
490      this.updateSourceMapKeyWithObf(cacheSourceMapObject, key, newKey);
491    }
492    return cacheSourceMapObject;
493  }
494
495  public getCompileFileList(): Set<string> {
496    let compileFileList: Set<string> = new Set();
497    for (let moduleId of SourceMapGenerator.rollupObject.getModuleIds()) {
498      // exclude .dts|.d.ets file
499      if (isCommonJsPluginVirtualFile(moduleId) || !isCurrentProjectFiles(moduleId, this.projectConfig)) {
500        continue;
501      }
502
503      if (this.isNewSourceMap) {
504        const isPackageModules = isPackageModulesFile(moduleId, this.projectConfig);
505        if (enableObfuscateFileName(isPackageModules, this.projectConfig)) {
506          compileFileList.add(this.genKey(moduleId, true));
507        } else {
508          compileFileList.add(this.genKey(moduleId));
509        }
510        continue;
511      }
512
513      // adapt compatibilty with hvigor
514      const projectRootPath = getProjectRootPath(moduleId, this.projectConfig, this.projectConfig?.rootPathSet);
515      let cacheModuleId = this.getIntermediateModuleId(toUnixPath(moduleId))
516        .replace(toUnixPath(projectRootPath), toUnixPath(this.projectConfig.cachePath));
517
518      const isPackageModules = isPackageModulesFile(moduleId, this.projectConfig);
519      if (enableObfuscateFileName(isPackageModules, this.projectConfig)) {
520        compileFileList.add(mangleFilePath(cacheModuleId));
521      } else {
522        compileFileList.add(cacheModuleId);
523      }
524    }
525    return compileFileList;
526  }
527
528  public getSourceMaps(): Object {
529    return this.sourceMaps;
530  }
531
532  public getSourceMap(moduleId: string): Object {
533    return this.getSpecifySourceMap(this.sourceMaps, moduleId);
534  }
535
536  //get specify sourcemap, allow receive param sourcemap
537  public getSpecifySourceMap(specifySourceMap: Object, moduleId: string): Object {
538    const key = this.isNewSourceMap ? this.genKey(moduleId) : moduleId;
539    if (specifySourceMap && specifySourceMap[key]) {
540      return specifySourceMap[key];
541    }
542    return undefined;
543  }
544
545  public updateSourceMap(moduleId: string, map: Object) {
546    if (!this.sourceMaps) {
547      this.sourceMaps = {};
548    }
549    this.updateSpecifySourceMap(this.sourceMaps, moduleId, map);
550  }
551
552  //update specify sourcemap, allow receive param sourcemap
553  public updateSpecifySourceMap(specifySourceMap: Object, moduleId: string, sourceMap: Object) {
554    const key = this.isNewSourceMap ? this.genKey(moduleId) : moduleId;
555    specifySourceMap[key] = sourceMap;
556  }
557
558  public fillSourceMapPackageInfo(moduleId: string, sourcemap: Object) {
559    if (!this.isNewSourceMap) {
560      return;
561    }
562
563    const pkgInfo = this.getPkgInfoByModuleId(moduleId);
564    sourcemap['entry-package-info'] = `${pkgInfo.entry.name}|${pkgInfo.entry.version}`;
565    if (pkgInfo.dependency) {
566      sourcemap['package-info'] = `${pkgInfo.dependency.name}|${pkgInfo.dependency.version}`;
567    }
568  }
569
570  private getIntermediateModuleId(moduleId: string, metaInfo?: Object): string {
571    let extName: string = "";
572    switch (path.extname(moduleId)) {
573      case EXTNAME_ETS: {
574        extName = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : EXTNAME_TS;
575        break;
576      }
577      case EXTNAME_TS: {
578        extName = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig, metaInfo) ? EXTNAME_JS : '';
579        break;
580      }
581      case EXTNAME_JS:
582      case EXTNAME_MJS:
583      case EXTNAME_CJS: {
584        extName = (moduleId.endsWith(EXTNAME_MJS) || moduleId.endsWith(EXTNAME_CJS)) ? EXTNAME_JS : '';
585        break;
586      }
587      default:
588        break;
589    }
590    if (extName.length !== 0) {
591      return changeFileExtension(moduleId, extName);
592    }
593    return moduleId;
594  }
595
596  public setSourceMapPath(path: string): void {
597    this.sourceMapPath = path;
598  }
599
600  public modifySourceMapKeyToCachePath(sourceMap: object): void {
601    const projectConfig: object = this.projectConfig;
602
603    // modify source map keys to keep IDE tools right
604    const relativeCachePath: string = toUnixPath(projectConfig.cachePath.replace(
605      projectConfig.projectRootPath + path.sep, ''));
606    Object.keys(sourceMap).forEach(key => {
607      let newKey: string = relativeCachePath + '/' + key;
608      if (!newKey.endsWith(EXTNAME_JS)) {
609        const moduleId: string = this.projectConfig.projectRootPath + path.sep + key;
610        const extName: string = shouldETSOrTSFileTransformToJS(moduleId, this.projectConfig) ? EXTNAME_JS : EXTNAME_TS;
611        newKey = changeFileExtension(newKey, extName);
612      }
613      const isOhModules = key.startsWith('oh_modules');
614      newKey = handleObfuscatedFilePath(newKey, isOhModules, this.projectConfig);
615      sourceMap[newKey] = sourceMap[key];
616      delete sourceMap[key];
617    });
618  }
619
620  public static cleanSourceMapObject(): void {
621    if (this.instance) {
622      this.instance.resetFileEdited();
623      this.instance.keyCache.clear();
624      this.instance.sourceMaps = undefined;
625      this.instance = undefined;
626    }
627    if (this.rollupObject) {
628      this.rollupObject = undefined;
629    }
630  }
631
632  private updateSourceMapKeyWithObf(specifySourceMap: Object, key: string, newKey: string): void {
633    if (!specifySourceMap.hasOwnProperty(key) || key === newKey) {
634      return;
635    }
636    specifySourceMap[newKey] = specifySourceMap[key];
637    delete specifySourceMap[key];
638  }
639
640  public saveKeyMappingForObfFileName(originalFilePath: string): void {
641    this.sourceMapKeyMappingForObf.set(this.genKey(originalFilePath), this.genKey(originalFilePath, true));
642  }
643
644  //use by UT
645  static initInstance(rollupObject: Object): SourceMapGenerator {
646    if (!SourceMapGenerator.instance) {
647      SourceMapGenerator.init(rollupObject);
648    }
649    return SourceMapGenerator.getInstance();
650  }
651
652  public checkSourceMapFormat(): void {
653    if (!fs.existsSync(this.sourceMapPath)) {
654      const errInfo: LogData = LogDataFactory.newInstance(
655        ErrorCode.ETS2BUNDLE_INTERNAL_CHECK_SOURCEMAP_FORMAT_FAILED,
656        ArkTSInternalErrorDescription,
657        `SourceMap file not exist, path: ${this.sourceMapPath}`
658      );
659      this.logger.printErrorAndExit(errInfo);
660      return;
661    }
662
663    try {
664      const content: string = fs.readFileSync(toUnixPath(this.sourceMapPath)).toString();
665      JSON.parse(content);
666    } catch (e) {
667      const errInfo: LogData = LogDataFactory.newInstance(
668        ErrorCode.ETS2BUNDLE_INTERNAL_CHECK_SOURCEMAP_FORMAT_FAILED,
669        ArkTSInternalErrorDescription,
670        `SourceMap content format error, path: ${this.sourceMapPath}`
671      );
672      this.logger.printErrorAndExit(errInfo);
673    }
674  }
675}