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