* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cluster from 'cluster';
import fs from 'fs';
import path from 'path';
import ts from 'typescript';
import os from 'os';
import sourceMap from 'source-map';
import {
DEBUG,
ESMODULE,
EXTNAME_ETS,
EXTNAME_JS,
EXTNAME_TS,
EXTNAME_JSON,
EXTNAME_CJS,
EXTNAME_MJS,
TEMPORARY
} from './common/ark_define';
import {
nodeLargeOrEqualTargetVersion,
genTemporaryPath,
mkdirsSync,
validateFilePathLength,
toUnixPath,
isPackageModulesFile,
isFileInProject
} from '../../utils';
import {
tryMangleFileName,
writeObfuscatedSourceCode,
writeFileSyncCaseAware,
} from '../../ark_utils';
import { AOT_FULL, AOT_PARTIAL, AOT_TYPE } from '../../pre_define';
import { SourceMapGenerator } from './generate_sourcemap';
import {
handleObfuscatedFilePath,
enableObfuscateFileName,
enableObfuscatedFilePathConfig,
getRelativeSourcePath
} from './common/ob_config_resolver';
import {
CompileEvent,
createAndStartEvent,
stopEvent
} from '../../performance';
import { BytecodeObfuscator } from './bytecode_obfuscator';
export let hasTsNoCheckOrTsIgnoreFiles: string[] = [];
export let compilingEtsOrTsFiles: string[] = [];
export function cleanUpFilesList(): void {
hasTsNoCheckOrTsIgnoreFiles = [];
compilingEtsOrTsFiles = [];
}
export function needAotCompiler(projectConfig: Object): boolean {
return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL ||
projectConfig.anBuildMode === AOT_PARTIAL);
}
export function isAotMode(projectConfig: Object): boolean {
return projectConfig.compileMode === ESMODULE && (projectConfig.anBuildMode === AOT_FULL ||
projectConfig.anBuildMode === AOT_PARTIAL || projectConfig.anBuildMode === AOT_TYPE);
}
export function isDebug(projectConfig: Object): boolean {
return projectConfig.buildMode.toLowerCase() === DEBUG;
}
export function isBranchElimination(projectConfig: Object): boolean {
return projectConfig.branchElimination;
}
export function isMasterOrPrimary() {
return ((nodeLargeOrEqualTargetVersion(16) && cluster.isPrimary) ||
(!nodeLargeOrEqualTargetVersion(16) && cluster.isMaster));
}
export function changeFileExtension(file: string, targetExt: string, originExt = ''): string {
let currentExt = originExt.length === 0 ? path.extname(file) : originExt;
let fileWithoutExt = file.substring(0, file.lastIndexOf(currentExt));
return fileWithoutExt + targetExt;
}
export function removeCacheFile(cacheFilePath: string, ext: string): void {
let filePath = toUnixPath(changeFileExtension(cacheFilePath, ext));
if (fs.existsSync(filePath)) {
fs.rmSync(filePath);
}
}
export function shouldETSOrTSFileTransformToJS(filePath: string, projectConfig: Object, metaInfo?: Object): boolean {
let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath,
projectConfig, metaInfo);
if (!projectConfig.processTs) {
removeCacheFile(cacheFilePath, EXTNAME_TS);
return true;
}
if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) {
const hasTsNoCheckOrTsIgnore = hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1;
removeCacheFile(cacheFilePath, hasTsNoCheckOrTsIgnore ? EXTNAME_TS : EXTNAME_JS);
return hasTsNoCheckOrTsIgnore;
}
cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig);
cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS));
return fs.existsSync(cacheFilePath);
}
export function shouldETSOrTSFileTransformToJSWithoutRemove(filePath: string, projectConfig: Object, metaInfo?: Object): boolean {
if (!projectConfig.processTs) {
return true;
}
if (compilingEtsOrTsFiles.indexOf(filePath) !== -1) {
return hasTsNoCheckOrTsIgnoreFiles.indexOf(filePath) !== -1;
}
let cacheFilePath: string = genTemporaryPath(filePath, projectConfig.projectPath, projectConfig.cachePath,
projectConfig, metaInfo);
cacheFilePath = updateCacheFilePathIfEnableObuscatedFilePath(filePath, cacheFilePath, projectConfig);
cacheFilePath = toUnixPath(changeFileExtension(cacheFilePath, EXTNAME_JS));
return fs.existsSync(cacheFilePath);
}
function updateCacheFilePathIfEnableObuscatedFilePath(filePath: string, cacheFilePath: string, projectConfig: Object): string {
const isPackageModules = isPackageModulesFile(filePath, projectConfig);
if (enableObfuscatedFilePathConfig(isPackageModules, projectConfig) && enableObfuscateFileName(isPackageModules, projectConfig)) {
return handleObfuscatedFilePath(cacheFilePath, isPackageModules, projectConfig);
}
return cacheFilePath;
}
export async function writeFileContentToTempDir(id: string, content: string, projectConfig: Object,
logger: Object, parentEvent: CompileEvent, metaInfo: Object): Promise<void> {
if (isCommonJsPluginVirtualFile(id)) {
return;
}
if (!isCurrentProjectFiles(id, projectConfig)) {
return;
}
let filePath: string;
if (projectConfig.compileHar) {
filePath = genTemporaryPath(id,
projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath,
projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath,
projectConfig, metaInfo, projectConfig.compileShared);
} else {
filePath = genTemporaryPath(id, projectConfig.projectPath, projectConfig.cachePath, projectConfig, metaInfo);
}
const eventWriteFileContent = createAndStartEvent(parentEvent, 'write file content');
switch (path.extname(id)) {
case EXTNAME_ETS:
case EXTNAME_TS:
case EXTNAME_JS:
case EXTNAME_MJS:
case EXTNAME_CJS:
await writeFileContent(id, filePath, content, projectConfig, logger, metaInfo);
break;
case EXTNAME_JSON:
const newFilePath: string = tryMangleFileName(filePath, projectConfig, id, projectConfig.isBytecodeObfEnabled);
mkdirsSync(path.dirname(newFilePath));
writeFileSyncCaseAware(newFilePath, content ?? '');
break;
default:
break;
}
stopEvent(eventWriteFileContent);
}
async function writeFileContent(sourceFilePath: string, filePath: string, content: string,
projectConfig: Object, logger: Object, metaInfo?: Object): Promise<void> {
if (!isSpecifiedExt(sourceFilePath, EXTNAME_JS)) {
filePath = changeFileExtension(filePath, EXTNAME_JS);
}
if (!isDebug(projectConfig)) {
const relativeSourceFilePath: string = getRelativeSourcePath(sourceFilePath, projectConfig.projectRootPath,
metaInfo?.belongProjectPath);
await writeObfuscatedSourceCode({content: content, buildFilePath: filePath,
relativeSourceFilePath: relativeSourceFilePath, originSourceFilePath: sourceFilePath, rollupModuleId: sourceFilePath},
logger, projectConfig, SourceMapGenerator.getInstance().getSourceMaps());
return;
}
mkdirsSync(path.dirname(filePath));
writeFileSyncCaseAware(filePath, content, 'utf-8');
}
export function getEs2abcFileThreadNumber(): number {
const fileThreads: number = os.cpus().length < 16 ? os.cpus().length : 16;
return fileThreads;
}
export function isCommonJsPluginVirtualFile(filePath: string): boolean {
return filePath.includes('\x00');
}
export function isCurrentProjectFiles(filePath: string, projectConfig: Object): boolean {
if (projectConfig.rootPathSet) {
for (const projectRootPath of projectConfig.rootPathSet) {
if (isFileInProject(filePath, projectRootPath)) {
return true;
}
}
return false;
}
return isFileInProject(filePath, projectConfig.projectRootPath);
}
export function genTemporaryModuleCacheDirectoryForBundle(projectConfig: Object): string {
const buildDirArr: string[] = projectConfig.aceModuleBuild.split(path.sep);
const abilityDir: string = buildDirArr[buildDirArr.length - 1];
const temporaryModuleCacheDirPath: string = path.join(projectConfig.cachePath, TEMPORARY, abilityDir);
mkdirsSync(temporaryModuleCacheDirPath);
return temporaryModuleCacheDirPath;
}
export function isSpecifiedExt(filePath: string, fileExtendName: string) {
return path.extname(filePath) === fileExtendName;
}
export function genCachePath(tailName: string, projectConfig: Object, logger: Object): string {
const pathName: string = projectConfig.cachePath !== undefined ?
path.join(projectConfig.cachePath, TEMPORARY, tailName) : path.join(projectConfig.aceModuleBuild, tailName);
mkdirsSync(path.dirname(pathName));
validateFilePathLength(pathName, logger);
return pathName;
}
export function isTsOrEtsSourceFile(file: string): boolean {
return /(?<!\.d)\.[e]?ts$/.test(file);
}
export function isJsSourceFile(file: string): boolean {
return /\.[cm]?js$/.test(file);
}
export function isJsonSourceFile(file: string): boolean {
return /\.json$/.test(file);
}
export async function updateSourceMap(originMap: sourceMap.RawSourceMap, newMap: sourceMap.RawSourceMap): Promise<any> {
if (!originMap) {
return newMap;
}
if (!newMap) {
return originMap;
}
const originConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(originMap);
const newConsumer: sourceMap.SourceMapConsumer = await new sourceMap.SourceMapConsumer(newMap);
const newMappingList: sourceMap.MappingItem[] = [];
newConsumer.eachMapping((mapping: sourceMap.MappingItem) => {
if (mapping.originalLine == null) {
return;
}
const originalPos =
originConsumer.originalPositionFor({ line: mapping.originalLine, column: mapping.originalColumn });
if (originalPos.source == null) {
return;
}
mapping.originalLine = originalPos.line;
mapping.originalColumn = originalPos.column;
newMappingList.push(mapping);
});
const updatedGenerator: sourceMap.SourceMapGenerator = sourceMap.SourceMapGenerator.fromSourceMap(newConsumer);
updatedGenerator._file = originMap.file;
updatedGenerator._mappings._array = newMappingList;
return JSON.parse(updatedGenerator.toString());
}
export function hasArkDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration |
ts.StructDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration, decortorName: string): boolean {
const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node);
if (decorators && decorators.length) {
for (let i = 0; i < decorators.length; i++) {
const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim();
return originalDecortor === decortorName;
}
}
return false;
}
export const utUtils = {
writeFileContent
};