• 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 cluster from 'cluster';
17import fs from 'fs';
18import path from 'path';
19import ts from 'typescript';
20import os from 'os';
21import sourceMap from 'source-map';
22import * as crypto from 'node:crypto';
23
24import {
25  DEBUG,
26  ESMODULE,
27  EXTNAME_ETS,
28  EXTNAME_JS,
29  EXTNAME_TS,
30  EXTNAME_JSON,
31  EXTNAME_CJS,
32  EXTNAME_MJS,
33  TEMPORARY
34} from './common/ark_define';
35import {
36  nodeLargeOrEqualTargetVersion,
37  genTemporaryPath,
38  mkdirsSync,
39  validateFilePathLength,
40  toUnixPath,
41  isPackageModulesFile,
42  isFileInProject
43} from '../../utils';
44import {
45  tryMangleFileName,
46  writeObfuscatedSourceCode,
47} from '../../ark_utils';
48import { AOT_FULL, AOT_PARTIAL, AOT_TYPE } from '../../pre_define';
49import { SourceMapGenerator } from './generate_sourcemap';
50import {
51  handleObfuscatedFilePath,
52  enableObfuscateFileName,
53  enableObfuscatedFilePathConfig,
54  getRelativeSourcePath
55} from './common/ob_config_resolver';
56import {
57  CompileEvent,
58  createAndStartEvent,
59  stopEvent
60} from '../../performance';
61import { BytecodeObfuscator } from './bytecode_obfuscator';
62
63export let hasTsNoCheckOrTsIgnoreFiles: string[] = [];
64export let compilingEtsOrTsFiles: string[] = [];
65
66export function cleanUpFilesList(): void {
67  hasTsNoCheckOrTsIgnoreFiles = [];
68  compilingEtsOrTsFiles = [];
69}
70
71export function needAotCompiler(projectConfig: Object): boolean {
72  return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL ||
73    projectConfig.anBuildMode === AOT_PARTIAL);
74}
75
76export function isAotMode(projectConfig: Object): boolean {
77  return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL ||
78    projectConfig.anBuildMode === AOT_PARTIAL || projectConfig.anBuildMode === AOT_TYPE);
79}
80
81export function isDebug(projectConfig: Object): boolean {
82  return projectConfig.buildMode.toLowerCase() === DEBUG;
83}
84
85export function isBranchElimination(projectConfig: Object): boolean {
86  return projectConfig.branchElimination;
87}
88
89export function isMasterOrPrimary() {
90  return ((nodeLargeOrEqualTargetVersion(16) && cluster.isPrimary) ||
91    (!nodeLargeOrEqualTargetVersion(16) && cluster.isMaster));
92}
93
94export function changeFileExtension(file: string, targetExt: string, originExt = ''): string {
95  let currentExt = originExt.length === 0 ? path.extname(file) : originExt;
96  let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt));
97  return fileWithoutExt + targetExt;
98}
99
100function removeCacheFile(cacheFilePath: string, ext: string): void {
101  let filePath = toUnixPath(changeFileExtension(cacheFilePath, ext));
102  if (fs.existsSync(filePath)) {
103    fs.rmSync(filePath);
104  }
105}
106
107export function shouldETSOrTSFileTransformToJS(filePath: string, projectConfig: Object, metaInfo?: Object): boolean {
108  let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath,
109    projectConfig, metaInfo);
110
111  if (!projectConfig.processTs) {
112    removeCacheFile(cacheFilePath, EXTNAME_TS);
113    return true;
114  }
115
116  if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) {
117    // file involves in compilation
118    const hasTsNoCheckOrTsIgnore = hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1;
119    // Remove cacheFile whose extension is different the target file
120    removeCacheFile(cacheFilePath, hasTsNoCheckOrTsIgnore ? EXTNAME_TS : EXTNAME_JS);
121    return hasTsNoCheckOrTsIgnore;
122  }
123
124  cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig);
125  cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS));
126  return fs.existsSync(cacheFilePath);
127}
128
129// This API is used exclusively to determine whether a file needs to be converted into a JS file without removing the cached files,
130// which differs from API 'shouldETSOrTSFileTransformToJS'.
131export function shouldETSOrTSFileTransformToJSWithoutRemove(filePath: string, projectConfig: Object, metaInfo?: Object): boolean {
132  if (!projectConfig.processTs) {
133    return true;
134  }
135
136  if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) {
137    // file involves in compilation
138    return hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1;
139  }
140
141  let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath,
142    projectConfig, metaInfo);
143  cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig);
144  cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS));
145  return fs.existsSync(cacheFilePath);
146}
147
148function updateCacheFilePathIfEnableObuscatedFilePath(filePath: string, cacheFilePath: string, projectConfig: Object): string {
149  const isPackageModules = isPackageModulesFile(filePath, projectConfig);
150  if (enableObfuscatedFilePathConfig(isPackageModules, projectConfig) && enableObfuscateFileName(isPackageModules, projectConfig)) {
151    return handleObfuscatedFilePath(cacheFilePath, isPackageModules, projectConfig);
152  }
153  return cacheFilePath;
154}
155
156export async function writeFileContentToTempDir(id: string, content: string, projectConfig: Object,
157  logger: Object, parentEvent: CompileEvent, metaInfo: Object): Promise<void> {
158  if (isCommonJsPluginVirtualFile(id)) {
159    return;
160  }
161
162  if (!isCurrentProjectFiles(id, projectConfig)) {
163    return;
164  }
165
166  let filePath: string;
167  if (projectConfig.compileHar) {
168    // compileShared: compile shared har of project
169    filePath = genTemporaryPath(id,
170      projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath,
171      projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath,
172      projectConfig, metaInfo, projectConfig.compileShared);
173  } else {
174    filePath = genTemporaryPath(id, projectConfig.projectPath, projectConfig.cachePath, projectConfig, metaInfo);
175  }
176
177  const eventWriteFileContent = createAndStartEvent(parentEvent, 'write file content');
178  switch (path.extname(id)) {
179    case EXTNAME_ETS:
180    case EXTNAME_TS:
181    case EXTNAME_JS:
182    case EXTNAME_MJS:
183    case EXTNAME_CJS:
184      await writeFileContent(id, filePath, content, projectConfig, logger, metaInfo);
185      break;
186    case EXTNAME_JSON:
187      const newFilePath: string = tryMangleFileName(filePath, projectConfig, id, projectConfig.isBytecodeObfEnabled);
188      mkdirsSync(path.dirname(newFilePath));
189      fs.writeFileSync(newFilePath, content ?? '');
190      break;
191    default:
192      break;
193  }
194  stopEvent(eventWriteFileContent);
195}
196
197async function writeFileContent(sourceFilePath: string, filePath: string, content: string,
198  projectConfig: Object, logger: Object, metaInfo?: Object): Promise<void> {
199  if (!isSpecifiedExt(sourceFilePath, EXTNAME_JS)) {
200    filePath = changeFileExtension(filePath, EXTNAME_JS);
201  }
202
203  if (!isDebug(projectConfig)) {
204    const relativeSourceFilePath: string = getRelativeSourcePath(sourceFilePath, projectConfig.projectRootPath,
205      metaInfo?.belongProjectPath);
206    await writeObfuscatedSourceCode({content: content, buildFilePath: filePath,
207      relativeSourceFilePath: relativeSourceFilePath, originSourceFilePath: sourceFilePath, rollupModuleId: sourceFilePath},
208      logger, projectConfig, SourceMapGenerator.getInstance().getSourceMaps());
209    return;
210  }
211  mkdirsSync(path.dirname(filePath));
212  fs.writeFileSync(filePath, content, 'utf-8');
213}
214
215export function getEs2abcFileThreadNumber(): number {
216  const fileThreads: number = os.cpus().length < 16 ? os.cpus().length : 16;
217  return fileThreads;
218}
219
220export function isCommonJsPluginVirtualFile(filePath: string): boolean {
221  // rollup uses commonjs plugin to handle commonjs files,
222  // which will automatic generate files like 'jsfile.js?commonjs-exports'
223  return filePath.includes('\x00');
224}
225
226export function isCurrentProjectFiles(filePath: string, projectConfig: Object): boolean {
227  if (projectConfig.rootPathSet) {
228    for (const projectRootPath of projectConfig.rootPathSet) {
229      if (isFileInProject(filePath, projectRootPath)) {
230        return true;
231      }
232    }
233    return false;
234  }
235  return isFileInProject(filePath, projectConfig.projectRootPath);
236}
237
238export function genTemporaryModuleCacheDirectoryForBundle(projectConfig: Object): string {
239  const buildDirArr: string[] = projectConfig.aceModuleBuild.split(path.sep);
240  const abilityDir: string = buildDirArr[buildDirArr.length - 1];
241  const temporaryModuleCacheDirPath: string = path.join(projectConfig.cachePath, TEMPORARY, abilityDir);
242  mkdirsSync(temporaryModuleCacheDirPath);
243
244  return temporaryModuleCacheDirPath;
245}
246
247export function isSpecifiedExt(filePath: string, fileExtendName: string) {
248  return path.extname(filePath) === fileExtendName;
249}
250
251export function genCachePath(tailName: string, projectConfig: Object, logger: Object): string {
252  const pathName: string = projectConfig.cachePath !== undefined ?
253    path.join(projectConfig.cachePath, TEMPORARY, tailName) : path.join(projectConfig.aceModuleBuild, tailName);
254  mkdirsSync(path.dirname(pathName));
255
256  validateFilePathLength(pathName, logger);
257  return pathName;
258}
259
260export function isTsOrEtsSourceFile(file: string): boolean {
261  return /(?<!\.d)\.[e]?ts$/.test(file);
262}
263
264export function isJsSourceFile(file: string): boolean {
265  return /\.[cm]?js$/.test(file);
266}
267
268export function isJsonSourceFile(file: string): boolean {
269  return /\.json$/.test(file);
270}
271
272export async function updateSourceMap(originMap: sourceMap.RawSourceMap, newMap: sourceMap.RawSourceMap): Promise<any> {
273  if (!originMap) {
274    return newMap;
275  }
276  if (!newMap) {
277    return originMap;
278  }
279  const originConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(originMap);
280  const newConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(newMap);
281  const newMappingList: sourceMap.MappingItem[] = [];
282  newConsumer.eachMapping((mapping: sourceMap.MappingItem) => {
283    if (mapping.originalLine == null) {
284      return;
285    }
286    const originalPos =
287      originConsumer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn });
288    if (originalPos.source == null) {
289      return;
290    }
291    mapping.originalLine = originalPos.line;
292    mapping.originalColumn = originalPos.column;
293    newMappingList.push(mapping);
294  });
295  const updatedGenerator: sourceMap.SourceMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(newConsumer);
296  updatedGenerator._file = originMap.file;
297  updatedGenerator._mappings._array = newMappingList;
298  return JSON.parse(updatedGenerator.toString());
299}
300
301export function hasArkDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration |
302  ts.StructDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration, decortorName: string): boolean {
303  const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
304  if (decorators && decorators.length) {
305    for (let i = 0; i < decorators.length; i++) {
306      const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim();
307      return originalDecortor === decortorName;
308    }
309  }
310  return false;
311}
312
313export const utUtils = {
314  writeFileContent
315};
316
317export function hasExistingPaths(paths: Set<string>): boolean {
318  for (const p of paths) {
319    if (fs.existsSync(toUnixPath(p))) {
320      return true;
321    }
322  }
323  return false;
324}
325
326export function isSubPathOf(targetPath: string, parentDir: string): boolean {
327  const resolvedParent = toUnixPath(path.resolve(parentDir));
328  const resolvedTarget = toUnixPath(path.resolve(targetPath));
329  return resolvedTarget === resolvedParent || resolvedTarget.startsWith(resolvedParent + '/');
330}
331
332export function calculateFileHash(filePath: string, algorithm: string = 'sha256'): string {
333  const fileBuffer = fs.readFileSync(filePath);
334  const hash = crypto.createHash(algorithm);
335  hash.update(fileBuffer);
336  return hash.digest('hex');
337}