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 this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import path from 'path';
17import fs from 'fs';
18import type sourceMap from 'source-map';
19
20import { minify, MinifyOutput } from 'terser';
21import {
22  deleteLineInfoForNameString,
23  MemoryUtils,
24  nameCacheMap,
25  unobfuscationNamesObj
26} from 'arkguard';
27import {
28  OH_MODULES,
29  SEPARATOR_AT,
30  SEPARATOR_BITWISE_AND,
31  SEPARATOR_SLASH
32} from './fast_build/ark_compiler/common/ark_define';
33import {
34  ARKTS_MODULE_NAME,
35  PACKAGES,
36  TEMPORARY,
37  ZERO,
38  ONE,
39  EXTNAME_JS,
40  EXTNAME_TS,
41  EXTNAME_MJS,
42  EXTNAME_CJS,
43  EXTNAME_ABC,
44  EXTNAME_ETS,
45  EXTNAME_TS_MAP,
46  EXTNAME_JS_MAP,
47  ESMODULE,
48  FAIL,
49  TS2ABC,
50  ES2ABC,
51  EXTNAME_PROTO_BIN,
52  NATIVE_MODULE
53} from './pre_define';
54import {
55  isMac,
56  isWindows,
57  isPackageModulesFile,
58  genTemporaryPath,
59  getExtensionIfUnfullySpecifiedFilepath,
60  mkdirsSync,
61  toUnixPath,
62  validateFilePathLength,
63  harFilesRecord,
64  getProjectRootPath
65} from './utils';
66import type { GeneratedFileInHar } from './utils';
67import {
68  extendSdkConfigs,
69  projectConfig,
70  sdkConfigPrefix
71} from '../main';
72import { getRelativeSourcePath, mangleFilePath } from './fast_build/ark_compiler/common/ob_config_resolver';
73import { moduleRequestCallback } from './fast_build/system_api/api_check_utils';
74import { performancePrinter } from 'arkguard/lib/ArkObfuscator';
75import { SourceMapGenerator } from './fast_build/ark_compiler/generate_sourcemap';
76import { sourceFileBelongProject } from './fast_build/ark_compiler/module/module_source_file';
77
78const red: string = '\u001b[31m';
79const reset: string = '\u001b[39m';
80const IDENTIFIER_CACHE: string = 'IdentifierCache';
81
82export const SRC_MAIN: string = 'src/main';
83
84export let newSourceMaps: Object = {};
85
86export const packageCollection: Map<string, Array<string>> = new Map();
87// Splicing ohmurl or record name based on filePath and context information table.
88export function getNormalizedOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object,
89  pkgParams: Object, importerFile: string): string {
90  const { pkgName, pkgPath, isRecordName } = pkgParams;
91  // rollup uses commonjs plugin to handle commonjs files,
92  // the commonjs files are prefixed with '\x00' and need to be removed.
93  if (filePath.startsWith('\x00')) {
94    filePath = filePath.replace('\x00', '');
95  }
96  let unixFilePath: string = toUnixPath(filePath);
97  unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension
98  let projectFilePath: string = unixFilePath.replace(toUnixPath(pkgPath), '');
99  // case1: /entry/src/main/ets/xxx/yyy
100  // case2: /entry/src/ohosTest/ets/xxx/yyy
101  // case3: /node_modules/xxx/yyy
102  // case4: /entry/node_modules/xxx/yyy
103  // case5: /library/node_modules/xxx/yyy
104  // case6: /library/index.ts
105  // ---> @normalized:N&<moduleName>&<bunldName>&<packageName>/entry/ets/xxx/yyy&<version>
106  let pkgInfo = projectConfig.pkgContextInfo[pkgName];
107  if (pkgInfo === undefined) {
108    logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' +
109      `Error Message: Failed to get a resolved OhmUrl for "${filePath}" imported by "${importerFile}".\n` +
110      `Solutions: > Check whether the module which ${filePath} belongs to is correctly configured.` +
111      '> Check the corresponding file name is correct(including case-sensitivity).', reset);
112  }
113  let recordName = `${pkgInfo.bundleName}&${pkgName}${projectFilePath}&${pkgInfo.version}`;
114  if (isRecordName) {
115    // record name style: <bunldName>&<packageName>/entry/ets/xxx/yyy&<version>
116    return recordName;
117  }
118  return `${pkgInfo.isSO ? 'Y' : 'N'}&${pkgInfo.moduleName}&${recordName}`;
119}
120
121export function getOhmUrlByFilepath(filePath: string, projectConfig: Object, logger: Object, namespace?: string,
122  importerFile?: string): string {
123  // remove '\x00' from the rollup virtual commonjs file's filePath
124  if (filePath.startsWith('\x00')) {
125    filePath = filePath.replace('\x00', '');
126  }
127  let unixFilePath: string = toUnixPath(filePath);
128  unixFilePath = unixFilePath.substring(0, filePath.lastIndexOf('.')); // remove extension
129  const REG_PROJECT_SRC: RegExp = /(\S+)\/src\/(?:main|ohosTest)\/(ets|js|mock)\/(\S+)/;
130
131  const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
132  const bundleName: string = packageInfo[0];
133  const moduleName: string = packageInfo[1];
134  const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[moduleName]);
135  const projectRootPath: string = toUnixPath(getProjectRootPath(filePath, projectConfig, projectConfig?.rootPathSet));
136  // case1: /entry/src/main/ets/xxx/yyy     ---> @bundle:<bundleName>/entry/ets/xxx/yyy
137  // case2: /entry/src/ohosTest/ets/xxx/yyy ---> @bundle:<bundleName>/entry_test@entry/ets/xxx/yyy
138  // case3: /node_modules/xxx/yyy           ---> @package:pkg_modules/xxx/yyy
139  // case4: /entry/node_modules/xxx/yyy     ---> @package:pkg_modules@entry/xxx/yyy
140  // case5: /library/node_modules/xxx/yyy   ---> @package:pkg_modules@library/xxx/yyy
141  // case6: /library/index.ts               ---> @bundle:<bundleName>/library/index
142  const projectFilePath: string = unixFilePath.replace(projectRootPath, '');
143  const packageDir: string = projectConfig.packageDir;
144  const result: RegExpMatchArray | null = projectFilePath.match(REG_PROJECT_SRC);
145  if (result && result[1].indexOf(packageDir) === -1) {
146    const relativePath = processSrcMain(result, projectFilePath);
147    if (namespace && moduleName !== namespace) {
148      return `${bundleName}/${moduleName}@${namespace}/${relativePath}`;
149    }
150    return `${bundleName}/${moduleName}/${relativePath}`;
151  }
152
153  const processParams: Object = {
154    projectFilePath,
155    unixFilePath,
156    packageDir,
157    projectRootPath,
158    moduleRootPath,
159    projectConfig,
160    namespace,
161    logger,
162    importerFile,
163    originalFilePath: filePath
164  };
165  return processPackageDir(processParams);
166}
167
168function processSrcMain(result: RegExpMatchArray | null, projectFilePath: string): string {
169  let langType: string = result[2];
170  let relativePath: string = result[3];
171  // case7: /entry/src/main/ets/xxx/src/main/js/yyy ---> @bundle:<bundleName>/entry/ets/xxx/src/main/js/yyy
172  const REG_SRC_MAIN: RegExp = /src\/(?:main|ohosTest)\/(ets|js)\//;
173  const srcMainIndex: number = result[1].search(REG_SRC_MAIN);
174  if (srcMainIndex !== -1) {
175    relativePath = projectFilePath.substring(srcMainIndex).replace(REG_SRC_MAIN, '');
176    langType = projectFilePath.replace(relativePath, '').match(REG_SRC_MAIN)[1];
177  }
178  return `${langType}/${relativePath}`;
179}
180
181function processPackageDir(params: Object): string {
182  const { projectFilePath, unixFilePath, packageDir, projectRootPath, moduleRootPath,
183    projectConfig, namespace, logger, importerFile, originalFilePath } = params;
184  if (projectFilePath.indexOf(packageDir) !== -1) {
185    if (compileToolIsRollUp()) {
186      const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir));
187      if (unixFilePath.indexOf(tryProjectPkg) !== -1) {
188        return unixFilePath.replace(tryProjectPkg, `${packageDir}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
189      }
190
191      // iterate the modulePathMap to find the module which contains the pkg_module's file
192      for (const moduleName in projectConfig.modulePathMap) {
193        const modulePath: string = projectConfig.modulePathMap[moduleName];
194        const tryModulePkg: string = toUnixPath(path.resolve(modulePath, packageDir));
195        if (unixFilePath.indexOf(tryModulePkg) !== -1) {
196          return unixFilePath.replace(tryModulePkg, `${packageDir}@${moduleName}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
197        }
198      }
199
200      logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' +
201        `Error Message: Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".\n` +
202        `Solutions: > Check whether the module which ${originalFilePath} belongs to is correctly configured.` +
203        '> Check the corresponding file name is correct(including case-sensitivity).', reset);
204      return originalFilePath;
205    }
206
207    // webpack with old implematation
208    const tryProjectPkg: string = toUnixPath(path.join(projectRootPath, packageDir));
209    if (unixFilePath.indexOf(tryProjectPkg) !== -1) {
210      return unixFilePath.replace(tryProjectPkg, `${packageDir}/${ONE}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
211    }
212
213    const tryModulePkg: string = toUnixPath(path.join(moduleRootPath, packageDir));
214    if (unixFilePath.indexOf(tryModulePkg) !== -1) {
215      return unixFilePath.replace(tryModulePkg, `${packageDir}/${ZERO}`).replace(new RegExp(packageDir, 'g'), PACKAGES);
216    }
217  }
218
219  const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
220  const bundleName: string = packageInfo[0];
221  const moduleName: string = packageInfo[1];
222  for (const key in projectConfig.modulePathMap) {
223    const moduleRootPath: string = toUnixPath(projectConfig.modulePathMap[key]);
224    if (unixFilePath.indexOf(moduleRootPath + '/') !== -1) {
225      const relativeModulePath: string = unixFilePath.replace(moduleRootPath + '/', '');
226      if (namespace && moduleName !== namespace) {
227        return `${bundleName}/${moduleName}@${namespace}/${relativeModulePath}`;
228      }
229      return `${bundleName}/${moduleName}/${relativeModulePath}`;
230    }
231  }
232
233  logger.error(red, 'ArkTS:ERROR Failed to resolve OhmUrl.\n' +
234    `Error Message: Failed to get a resolved OhmUrl for "${originalFilePath}" imported by "${importerFile}".\n` +
235    `Solutions: > Check whether the module which ${originalFilePath} belongs to is correctly configured.` +
236    '> Check the corresponding file name is correct(including case-sensitivity).', reset);
237  return originalFilePath;
238}
239
240
241export function getOhmUrlBySystemApiOrLibRequest(moduleRequest: string, config?: Object, logger?: Object,
242  importerFile?: string, useNormalizedOHMUrl: boolean = false): string {
243  // 'arkui-x' represents cross platform related APIs, processed as 'ohos'
244  const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`);
245  const REG_LIB_SO: RegExp = /lib(\S+)\.so/;
246
247  if (REG_SYSTEM_MODULE.test(moduleRequest.trim())) {
248    return moduleRequest.replace(REG_SYSTEM_MODULE, (_, moduleType, systemKey) => {
249      let moduleRequestStr = '';
250      if (extendSdkConfigs) {
251        moduleRequestStr = moduleRequestCallback(moduleRequest, _, moduleType, systemKey);
252      }
253      if (moduleRequestStr !== '') {
254        return moduleRequestStr;
255      }
256      const systemModule: string = `${moduleType}.${systemKey}`;
257      if (NATIVE_MODULE.has(systemModule)) {
258        return `@native:${systemModule}`;
259      } else if (moduleType === ARKTS_MODULE_NAME) {
260        // @arkts.xxx -> @ohos:arkts.xxx
261        return `@ohos:${systemModule}`;
262      } else {
263        return `@ohos:${systemKey}`;
264      };
265    });
266  }
267  if (REG_LIB_SO.test(moduleRequest.trim())) {
268    if (useNormalizedOHMUrl) {
269      const pkgInfo = config.pkgContextInfo[moduleRequest];
270      if (pkgInfo === undefined) {
271        logger?.error(red, `ArkTS:INTERNAL ERROR: Can not get pkgContextInfo of package '${moduleRequest}' ` +
272          `which being imported by '${importerFile}'`, reset);
273      }
274      const isSo = pkgInfo.isSO ? 'Y' : 'N';
275      return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${moduleRequest}&${pkgInfo.version}`;
276    }
277    return moduleRequest.replace(REG_LIB_SO, (_, libsoKey) => {
278      return `@app:${projectConfig.bundleName}/${projectConfig.moduleName}/${libsoKey}`;
279    });
280  }
281  return undefined;
282}
283
284export function genSourceMapFileName(temporaryFile: string): string {
285  let abcFile: string = temporaryFile;
286  if (temporaryFile.endsWith(EXTNAME_TS)) {
287    abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_TS_MAP);
288  } else {
289    abcFile = temporaryFile.replace(/\.js$/, EXTNAME_JS_MAP);
290  }
291  return abcFile;
292}
293
294export function getBuildModeInLowerCase(projectConfig: Object): string {
295  return (compileToolIsRollUp() ? projectConfig.buildMode : projectConfig.buildArkMode).toLowerCase();
296}
297
298/**
299 * This Api only used by webpack compiling process - js-loader
300 * @param sourcePath The path in build cache dir
301 * @param sourceCode The intermediate js source code
302 */
303export function writeFileSyncByString(sourcePath: string, sourceCode: string, projectConfig: Object, logger: Object): void {
304  const filePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath,
305    projectConfig, undefined);
306  if (filePath.length === 0) {
307    return;
308  }
309  mkdirsSync(path.dirname(filePath));
310  if (/\.js$/.test(sourcePath)) {
311    sourceCode = transformModuleSpecifier(sourcePath, sourceCode, projectConfig);
312    if (projectConfig.buildArkMode === 'debug') {
313      fs.writeFileSync(filePath, sourceCode);
314      return;
315    }
316    writeObfuscatedSourceCode({content: sourceCode, buildFilePath: filePath, relativeSourceFilePath: ''},
317      logger, projectConfig);
318  }
319  if (/\.json$/.test(sourcePath)) {
320    fs.writeFileSync(filePath, sourceCode);
321  }
322}
323
324export function transformModuleSpecifier(sourcePath: string, sourceCode: string, projectConfig: Object): string {
325  // replace relative moduleSpecifier with ohmURl
326  const REG_RELATIVE_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]((?:\.\/|\.\.\/)[^'"]+|(?:\.\/?|\.\.\/?))['"]/g;
327  const REG_HAR_DEPENDENCY: RegExp = /(?:import|from)(?:\s*)['"]([^\.\/][^'"]+)['"]/g;
328  // replace requireNapi and requireNativeModule with import
329  const REG_REQUIRE_NATIVE_MODULE: RegExp = /var (\S+) = globalThis.requireNativeModule\(['"](\S+)['"]\);/g;
330  const REG_REQUIRE_NAPI_APP: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"], true, ['"](\S+)['"]\);/g;
331  const REG_REQUIRE_NAPI_OHOS: RegExp = /var (\S+) = globalThis.requireNapi\(['"](\S+)['"]\);/g;
332
333  return sourceCode.replace(REG_HAR_DEPENDENCY, (item, moduleRequest) => {
334    return replaceHarDependency(item, moduleRequest, projectConfig);
335  }).replace(REG_RELATIVE_DEPENDENCY, (item, moduleRequest) => {
336    return replaceRelativeDependency(item, moduleRequest, toUnixPath(sourcePath), projectConfig);
337  }).replace(REG_REQUIRE_NATIVE_MODULE, (_, moduleRequest, moduleName) => {
338    return `import ${moduleRequest} from '@native:${moduleName}';`;
339  }).replace(REG_REQUIRE_NAPI_APP, (_, moduleRequest, soName, moudlePath) => {
340    return `import ${moduleRequest} from '@app:${moudlePath}/${soName}';`;
341  }).replace(REG_REQUIRE_NAPI_OHOS, (_, moduleRequest, moduleName) => {
342    return `import ${moduleRequest} from '@ohos:${moduleName}';`;
343  });
344}
345
346function removeSuffix(filePath: string): string {
347  const SUFFIX_REG = /\.(?:d\.)?(ets|ts|mjs|cjs|js)$/;
348  return filePath.split(path.sep).join('/').replace(SUFFIX_REG, '');
349}
350
351export function getNormalizedOhmUrlByAliasName(aliasName: string, projectConfig: Object,
352  logger?: Object, filePath?: string): string {
353  let pkgName: string = aliasName;
354  const aliasPkgNameMap: Map<string, string> = projectConfig.dependencyAliasMap;
355  if (aliasPkgNameMap.has(aliasName)) {
356    pkgName = aliasPkgNameMap.get(aliasName);
357  }
358  const pkgInfo: Object = projectConfig.pkgContextInfo[pkgName];
359  if (!pkgInfo) {
360    logger.error(red, `ArkTS:INTERNAL ERROR: Failed to find package '${pkgName}'.\n` +
361      `Error Message: Failed to obtain package '${pkgName}' from the package context information.`, reset);
362  }
363  let normalizedPath: string = '';
364  if (!filePath) {
365    if (!pkgInfo.entryPath) {
366      logger.error(red, `ArkTS:INTERNAL ERROR: Failed to find entry file of '${pkgName}'.\n` +
367        `Error Message: Failed to obtain the entry file information of '${pkgName}' from the package context information.`, reset);
368    }
369    normalizedPath = `${pkgName}/${toUnixPath(pkgInfo.entryPath)}`;
370    normalizedPath = removeSuffix(normalizedPath);
371  } else {
372    const relativePath = toUnixPath(filePath).replace(aliasName, '');
373    normalizedPath = `${pkgName}${relativePath}`;
374  }
375  const isSo = pkgInfo.isSO ? 'Y' : 'N';
376  return `@normalized:${isSo}&${pkgInfo.moduleName}&${pkgInfo.bundleName}&${normalizedPath}&${pkgInfo.version}`;
377}
378
379export function getOhmUrlByByteCodeHar(moduleRequest: string, projectConfig: Object, logger?: Object):
380  string | undefined {
381  if (projectConfig.byteCodeHarInfo) {
382    if (Object.prototype.hasOwnProperty.call(projectConfig.byteCodeHarInfo, moduleRequest)) {
383      return getNormalizedOhmUrlByAliasName(moduleRequest, projectConfig, logger);
384    }
385    for (const byteCodeHarName in projectConfig.byteCodeHarInfo) {
386      if (moduleRequest.startsWith(byteCodeHarName + '/')) {
387        return getNormalizedOhmUrlByAliasName(byteCodeHarName, projectConfig, logger, moduleRequest);
388      }
389    }
390  }
391  return undefined;
392}
393
394export function getOhmUrlByExternalPackage(moduleRequest: string, projectConfig: Object, logger?: Object,
395  useNormalizedOHMUrl: boolean = false): string | undefined {
396  // The externalPkgMap store the ohmurl with the alias of hsp package and the hars depended on bytecode har.
397  let externalPkgMap: Object = Object.assign({}, projectConfig.hspNameOhmMap, projectConfig.harNameOhmMap);
398  if (Object.keys(externalPkgMap).length !== 0) {
399    if (Object.prototype.hasOwnProperty.call(externalPkgMap, moduleRequest)) {
400      if (useNormalizedOHMUrl) {
401        return getNormalizedOhmUrlByAliasName(moduleRequest, projectConfig, logger);
402      }
403      // case1: "@ohos/lib" ---> "@bundle:bundleName/lib/ets/index"
404      return externalPkgMap[moduleRequest];
405    }
406
407    for (const externalPkgName in externalPkgMap) {
408      if (moduleRequest.startsWith(externalPkgName + '/')) {
409        if (useNormalizedOHMUrl) {
410          return getNormalizedOhmUrlByAliasName(externalPkgName, projectConfig, logger, moduleRequest);
411        }
412        // case2: "@ohos/lib/src/main/ets/pages/page1" ---> "@bundle:bundleName/lib/ets/pages/page1"
413        const idx: number = externalPkgMap[externalPkgName].split('/', 2).join('/').length;
414        const ohmName: string = externalPkgMap[externalPkgName].substring(0, idx);
415        if (moduleRequest.indexOf(externalPkgName + '/' + SRC_MAIN) === 0) {
416          return moduleRequest.replace(externalPkgName + '/' + SRC_MAIN, ohmName);
417        } else {
418          return moduleRequest.replace(externalPkgName, ohmName);
419        }
420      }
421    }
422  }
423  return undefined;
424}
425
426function replaceHarDependency(item: string, moduleRequest: string, projectConfig: Object): string {
427  const hspOhmUrl: string | undefined = getOhmUrlByExternalPackage(moduleRequest, projectConfig);
428  if (hspOhmUrl !== undefined) {
429    return item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
430      return quotation + hspOhmUrl + quotation;
431    });
432  }
433  return item;
434}
435
436function locateActualFilePathWithModuleRequest(absolutePath: string): string {
437  if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) {
438    return absolutePath;
439  }
440
441  const filePath: string = absolutePath + getExtensionIfUnfullySpecifiedFilepath(absolutePath);
442  if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
443    return absolutePath;
444  }
445
446  return path.join(absolutePath, 'index');
447}
448
449function replaceRelativeDependency(item: string, moduleRequest: string, sourcePath: string, projectConfig: Object): string {
450  if (sourcePath && projectConfig.compileMode === ESMODULE) {
451    // remove file extension from moduleRequest
452    const SUFFIX_REG: RegExp = /\.(?:[cm]?js|[e]?ts|json)$/;
453    moduleRequest = moduleRequest.replace(SUFFIX_REG, '');
454
455    // normalize the moduleRequest
456    item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
457      let normalizedModuleRequest: string = toUnixPath(path.normalize(moduleRequest));
458      if (moduleRequest.startsWith('./')) {
459        normalizedModuleRequest = './' + normalizedModuleRequest;
460      }
461      return quotation + normalizedModuleRequest + quotation;
462    });
463
464    const filePath: string =
465      locateActualFilePathWithModuleRequest(path.resolve(path.dirname(sourcePath), moduleRequest));
466    const result: RegExpMatchArray | null =
467      filePath.match(/(\S+)(\/|\\)src(\/|\\)(?:main|ohosTest)(\/|\\)(ets|js)(\/|\\)(\S+)/);
468    if (result && projectConfig.aceModuleJsonPath) {
469      const npmModuleIdx: number = result[1].search(/(\/|\\)node_modules(\/|\\)/);
470      const projectRootPath: string = projectConfig.projectRootPath;
471      if (npmModuleIdx === -1 || npmModuleIdx === projectRootPath.search(/(\/|\\)node_modules(\/|\\)/)) {
472        const packageInfo: string[] = getPackageInfo(projectConfig.aceModuleJsonPath);
473        const bundleName: string = packageInfo[0];
474        const moduleName: string = packageInfo[1];
475        moduleRequest = `@bundle:${bundleName}/${moduleName}/${result[5]}/${toUnixPath(result[7])}`;
476        item = item.replace(/(['"])(?:\S+)['"]/, (_, quotation) => {
477          return quotation + moduleRequest + quotation;
478        });
479      }
480    }
481  }
482  return item;
483}
484
485interface ModuleInfo {
486  content: string,
487  /**
488   * the path in build cache dir
489   */
490  buildFilePath: string,
491  /**
492   * the `originSourceFilePath` relative to project root dir.
493   */
494  relativeSourceFilePath: string,
495  /**
496   * the origin source file path will be set with rollup moduleId when obfuscate intermediate js source code,
497   * whereas be set with tsc node.fileName when obfuscate intermediate ts source code.
498   */
499  originSourceFilePath?: string,
500  rollupModuleId?: string
501}
502
503export async function writeObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Object,
504  projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> {
505  if (compileToolIsRollUp() && projectConfig.arkObfuscator) {
506    MemoryUtils.tryGC();
507    performancePrinter?.filesPrinter?.startEvent(moduleInfo.buildFilePath);
508    await writeArkguardObfuscatedSourceCode(moduleInfo, logger, projectConfig, rollupNewSourceMaps);
509    performancePrinter?.filesPrinter?.endEvent(moduleInfo.buildFilePath, undefined, true);
510    MemoryUtils.tryGC();
511    return;
512  }
513  mkdirsSync(path.dirname(moduleInfo.buildFilePath));
514  if (!compileToolIsRollUp()) {
515    await writeMinimizedSourceCode(moduleInfo.content, moduleInfo.buildFilePath, logger, projectConfig.compileHar);
516    return;
517  }
518
519  if (moduleInfo.originSourceFilePath) {
520    const originSourceFilePath = toUnixPath(moduleInfo.originSourceFilePath);
521    let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originSourceFilePath);
522
523    if (!genFileInHar) {
524      genFileInHar = { sourcePath: originSourceFilePath };
525    }
526    if (!genFileInHar.sourceCachePath) {
527      genFileInHar.sourceCachePath = toUnixPath(moduleInfo.buildFilePath);
528    }
529    harFilesRecord.set(originSourceFilePath, genFileInHar);
530  }
531
532  fs.writeFileSync(moduleInfo.buildFilePath, moduleInfo.content);
533}
534
535/**
536 * This Api only be used by rollup compiling process & only be
537 * exported for unit test.
538 */
539export async function writeArkguardObfuscatedSourceCode(moduleInfo: ModuleInfo, logger: Object,
540  projectConfig: Object, rollupNewSourceMaps: Object = {}): Promise<void> {
541  const arkObfuscator = projectConfig.arkObfuscator;
542  const isDeclaration = (/\.d\.e?ts$/).test(moduleInfo.buildFilePath);
543  const packageDir = projectConfig.packageDir;
544  const projectRootPath = projectConfig.projectRootPath;
545  const useNormalized = projectConfig.useNormalizedOHMUrl;
546  const localPackageSet = projectConfig.localPackageSet;
547  const useTsHar = projectConfig.useTsHar;
548  const sourceMapGeneratorInstance = SourceMapGenerator.getInstance();
549
550  let previousStageSourceMap: sourceMap.RawSourceMap | undefined = undefined;
551  if (moduleInfo.relativeSourceFilePath.length > 0 && !isDeclaration) {
552    const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath;
553    previousStageSourceMap = sourceMapGeneratorInstance.getSpecifySourceMap(rollupNewSourceMaps, selectedFilePath) as sourceMap.RawSourceMap;
554  }
555
556  let historyNameCache = new Map<string, string>();
557  let namecachePath = moduleInfo.relativeSourceFilePath;
558  if (isDeclaration) {
559    namecachePath = getRelativeSourcePath(moduleInfo.originSourceFilePath, projectRootPath,
560      sourceFileBelongProject.get(toUnixPath(moduleInfo.originSourceFilePath)));
561  }
562  if (nameCacheMap) {
563    let identifierCache = nameCacheMap.get(namecachePath)?.[IDENTIFIER_CACHE];
564    deleteLineInfoForNameString(historyNameCache, identifierCache);
565  }
566
567  let mixedInfo: { content: string, sourceMap?: Object, nameCache?: Object, unobfuscationNameMap?: Map<string, Set<string>>};
568  let projectInfo: {
569    packageDir: string,
570    projectRootPath: string,
571    localPackageSet: Set<string>,
572    useNormalized: boolean,
573    useTsHar: boolean
574  } = { packageDir, projectRootPath, localPackageSet, useNormalized, useTsHar };
575  let filePathObj = { buildFilePath: moduleInfo.buildFilePath, relativeFilePath: moduleInfo.relativeSourceFilePath };
576  try {
577    mixedInfo = await arkObfuscator.obfuscate(moduleInfo.content, filePathObj, previousStageSourceMap,
578      historyNameCache, moduleInfo.originSourceFilePath, projectInfo);
579  } catch (err) {
580    logger.error(red, `ArkTS:INTERNAL ERROR: Failed to obfuscate file '${moduleInfo.relativeSourceFilePath}' with arkguard. ${err}`);
581  }
582
583  if (mixedInfo.sourceMap && !isDeclaration) {
584    const selectedFilePath = sourceMapGeneratorInstance.isNewSourceMaps() ? moduleInfo.rollupModuleId! : moduleInfo.relativeSourceFilePath;
585    mixedInfo.sourceMap.sources = [moduleInfo.relativeSourceFilePath];
586    sourceMapGeneratorInstance.fillSourceMapPackageInfo(moduleInfo.rollupModuleId!, mixedInfo.sourceMap);
587    sourceMapGeneratorInstance.updateSpecifySourceMap(rollupNewSourceMaps, selectedFilePath, mixedInfo.sourceMap);
588  }
589
590  if (mixedInfo.nameCache && !isDeclaration) {
591    let obfName: string = moduleInfo.relativeSourceFilePath;
592    let isOhModule = isPackageModulesFile(moduleInfo.originSourceFilePath, projectConfig);
593    if (projectConfig.obfuscationMergedObConfig?.options.enableFileNameObfuscation && !isOhModule) {
594      obfName = mangleFilePath(moduleInfo.relativeSourceFilePath);
595    }
596    mixedInfo.nameCache.obfName = obfName;
597    nameCacheMap.set(moduleInfo.relativeSourceFilePath, mixedInfo.nameCache);
598  }
599
600  if (mixedInfo.unobfuscationNameMap && !isDeclaration) {
601    let arrayObject: Record<string, string[]> = {};
602    // The type of unobfuscationNameMap's value is Set, convert Set to Array.
603    mixedInfo.unobfuscationNameMap.forEach((value: Set<string>, key: string) => {
604      let array: string[] = Array.from(value);
605      arrayObject[key] = array;
606    });
607    unobfuscationNamesObj[moduleInfo.relativeSourceFilePath] = arrayObject;
608  }
609
610  const newFilePath: string = tryMangleFileName(moduleInfo.buildFilePath, projectConfig, moduleInfo.originSourceFilePath);
611  if (newFilePath !== moduleInfo.buildFilePath && !isDeclaration) {
612    sourceMapGeneratorInstance.saveKeyMappingForObfFileName(moduleInfo.rollupModuleId!);
613  }
614  mkdirsSync(path.dirname(newFilePath));
615  fs.writeFileSync(newFilePath, mixedInfo.content ?? '');
616}
617
618export function tryMangleFileName(filePath: string, projectConfig: Object, originalFilePath: string): string {
619  originalFilePath = toUnixPath(originalFilePath);
620  let isOhModule = isPackageModulesFile(originalFilePath, projectConfig);
621  let genFileInHar: GeneratedFileInHar = harFilesRecord.get(originalFilePath);
622  if (!genFileInHar) {
623    genFileInHar = { sourcePath: originalFilePath };
624    harFilesRecord.set(originalFilePath, genFileInHar);
625  }
626
627  if (projectConfig.obfuscationMergedObConfig?.options?.enableFileNameObfuscation && !isOhModule) {
628    const mangledFilePath: string = mangleFilePath(filePath);
629    if ((/\.d\.e?ts$/).test(filePath)) {
630      genFileInHar.obfuscatedDeclarationCachePath = mangledFilePath;
631    } else {
632      genFileInHar.obfuscatedSourceCachePath = mangledFilePath;
633    }
634    filePath = mangledFilePath;
635  } else if (!(/\.d\.e?ts$/).test(filePath)) {
636    genFileInHar.sourceCachePath = filePath;
637  }
638  return filePath;
639}
640
641export async function mangleDeclarationFileName(logger: Object, projectConfig: Object,
642  sourceFileBelongProject: Map<string, string>): Promise<void> {
643  for (const [sourcePath, genFilesInHar] of harFilesRecord) {
644    if (genFilesInHar.originalDeclarationCachePath && genFilesInHar.originalDeclarationContent) {
645      let filePath = genFilesInHar.originalDeclarationCachePath;
646      let relativeSourceFilePath = getRelativeSourcePath(filePath,
647         projectConfig.projectRootPath, sourceFileBelongProject.get(toUnixPath(filePath)));
648      await writeObfuscatedSourceCode({
649          content: genFilesInHar.originalDeclarationContent,
650          buildFilePath: genFilesInHar.originalDeclarationCachePath,
651          relativeSourceFilePath: relativeSourceFilePath,
652          originSourceFilePath: sourcePath
653        }, logger, projectConfig, {});
654    }
655  }
656}
657
658export async function writeMinimizedSourceCode(content: string, filePath: string, logger: Object,
659  isHar: boolean = false): Promise<void> {
660  let result: MinifyOutput;
661  try {
662    const minifyOptions = {
663      compress: {
664        join_vars: false,
665        sequences: 0,
666        directives: false
667      }
668    };
669    if (!isHar) {
670      minifyOptions['format'] = {
671        semicolons: false,
672        beautify: true,
673        indent_level: 2
674      };
675    }
676    result = await minify(content, minifyOptions);
677  } catch {
678    logger.error(red, `ArkTS:INTERNAL ERROR: Failed to obfuscate source code for ${filePath}`, reset);
679  }
680
681  fs.writeFileSync(filePath, result.code);
682}
683
684export function genBuildPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object): string {
685  filePath = toUnixPath(filePath);
686  if (filePath.endsWith(EXTNAME_MJS)) {
687    filePath = filePath.replace(/\.mjs$/, EXTNAME_JS);
688  }
689  if (filePath.endsWith(EXTNAME_CJS)) {
690    filePath = filePath.replace(/\.cjs$/, EXTNAME_JS);
691  }
692  projectPath = toUnixPath(projectPath);
693
694  if (isPackageModulesFile(filePath, projectConfig)) {
695    const packageDir: string = projectConfig.packageDir;
696    const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir));
697    let output: string = '';
698    if (filePath.indexOf(fakePkgModulesPath) === -1) {
699      const hapPath: string = toUnixPath(projectConfig.projectRootPath);
700      const tempFilePath: string = filePath.replace(hapPath, '');
701      const sufStr: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1);
702      output = path.join(projectConfig.nodeModulesPath, ZERO, sufStr);
703    } else {
704      output = filePath.replace(fakePkgModulesPath, path.join(projectConfig.nodeModulesPath, ONE));
705    }
706    return output;
707  }
708
709  if (filePath.indexOf(projectPath) !== -1) {
710    const sufStr: string = filePath.replace(projectPath, '');
711    const output: string = path.join(buildPath, sufStr);
712    return output;
713  }
714
715  return '';
716}
717
718export function getPackageInfo(configFile: string): Array<string> {
719  if (packageCollection.has(configFile)) {
720    return packageCollection.get(configFile);
721  }
722  const data: Object = JSON.parse(fs.readFileSync(configFile).toString());
723  const bundleName: string = data.app.bundleName;
724  const moduleName: string = data.module.name;
725  packageCollection.set(configFile, [bundleName, moduleName]);
726  return [bundleName, moduleName];
727}
728
729/**
730 * This Api only used by webpack compiling process - result_process
731 * @param sourcePath The path in build cache dir
732 * @param sourceContent The intermediate js source code
733 */
734export function generateSourceFilesToTemporary(sourcePath: string, sourceContent: string, sourceMap: Object,
735  projectConfig: Object, logger: Object): void {
736    let jsFilePath: string = genTemporaryPath(sourcePath, projectConfig.projectPath, process.env.cachePath,
737      projectConfig, undefined);
738  if (jsFilePath.length === 0) {
739    return;
740  }
741  if (jsFilePath.endsWith(EXTNAME_ETS)) {
742    jsFilePath = jsFilePath.replace(/\.ets$/, EXTNAME_JS);
743  } else {
744    jsFilePath = jsFilePath.replace(/\.ts$/, EXTNAME_JS);
745  }
746  let sourceMapFile: string = genSourceMapFileName(jsFilePath);
747  if (sourceMapFile.length > 0 && projectConfig.buildArkMode === 'debug') {
748    let source = toUnixPath(sourcePath).replace(toUnixPath(projectConfig.projectRootPath) + '/', '');
749    // adjust sourceMap info
750    sourceMap.sources = [source];
751    sourceMap.file = path.basename(sourceMap.file);
752    delete sourceMap.sourcesContent;
753    newSourceMaps[source] = sourceMap;
754  }
755  sourceContent = transformModuleSpecifier(sourcePath, sourceContent, projectConfig);
756
757  mkdirsSync(path.dirname(jsFilePath));
758  if (projectConfig.buildArkMode === 'debug') {
759    fs.writeFileSync(jsFilePath, sourceContent);
760    return;
761  }
762
763  writeObfuscatedSourceCode({content: sourceContent, buildFilePath: jsFilePath, relativeSourceFilePath: ''},
764    logger, projectConfig);
765}
766
767export function genAbcFileName(temporaryFile: string): string {
768  let abcFile: string = temporaryFile;
769  if (temporaryFile.endsWith(EXTNAME_TS)) {
770    abcFile = temporaryFile.replace(/\.ts$/, EXTNAME_ABC);
771  } else {
772    abcFile = temporaryFile.replace(/\.js$/, EXTNAME_ABC);
773  }
774  return abcFile;
775}
776
777export function isOhModules(projectConfig: Object): boolean {
778  return projectConfig.packageDir === OH_MODULES;
779}
780
781export function isEs2Abc(projectConfig: Object): boolean {
782  return projectConfig.pandaMode === ES2ABC || projectConfig.pandaMode === 'undefined' ||
783    projectConfig.pandaMode === undefined;
784}
785
786export function isTs2Abc(projectConfig: Object): boolean {
787  return projectConfig.pandaMode === TS2ABC;
788}
789
790export function genProtoFileName(temporaryFile: string): string {
791  return temporaryFile.replace(/\.(?:[tj]s|json)$/, EXTNAME_PROTO_BIN);
792}
793
794export function genMergeProtoFileName(temporaryFile: string): string {
795  let protoTempPathArr: string[] = temporaryFile.split(TEMPORARY);
796  const sufStr: string = protoTempPathArr[protoTempPathArr.length - 1];
797  let protoBuildPath: string = path.join(process.env.cachePath, 'protos', sufStr);
798
799  return protoBuildPath;
800}
801
802export function removeDuplicateInfo(moduleInfos: Array<any>): Array<any> {
803  const tempModuleInfos: any[] = Array<any>();
804  moduleInfos.forEach((item) => {
805    let check: boolean = tempModuleInfos.every((newItem) => {
806      return item.tempFilePath !== newItem.tempFilePath;
807    });
808    if (check) {
809      tempModuleInfos.push(item);
810    }
811  });
812  moduleInfos = tempModuleInfos;
813
814  return moduleInfos;
815}
816
817export function buildCachePath(tailName: string, projectConfig: Object, logger: Object): string {
818  let pathName: string = process.env.cachePath !== undefined ?
819    path.join(projectConfig.cachePath, tailName) : path.join(projectConfig.aceModuleBuild, tailName);
820  validateFilePathLength(pathName, logger);
821  return pathName;
822}
823
824export function getArkBuildDir(arkDir: string): string {
825  if (isWindows()) {
826    return path.join(arkDir, 'build-win');
827  } else if (isMac()) {
828    return path.join(arkDir, 'build-mac');
829  } else {
830    return path.join(arkDir, 'build');
831  }
832}
833
834export function getBuildBinDir(arkDir: string): string {
835  return path.join(getArkBuildDir(arkDir), 'bin');
836}
837
838export function cleanUpUtilsObjects(): void {
839  newSourceMaps = {};
840  nameCacheMap.clear();
841  packageCollection.clear();
842}
843
844export function getHookEventFactory(share: Object, pluginName: string, hookName: string): Object {
845  if (typeof share.getHookEventFactory === 'function') {
846    return share.getHookEventFactory(pluginName, hookName);
847  } else {
848    return undefined;
849  }
850}
851
852export function createAndStartEvent(eventOrEventFactory: Object, eventName: string, syncFlag = false): Object {
853  if (eventOrEventFactory === undefined) {
854    return undefined;
855  }
856  let event: Object;
857  if (typeof eventOrEventFactory.createSubEvent === 'function') {
858    event = eventOrEventFactory.createSubEvent(eventName);
859  } else {
860    event = eventOrEventFactory.createEvent(eventName);
861  }
862  if (typeof event.startAsyncEvent === 'function' && syncFlag) {
863    event.startAsyncEvent();
864  } else {
865    event.start();
866  }
867  return event;
868}
869
870export function stopEvent(event: Object, syncFlag = false): void {
871  if (event !== undefined) {
872    if (typeof event.stopAsyncEvent === 'function' && syncFlag) {
873      event.stopAsyncEvent();
874    } else {
875      event.stop();
876    }
877  }
878}
879
880export function compileToolIsRollUp(): boolean {
881  return process.env.compileTool === 'rollup';
882}
883
884export function transformOhmurlToRecordName(ohmurl: string): string {
885  // @normalized:N&<moduleName>&<bunldName>&<packageName>/entry/ets/xxx/yyy&<version>
886  // ----> <bunldName>&<packageName>/entry/ets/xxx/yyy&<version>
887  return ohmurl.split(SEPARATOR_BITWISE_AND).slice(2).join(SEPARATOR_BITWISE_AND);
888}
889
890export function transformOhmurlToPkgName(ohmurl: string): string {
891  let normalizedPath: string = ohmurl.split(SEPARATOR_BITWISE_AND)[3];
892  let paths: Array<string> = normalizedPath.split(SEPARATOR_SLASH);
893  if (normalizedPath.startsWith(SEPARATOR_AT)) {
894    // Spec: If the normalized import path starts with '@', the package name is before the second '/' in the normalized
895    // import path, like:  @aaa/bbb/ccc/ddd ---> package name is @aaa/bbb
896    return paths.slice(0, 2).join(SEPARATOR_SLASH);
897  }
898  return paths[0];
899}