* Copyright (c) 2022 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 fs from 'fs';
import path from 'path';
import * as ts from 'typescript';
import * as crypto from 'crypto';
const fse = require('fs-extra');
const JSON5 = require('json5');
import { fileInfoCache } from './file_info_cache';
import {
projectConfig,
systemModules,
globalProgram,
sdkConfigs,
sdkConfigPrefix,
partialUpdateConfig,
resetProjectConfig,
resetGlobalProgram
} from '../main';
import {
preprocessExtend,
preprocessNewExtend
} from './validate_ui_syntax';
import {
INNER_COMPONENT_MEMBER_DECORATORS,
COMPONENT_BUILD_FUNCTION,
STYLE_ADD_DOUBLE_DOLLAR,
$$,
PROPERTIES_ADD_DOUBLE_DOLLAR,
DOLLAR_BLOCK_INTERFACE,
COMPONENT_EXTEND_DECORATOR,
COMPONENT_BUILDER_DECORATOR,
ESMODULE,
EXTNAME_D_ETS,
EXTNAME_JS,
FOREACH_LAZYFOREACH,
COMPONENT_IF,
TS_WATCH_END_MSG,
TS_BUILD_INFO_SUFFIX,
HOT_RELOAD_BUILD_INFO_SUFFIX,
WATCH_COMPILER_BUILD_INFO_SUFFIX,
COMPONENT_STYLES_DECORATOR,
COMPONENT_LOCAL_BUILDER_DECORATOR
} from './pre_define';
import {
JS_BIND_COMPONENTS,
BUILDIN_STYLE_NAMES
} from './component_map';
import { logger } from './compile_info';
import {
hasDecorator,
isString,
generateSourceFilesInHar,
storedFileInfo,
toUnixPath,
isWindows,
isMac,
tryToLowerCasePath,
getRollupCache,
setRollupCache,
getRollupCommonCache,
setRollupCommonCache
} from './utils';
import {
isExtendFunction,
isOriginalExtend
} from './process_ui_syntax';
import { visualTransform } from './process_visual';
import { tsWatchEmitter } from './fast_build/ets_ui/rollup-plugin-ets-checker';
import {
doArkTSLinter,
ArkTSLinterMode,
ArkTSVersion,
transfromErrorCode,
} from './do_arkTS_linter';
import {
getJsDocNodeCheckConfig,
isCardFile,
getRealModulePath,
getJsDocNodeConditionCheckResult,
isSourceRetentionDeclarationValid,
isSourceRetentionAnnotationContentValid,
isApiAvailableVersionSpecifications
} from './fast_build/system_api/api_check_utils';
import { sourceFileDependencies } from './fast_build/ark_compiler/common/ob_config_resolver';
import { MemoryMonitor } from './fast_build/meomry_monitor/rollup-plugin-memory-monitor';
import { MemoryDefine } from './fast_build/meomry_monitor/memory_define';
import {
CompileEvent,
createAndStartEvent,
stopEvent
} from './performance';
import {
LINTER_SUBSYSTEM_CODE,
HvigorErrorInfo
} from './hvigor_error_code/hvigor_error_info';
import { ErrorCodeModule } from './hvigor_error_code/const/error_code_module';
import { buildErrorInfoFromDiagnostic } from './hvigor_error_code/utils';
import { concatenateEtsOptions, getExternalComponentPaths } from './external_component_map';
import { ATOMICSERVICE_BUNDLE_TYPE } from './fast_build/system_api/api_check_define';
import { sdkBuildErrorInfoFromDiagnostic } from './fast_build/system_api/api_check_utils';
import { DIAGNOSTIC_SDK_CODE_MAP, SdkHvigorErrorInfo } from './fast_build/system_api/api_check_define';
export interface LanguageServiceCache {
service?: ts.LanguageService;
pkgJsonFileHash?: string;
targetESVersion?: ts.ScriptTarget;
types?: string[];
maxFlowDepth?: number;
useTsHar?: boolean;
preTsImportSendable?: boolean;
preSkipOhModulesLint?: boolean;
preEnableStrictCheckOHModule?: boolean;
preDisableStrictCheckPaths?: string[];
preDisableSendableCheckRules?: string[];
preMixCompile?: boolean;
autoLazyImport?: boolean;
autoLazyFilterInclude?: string[];
autoLazyFilterExclude?: string[];
useDeclarationFileSignature?: boolean;
strictCheckerOnly?: boolean;
tsImportSoCheck?: boolean;
}
export const SOURCE_FILES: Map<string, ts.SourceFile> = new Map();
export let localPackageSet: Set<string> = new Set();
export const TSC_SYSTEM_CODE = '105';
export const fileCache: Map<string, string> = new Map();
export const MAX_FLOW_DEPTH_DEFAULT_VALUE = 2000;
export const MAX_FLOW_DEPTH_MAXIMUM_VALUE = 65535;
export const SENDABLE_CLASS_DECORATOR_RULE = 'arkts-sendable-class-decorator';
export function readDeaclareFiles(): string[] {
const declarationsFileNames: string[] = [];
fs.readdirSync(path.resolve(__dirname, '../declarations'))
.forEach((fileName: string) => {
if (/\.d\.ts$/.test(fileName)) {
declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName));
}
});
return declarationsFileNames;
}
const buildInfoWriteFile: ts.WriteFileCallback = (fileName: string, data: string) => {
if (fileName.includes(TS_BUILD_INFO_SUFFIX)) {
const fd: number = fs.openSync(fileName, 'w');
fs.writeSync(fd, data, undefined, 'utf8');
fs.closeSync(fd);
}
};
const filesBuildInfo: Map<string, string> = new Map();
export let compilerOptions = ts.readConfigFile(
path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions;
const componentPaths: string[] | undefined = getExternalComponentPaths();
if (componentPaths) {
for (const componentPath of componentPaths) {
if (!fs.existsSync(componentPath)) {
continue;
}
const externalCompilerOptions: ts.CompilerOptions = ts.readConfigFile(
path.resolve(componentPath, 'externalconfig.json'), ts.sys.readFile
).config.compilerOptions;
concatenateEtsOptions(compilerOptions, externalCompilerOptions);
}
}
function setCompilerOptions(resolveModulePaths: string[]): void {
const allPath: Array<string> = ['*'];
const basePath: string = path.resolve(projectConfig.projectPath);
if (process.env.compileTool === 'rollup' && resolveModulePaths && resolveModulePaths.length) {
resolveModulePaths.forEach((item: string) => {
if (!(/oh_modules$/.test(item) || /node_modules$/.test(item))) {
allPath.push(path.join(path.relative(basePath, item), '*'));
}
});
} else {
if (!projectConfig.aceModuleJsonPath) {
allPath.push('../../../../../*');
allPath.push('../../*');
} else {
allPath.push('../../../../*');
allPath.push('../*');
}
}
const suffix: string = projectConfig?.hotReload ? HOT_RELOAD_BUILD_INFO_SUFFIX :
`${TS_BUILD_INFO_SUFFIX}${projectConfig?.widgetCompile === 'true' ? '_widget' : ''}`;
const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', suffix);
checkArkTSVersion();
Object.assign(compilerOptions, {
'allowJs': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE ? true : false,
'checkJs': (projectConfig.bundleType === ATOMICSERVICE_BUNDLE_TYPE) ? true :
(getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE ? false : undefined),
'emitNodeModulesFiles': true,
'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve,
'module': ts.ModuleKind.CommonJS,
'moduleResolution': ts.ModuleResolutionKind.NodeJs,
'noEmit': true,
'target': convertConfigTarget(getTargetESVersion()),
'maxFlowDepth': getMaxFlowDepth(),
'baseUrl': basePath,
'paths': {
'*': allPath
},
'lib': convertConfigLib(getTargetESVersionLib()),
'types': projectConfig.compilerTypes,
'etsAnnotationsEnable': projectConfig.allowEtsAnnotations,
'etsLoaderPath': projectConfig.etsLoaderPath,
'needDoArkTsLinter': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE,
'isCompatibleVersion': getArkTSLinterMode() === ArkTSLinterMode.COMPATIBLE_MODE,
'skipTscOhModuleCheck': partialUpdateConfig.skipTscOhModuleCheck,
'skipArkTSStaticBlocksCheck': partialUpdateConfig.skipArkTSStaticBlocksCheck,
'incremental': true,
'tsBuildInfoFile': buildInfoPath,
'tsImportSendableEnable': tsImportSendable,
'skipPathsInKeyForCompilationSettings': reuseLanguageServiceForDepChange,
'skipBaseUrlInKeyForCompilationSettings': shareDocumentRegistryCache,
'compatibleSdkVersionStage': projectConfig.compatibleSdkVersionStage,
'compatibleSdkVersion': projectConfig.compatibleSdkVersion,
'compileSdkVersion': projectConfig.compileSdkVersion,
'skipOhModulesLint': skipOhModulesLint,
'enableStrictCheckOHModule': enableStrictCheckOHModule,
'disableStrictCheckPaths': disableStrictCheckPaths,
'disableSendableCheckRules': disableSendableCheckRules,
'mixCompile': mixCompile,
'isCompileJsHar': isCompileJsHar(),
'moduleRootPath': projectConfig.moduleRootPath,
'strictCheckerOnly': projectConfig.strictCheckerOnly,
'tsImportSoCheck': projectConfig.tsImportSoCheck,
});
if (projectConfig.compileMode === ESMODULE) {
Object.assign(compilerOptions, {
'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Remove,
'module': ts.ModuleKind.ES2020
});
}
if (projectConfig.packageDir === 'oh_modules') {
Object.assign(compilerOptions, { 'packageManagerType': 'ohpm' });
}
if (moduleResolutionCache) {
moduleResolutionCache.update(compilerOptions);
} else {
moduleResolutionCache = ts.createModuleResolutionCache(process.cwd(), getCanonicalFileName, compilerOptions);
}
readTsBuildInfoFileInCrementalMode(buildInfoPath, projectConfig);
}
function isCompileJsHar(): boolean {
return projectConfig.compileHar && projectConfig.byteCodeHar === false && projectConfig.buildMode === 'Release' &&
projectConfig.obfuscationOptions?.selfConfig.ruleOptions.enable && !projectConfig.useTsHar;
}
function checkArkTSVersion(): void {
const etsCheckerLogger = fastBuildLogger || logger;
if (getArkTSVersion() === ArkTSVersion.ArkTS_1_0 && tsImportSendable) {
const logMessage: string = 'ArkTS: ArkTSVersion1.0 does not support tsImportSendable in any condition';
etsCheckerLogger.error('\u001b[31m' + logMessage);
tsImportSendable = false;
}
}
function convertConfigTarget(target: number | string): number | string {
if ((typeof target === 'number') && (target in ts.ScriptTarget)) {
return target;
}
return ts.convertCompilerOptionsFromJson({ 'target': target }, '').options.target;
}
function convertConfigLib(libs: string[]): string[] {
let converted: boolean = true;
let libMapValues: string[] = Array.from(ts.libMap.values());
for (let i = 0; i < libs.length; i++) {
if (!libMapValues.includes(libs[i])) {
converted = false;
break;
}
}
if (converted) {
return libs;
}
return ts.convertCompilerOptionsFromJson({ 'lib': libs }, '').options.lib;
}
* Read the source code information in the project of the last compilation process, and then use it
* to determine whether the file has been modified during this compilation process.
*/
function readTsBuildInfoFileInCrementalMode(buildInfoPath: string, projectConfig: Object): void {
if (!fs.existsSync(buildInfoPath) || !(projectConfig.compileHar || projectConfig.compileShared)) {
return;
}
type FileInfoType = {
version: string;
affectsGlobalScope: boolean;
};
type ProgramType = {
fileNames: string[];
fileInfos: (FileInfoType | string)[];
};
let buildInfoProgram: ProgramType;
try {
const content: { program: ProgramType } = JSON.parse(fs.readFileSync(buildInfoPath, 'utf-8'));
buildInfoProgram = content.program;
if (!buildInfoProgram || !buildInfoProgram.fileNames || !buildInfoProgram.fileInfos) {
throw new Error('.tsbuildinfo content is invalid');
}
} catch (err) {
fastBuildLogger.warn('\u001b[33m' + 'ArkTS: Failed to parse .tsbuildinfo file. Error message: ' + err.message.toString());
return;
}
const buildInfoDirectory: string = path.dirname(buildInfoPath);
* For the windos and mac platform, the file path in tsbuildinfo is in lowercase, while buildInfoDirectory is the original path (including uppercase).
* Therefore, the path needs to be converted to lowercase, and then perform path comparison.
*/
const isMacOrWin = isWindows() || isMac();
const fileNames: string[] = buildInfoProgram.fileNames;
const fileInfos: (FileInfoType | string)[] = buildInfoProgram.fileInfos;
fileInfos.forEach((fileInfo, index) => {
const version: string = typeof fileInfo === 'string' ? fileInfo : fileInfo.version;
const absPath: string = path.resolve(buildInfoDirectory, fileNames[index]);
filesBuildInfo.set(isMacOrWin ? tryToLowerCasePath(absPath) : absPath, version);
});
}
interface extendInfo {
start: number,
end: number,
compName: string
}
function createHash(str: string): string {
const hash = crypto.createHash('sha256');
hash.update(str);
return hash.digest('hex');
}
export function getFileContentWithHash(fileName: string): string {
let fileContent: string | undefined = fileCache.get(fileName);
if (fileContent === undefined) {
fileContent = fs.readFileSync(fileName).toString();
fileCache.set(fileName, fileContent);
setHashValueByFilePath?.(fileName, createHash(fileContent));
}
return fileContent;
}
export const fileHashScriptVersion: (fileName: string) => string = (fileName: string) => {
if (!fs.existsSync(fileName)) {
return '0';
}
let fileContent: string = getFileContentWithHash(fileName);
let cacheInfo: CacheFileName = cache[path.resolve(fileName)];
const errorCodeRequireRecheck: number = 2307;
if (cacheInfo && cacheInfo.error === true && cacheInfo.errorCodes && cacheInfo.errorCodes.includes(errorCodeRequireRecheck)) {
fileContent += Date.now().toString();
return createHash(fileContent);
}
return getHashByFilePath?.(fileName) ?? createHash(fileContent);
};
const reuseLanguageServiceForDepChange: boolean = true;
export let needReCheckForChangedDepUsers: boolean = false;
let setHashValueByFilePath: Function | undefined = undefined;
let getHashByFilePath: Function | undefined = undefined;
export function createLanguageService(rootFileNames: string[], resolveModulePaths: string[],
parentEvent?: CompileEvent, rollupShareObject?: Object): ts.LanguageService {
setHashValueByFilePath = rollupShareObject?.setHashValueByFilePath;
getHashByFilePath = rollupShareObject?.getHashByFilePath;
setCompilerOptions(resolveModulePaths);
const servicesHost: ts.LanguageServiceHost = {
getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()],
getScriptVersion: fileHashScriptVersion,
getScriptSnapshot: function (fileName) {
if (!fs.existsSync(fileName)) {
return undefined;
}
let fileContent: string = getFileContentWithHash(fileName);
if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
ts.PerformanceDotting?.startAdvanced('scriptSnapshot');
appComponentCollection.set(path.join(fileName), new Set());
let content: string = processContent(fileContent, fileName);
const extendFunctionInfo: extendInfo[] = [];
content = instanceInsteadThis(content, fileName, extendFunctionInfo, this.uiProps);
ts.PerformanceDotting?.stopAdvanced('scriptSnapshot');
return ts.ScriptSnapshot.fromString(content);
}
return ts.ScriptSnapshot.fromString(fileContent);
},
getCurrentDirectory: () => process.cwd(),
getCompilationSettings: () => compilerOptions,
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
readDirectory: ts.sys.readDirectory,
resolveModuleNames: resolveModuleNames,
resolveTypeReferenceDirectives: resolveTypeReferenceDirectives,
directoryExists: ts.sys.directoryExists,
getDirectories: ts.sys.getDirectories,
getJsDocNodeCheckedConfig: (fileCheckedInfo: ts.FileCheckModuleInfo, sourceFileName: string) => {
return getJsDocNodeCheckConfig(fileCheckedInfo.currentFileName, sourceFileName);
},
getFileCheckedModuleInfo: (containFilePath: string) => {
return {
fileNeedCheck: true,
checkPayload: undefined,
currentFileName: containFilePath
};
},
getJsDocNodeConditionCheckedResult: (jsDocFileCheckedInfo: ts.FileCheckModuleInfo, jsDocInfos: ts.JsDocTagInfo[], jsDocs?: ts.JSDoc[]) => {
return getJsDocNodeConditionCheckResult(jsDocFileCheckedInfo, jsDocInfos, jsDocs);
},
isSourceRetentionDeclarationValid: (annoDecl: ts.AnnotationDeclaration) => {
return isSourceRetentionDeclarationValid(annoDecl);
},
isSourceRetentionAnnotationContentValid: (annotation: ts.Annotation) => {
return isSourceRetentionAnnotationContentValid(annotation);
},
isApiAvailableVersionSpecifications: (apiAvailable: ts.CallExpression, typeOfNodeFunc: Function) => {
return isApiAvailableVersionSpecifications(apiAvailable, typeOfNodeFunc);
},
uiProps: new Set(),
clearProps: function () {
dollarCollection.clear();
extendCollection.clear();
newExtendCollection.clear();
this.uiProps = new Set();
},
hasInvalidatedResolutions: (filePath: string): boolean => {
return reuseLanguageServiceForDepChange && needReCheckForChangedDepUsers;
},
useDeclarationFileSignature: rollupShareObject?.projectConfig?.useDeclarationFileSignature,
clearFileCache: function () {
fileCache.clear();
},
isSourceOrExternalCode: rollupShareObject?.projectConfig?.isSourceOrExternalCode,
};
ts.PerformanceDotting?.setPerformanceSwitch(projectConfig?.perf);
if (process.env.watchMode === 'true') {
const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ETS_CHECKER_CREATE_LANGUAGE_SERVICE);
const tsLanguageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
MemoryMonitor.stopRecordStage(recordInfo);
return tsLanguageService;
}
const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ETS_CHECKER_CREATE_LANGUAGE_SERVICE);
const tsLanguageService = getOrCreateLanguageService(servicesHost, rootFileNames, parentEvent, rollupShareObject);
MemoryMonitor.stopRecordStage(recordInfo);
return tsLanguageService;
}
export let targetESVersionChanged: boolean = false;
function getOrCreateLanguageService(servicesHost: ts.LanguageServiceHost, rootFileNames: string[],
parentEvent: CompileEvent, rollupShareObject?: any): ts.LanguageService {
let cacheKey: string = 'service';
let cache: LanguageServiceCache | undefined = getRollupCache(rollupShareObject, projectConfig, cacheKey);
let service: ts.LanguageService | undefined = cache?.service;
const currentHash: string | undefined = rollupShareObject?.projectConfig?.pkgJsonFileHash;
const currentTargetESVersion: ts.ScriptTarget = compilerOptions.target;
const currentTypes: string[] | undefined = compilerOptions.types;
const currentMaxFlowDepth: number | undefined = compilerOptions.maxFlowDepth;
const currentAutoLazyImport: boolean = rollupShareObject?.projectConfig?.autoLazyImport === true;
const currentAutoLazyFilterInclude: string[] | undefined = Array.isArray(rollupShareObject?.projectConfig?.autoLazyFilter?.include) ?
rollupShareObject.projectConfig.autoLazyFilter.include : undefined;
const currentAutoLazyFilterExclude: string[] | undefined = Array.isArray(rollupShareObject?.projectConfig?.autoLazyFilter?.exclude) ?
rollupShareObject.projectConfig.autoLazyFilter.exclude : undefined;
const currentUseDeclarationFileSignature: boolean | undefined = rollupShareObject?.projectConfig?.useDeclarationFileSignature;
const currentStrictCheckerOnly: boolean | undefined = rollupShareObject?.projectConfig?.strictCheckerOnly;
const currentTsImportSoCheck: boolean | undefined = rollupShareObject?.projectConfig?.tsImportSoCheck;
const lastHash: string | undefined = cache?.pkgJsonFileHash;
const lastTargetESVersion: ts.ScriptTarget | undefined = cache?.targetESVersion;
const lastTypes: string[] | undefined = cache?.types;
const lastMaxFlowDepth: number | undefined = cache?.maxFlowDepth;
const lastUseDeclarationFileSignature: boolean | undefined = cache?.useDeclarationFileSignature;
const lastStrictCheckerOnly: boolean | undefined = cache?.strictCheckerOnly;
const lastTsImportSoCheck: boolean | undefined = cache?.tsImportSoCheck;
const hashDiffers: boolean | undefined = currentHash && lastHash && currentHash !== lastHash;
const shouldRebuildForDepDiffers: boolean | undefined = reuseLanguageServiceForDepChange ?
(hashDiffers && !rollupShareObject?.depInfo?.enableIncre) : hashDiffers;
const targetESVersionDiffers: boolean | undefined = lastTargetESVersion && currentTargetESVersion && lastTargetESVersion !== currentTargetESVersion;
const typesDiff: boolean | undefined = lastTypes && currentTypes && !areEqualArrays(lastTypes, currentTypes);
const maxFlowDepthDiffers: boolean | undefined = lastMaxFlowDepth && currentMaxFlowDepth && lastMaxFlowDepth !== currentMaxFlowDepth;
const useTsHarDiff: boolean = projectConfig.compileHar && !projectConfig.byteCodeHar && checkValueDiff(cache?.useTsHar, projectConfig.useTsHar);
const tsImportSendableDiff: boolean = checkValueDiff(cache?.preTsImportSendable, tsImportSendable);
const skipOhModulesLintDiff: boolean = checkValueDiff(cache?.preSkipOhModulesLint, skipOhModulesLint);
const enableStrictCheckOHModuleDiff: boolean = checkValueDiff(cache?.preEnableStrictCheckOHModule, enableStrictCheckOHModule);
const disableStrictCheckPathsDiff: boolean = !areEqualArrays(cache?.preDisableStrictCheckPaths, disableStrictCheckPaths);
const disableSendableCheckRulesDiff: boolean = checkRulesDiff(cache?.preDisableSendableCheckRules, disableSendableCheckRules);
const mixCompileDiff: boolean = checkValueDiff(cache?.preMixCompile, mixCompile);
const autoLazyImportDiff: boolean = checkValueDiff(cache?.autoLazyImport, currentAutoLazyImport);
const autoLazyFilterIncludeDiff: boolean = !areEqualArrays(cache?.autoLazyFilterInclude, currentAutoLazyFilterInclude);
const autoLazyFilterExcludeDiff: boolean = !areEqualArrays(cache?.autoLazyFilterExclude, currentAutoLazyFilterExclude);
const autoLazyFilterDiff: boolean = autoLazyFilterIncludeDiff || autoLazyFilterExcludeDiff;
const useDeclarationFileSignatureDiff: boolean = checkValueDiff(lastUseDeclarationFileSignature, currentUseDeclarationFileSignature);
const strictCheckerOnlyDiff: boolean = checkValueDiff(lastStrictCheckerOnly, currentStrictCheckerOnly);
const tsImportSoCheckDiff: boolean = checkValueDiff(lastTsImportSoCheck, currentTsImportSoCheck);
const onlyDeleteBuildInfoCache: boolean | undefined = tsImportSendableDiff || maxFlowDepthDiffers || skipOhModulesLintDiff ||
enableStrictCheckOHModuleDiff || disableStrictCheckPathsDiff || disableSendableCheckRulesDiff ||
mixCompileDiff || typesDiff || autoLazyImportDiff || autoLazyFilterDiff ||
useDeclarationFileSignatureDiff || strictCheckerOnlyDiff || tsImportSoCheckDiff;
const shouldInvalidCache: boolean | undefined = targetESVersionDiffers || useTsHarDiff;
const shouldRebuild: boolean | undefined = shouldRebuildForDepDiffers || shouldInvalidCache || onlyDeleteBuildInfoCache;
if (reuseLanguageServiceForDepChange && hashDiffers && rollupShareObject?.depInfo?.enableIncre) {
needReCheckForChangedDepUsers = true;
}
if (!service || shouldRebuild) {
const eventShouldRebuild = createAndStartEvent(parentEvent,
'!service: ' + !service + '\n' +
'the reason of shouldRebuild: ' + 'shouldRebuild: ' + shouldRebuild + ';' + '\n' +
'shouldRebuildForDepDiffers: ' + shouldRebuildForDepDiffers + ';' + '\n' +
'shouldInvalidCache: ' + shouldInvalidCache + ';' + '\n' +
'targetESVersionDiffers: ' + targetESVersionDiffers + ';' + 'useTsHarDiff: ' + useTsHarDiff + ';' + '\n' +
'onlyDeleteBuildInfoCache: ' + onlyDeleteBuildInfoCache + ';' + '\n' +
'tsImportSendableDiff: ' + tsImportSendableDiff + ';' + 'maxFlowDepthDiffers: ' + maxFlowDepthDiffers + ';' +
'skipOhModulesLintDiff: ' + skipOhModulesLintDiff + ';' +
'enableStrictCheckOHModuleDiff: ' + enableStrictCheckOHModuleDiff + ';' +
'disableStrictCheckPathsDiff: ' + disableStrictCheckPathsDiff + ';' +
'mixCompileDiff: ' + mixCompileDiff + ';' + 'typesDiff: ' + typesDiff + ';'
);
rebuildProgram(shouldInvalidCache, onlyDeleteBuildInfoCache);
service = ts.createLanguageService(servicesHost, shareDocumentRegistryCache ?
getOrCreateDocumentRegistryCache(rollupShareObject) : ts.createDocumentRegistry());
stopEvent(eventShouldRebuild);
} else {
const updateRootFileNames = [...rootFileNames, ...readDeaclareFiles()];
const eventUpdateRootFileNames = createAndStartEvent(parentEvent, 'eventUpdateRootFileNumber: ' + updateRootFileNames.length);
service.updateRootFiles(updateRootFileNames);
stopEvent(eventUpdateRootFileNames);
}
const newCache: LanguageServiceCache = {
service: service,
pkgJsonFileHash: currentHash,
targetESVersion: currentTargetESVersion,
types: currentTypes,
maxFlowDepth: currentMaxFlowDepth,
useTsHar: projectConfig.useTsHar,
preTsImportSendable: tsImportSendable,
preSkipOhModulesLint: skipOhModulesLint,
preEnableStrictCheckOHModule: enableStrictCheckOHModule,
preDisableStrictCheckPaths: disableStrictCheckPaths ? [...disableStrictCheckPaths] : undefined,
preDisableSendableCheckRules: disableSendableCheckRules,
preMixCompile: mixCompile,
useDeclarationFileSignature: currentUseDeclarationFileSignature,
strictCheckerOnly: currentStrictCheckerOnly,
tsImportSoCheck: currentTsImportSoCheck,
autoLazyImport: currentAutoLazyImport,
autoLazyFilterInclude: currentAutoLazyFilterInclude ? [...currentAutoLazyFilterInclude] : undefined,
autoLazyFilterExclude: currentAutoLazyFilterExclude ? [...currentAutoLazyFilterExclude] : undefined
};
setRollupCache(rollupShareObject, projectConfig, cacheKey, newCache);
return service;
}
const shareDocumentRegistryCache: boolean = true;
* Boost languageService's sourcefiles creation procedure & lower memory consumption by caching DocumentRegistry
* into Rollup CommonCache(which will make every single file has only one sourcefile being created during building) to
* share common sourcefiles with multi-modules compiling tasks on the same worker process.
* Once compiling gets done, hvigor will clear the Rollup CommonCache so that tsc's original sourcefile updation process
* will refresh the changed files sourcefiles to prevent the new program getting the old un-refreshed sourcefiles in
* inc-compilation.
*/
function getOrCreateDocumentRegistryCache(rollupShareObject?: any): ts.DocumentRegistry {
const commonCacheKey: string = 'documentRegistry';
let documentRegistryCache: object | undefined = getRollupCommonCache(rollupShareObject, commonCacheKey);
if (documentRegistryCache) {
return <ts.DocumentRegistry>documentRegistryCache;
} else {
documentRegistryCache = ts.createDocumentRegistry();
setRollupCommonCache(rollupShareObject, commonCacheKey, documentRegistryCache);
return <ts.DocumentRegistry>documentRegistryCache;
}
}
* compare cache value and current value, check if they are different
* @param cacheValue cache value
* @param currentValue current value
* @returns true if they are different, false otherwise
*/
function checkValueDiff(cacheValue: boolean | undefined, currentValue: boolean): boolean {
return !(cacheValue === undefined && !currentValue) && cacheValue !== currentValue;
}
export function areEqualArrays(lastArray: string[] | undefined, currentArray: string[] | undefined): boolean {
if (!lastArray || !currentArray) {
return lastArray === currentArray;
}
const currentSet: Set<string> = new Set(currentArray!);
const lastSet: Set<string> = new Set(lastArray!);
if (lastSet.size !== currentSet.size) {
return false;
}
for (const item of lastSet) {
if (!currentSet.has(item)) {
return false;
}
}
return true;
}
function normalizeDisableStrictCheckPaths(disableStrictCheckPathsToHandle: unknown): string[] | undefined {
if (!Array.isArray(disableStrictCheckPathsToHandle)) {
return undefined;
}
const normalizedDisableStrictCheckPaths: string[] = [];
for (const disableStrictCheckPath of disableStrictCheckPathsToHandle) {
if (typeof disableStrictCheckPath !== 'string' || !disableStrictCheckPath) {
continue;
}
if (!normalizedDisableStrictCheckPaths.includes(disableStrictCheckPath)) {
normalizedDisableStrictCheckPaths.push(disableStrictCheckPath);
}
}
return normalizedDisableStrictCheckPaths;
}
* compare cache value and current value, check if they are different
* @param lastRules cache rules
* @param currentRules current rules
* @returns true if they are different, false otherwise
*/
function checkRulesDiff(lastRules: string[] | undefined, currentRules: string[] | undefined): boolean {
const hasSendableClassDecoratorCacheRule: boolean = hasSendableClassDecoratorRule(lastRules);
const hasSendableClassDecoratorCurrentRule: boolean = hasSendableClassDecoratorRule(currentRules);
return checkValueDiff(hasSendableClassDecoratorCacheRule, hasSendableClassDecoratorCurrentRule);
}
* check if sendableCheckRules contains SENDABLE_CLASS_DECORATOR_RULE
* @param sendableCheckRules sendable check rules
* @returns true if sendableCheckRules contains SENDABLE_CLASS_DECORATOR_RULE, false otherwise
*/
function hasSendableClassDecoratorRule(sendableCheckRules: string[] | undefined): boolean {
if (!sendableCheckRules) {
return false;
}
return sendableCheckRules?.includes(SENDABLE_CLASS_DECORATOR_RULE);
}
function rebuildProgram(shouldInvalidCache: boolean | undefined, onlyDeleteBuildInfoCache: boolean | undefined): void {
if (shouldInvalidCache) {
deleteBuildInfoCache(compilerOptions.tsBuildInfoFile);
targetESVersionChanged = true;
} else if (onlyDeleteBuildInfoCache) {
deleteBuildInfoCache(compilerOptions.tsBuildInfoFile);
}
}
function deleteBuildInfoCache(tsBuildInfoFilePath: string): void {
const tsBuildInfoLinterFilePath: string = tsBuildInfoFilePath + '.linter';
deleteFile(tsBuildInfoFilePath);
deleteFile(tsBuildInfoLinterFilePath);
}
function deleteFile(filePath: string): void {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
}
interface CacheFileName {
mtimeMs: number,
children: string[],
parent: string[],
error: boolean,
errorCodes?: number[]
}
interface NeedUpdateFlag {
flag: boolean;
}
interface CheckerResult {
count: number
}
interface WarnCheckerResult {
count: number
}
interface WholeCache {
runtimeOS: string,
sdkInfo: string,
fileList: Cache
}
type Cache = Record<string, CacheFileName>;
export let cache: Cache = {};
export const hotReloadSupportFiles: Set<string> = new Set();
export const shouldResolvedFiles: Set<string> = new Set();
export const appComponentCollection: Map<string, Set<string>> = new Map();
const allResolvedModules: Set<string> = new Set();
export const allSourceFilePaths: Set<string> = new Set();
export const allModuleIds: Set<string> = new Set();
export let props: Set<string> = new Set();
export let fastBuildLogger = null;
export const checkerResult: CheckerResult = { count: 0 };
export const warnCheckerResult: WarnCheckerResult = { count: 0 };
export let languageService: ts.LanguageService = null;
let tsImportSendable: boolean = false;
let skipOhModulesLint: boolean = false;
let enableStrictCheckOHModule: boolean = false;
let disableStrictCheckPaths: string[] | undefined;
let disableSendableCheckRules: string[] = [];
let mixCompile: boolean = false;
export let maxMemoryInServiceChecker: number = 0;
export function serviceChecker(rootFileNames: string[], newLogger: Object = null, resolveModulePaths: string[] = null,
parentEvent?: CompileEvent, rollupShareObject?: Object): void {
fastBuildLogger = newLogger;
let cacheFile: string = null;
tsImportSendable = rollupShareObject?.projectConfig.tsImportSendable;
skipOhModulesLint = rollupShareObject?.projectConfig.skipOhModulesLint;
enableStrictCheckOHModule = rollupShareObject?.projectConfig.enableStrictCheckOHModule;
disableStrictCheckPaths = normalizeDisableStrictCheckPaths(rollupShareObject?.projectConfig?.strictMode?.disableStrictCheckPaths);
disableSendableCheckRules = rollupShareObject?.projectConfig.disableSendableCheckRules || [];
mixCompile = rollupShareObject?.projectConfig.mixCompile;
if (projectConfig.xtsMode || process.env.watchMode === 'true') {
if (projectConfig.hotReload) {
rootFileNames.forEach(fileName => {
hotReloadSupportFiles.add(fileName);
});
}
const recordInfo = MemoryMonitor.recordStage(MemoryDefine.CREATE_LANGUAGE_SERVICE);
languageService = createLanguageService(rootFileNames, resolveModulePaths, parentEvent);
MemoryMonitor.stopRecordStage(recordInfo);
props = languageService.getProps();
} else {
cacheFile = rollupShareObject?.projectConfig?.widgetCompile === 'true' ? path.resolve(projectConfig.cachePath, '../.ts_checker_cache_widget') :
path.resolve(projectConfig.cachePath, '../.ts_checker_cache');
const [isJsonObject, cacheJsonObject]: [boolean, WholeCache | undefined] = isJsonString(cacheFile);
const wholeCache: WholeCache = isJsonObject ? cacheJsonObject : { 'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {} };
if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) {
cache = wholeCache.fileList;
} else {
cache = {};
}
const recordInfo = MemoryMonitor.recordStage(MemoryDefine.CREATE_LANGUAGE_SERVICE);
languageService = createLanguageService(rootFileNames, resolveModulePaths, parentEvent, rollupShareObject);
MemoryMonitor.stopRecordStage(recordInfo);
}
const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance();
timePrinterInstance.setArkTSTimePrintSwitch(false);
timePrinterInstance.appendTime(ts.TimePhase.START);
ts.PerformanceDotting?.startAdvanced('createProgram');
const recordInfo = MemoryMonitor.recordStage(MemoryDefine.GET_BUILDER_PROGRAM);
globalProgram.builderProgram = languageService.getBuilderProgram( true);
globalProgram.program = globalProgram.builderProgram.getProgram();
traverseProgramSourceFiles(languageService.getProps());
props = languageService.getProps();
timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM);
MemoryMonitor.stopRecordStage(recordInfo);
ts.PerformanceDotting?.stopAdvanced('createProgram');
collectAllFiles(globalProgram.program, undefined, undefined, rollupShareObject);
collectFileToIgnoreDiagnostics(rootFileNames);
ts.PerformanceDotting?.startAdvanced('runArkTSLinterTime');
const runArkTSLinterRecordInfo = MemoryMonitor.recordStage(MemoryDefine.RUN_ARK_TS_LINTER);
const errorCodeLogger: Object | undefined = !!rollupShareObject?.getHvigorConsoleLogger ?
rollupShareObject?.getHvigorConsoleLogger(LINTER_SUBSYSTEM_CODE) : undefined;
runArkTSLinter(errorCodeLogger, parentEvent);
MemoryMonitor.stopRecordStage(runArkTSLinterRecordInfo);
ts.PerformanceDotting?.stopAdvanced('runArkTSLinterTime');
if (process.env.watchMode !== 'true') {
const processBuildHaprrecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP);
processBuildHap(cacheFile, rootFileNames, parentEvent, rollupShareObject);
MemoryMonitor.stopRecordStage(processBuildHaprrecordInfo);
}
if (projectConfig?.projectArkOption?.bundle?.bundledDeclare) {
if ((projectConfig.byteCodeHar || projectConfig.compileShared) && projectConfig.declaredFilesPath) {
performDeclarationMerging(projectConfig.declaredFilesPath, true);
} else if (projectConfig.cachePath && projectConfig.moduleName) {
performDeclarationMerging(path.join(projectConfig.cachePath, projectConfig.moduleName), false);
} else {
logger.debug('Missing required paths, skip declaration merging');
}
}
maxMemoryInServiceChecker = process.memoryUsage().heapUsed;
let shouldReleaseTypeChecker: boolean = rollupShareObject?.projectConfig?.executionMode !== 'performance' && globalProgram.program &&
process.env.watchMode !== 'true' && !projectConfig.isPreview && !projectConfig.hotReload && !projectConfig.coldReload;
if (shouldReleaseTypeChecker) {
globalProgram.program.releaseTypeChecker();
const allowGC: boolean = global && global.gc && typeof global.gc === 'function';
if (allowGC) {
global.gc();
}
}
}
export function traverseProgramSourceFiles(props: Set<string>): void {
globalProgram.program.getSourceFiles().forEach((sourceFile: ts.SourceFile) => {
checkUISyntax(sourceFile, sourceFile.fileName, [], props);
});
}
function isJsonString(cacheFile: string): [boolean, WholeCache | undefined] {
if (fs.existsSync(cacheFile)) {
try {
return [true, JSON.parse(fs.readFileSync(cacheFile).toString())];
} catch (e) {
return [false, undefined];
}
} else {
return [false, undefined];
}
}
export function collectAllFiles(program?: ts.Program, rollupFileList?: IterableIterator<string>,
rollupObject?: Object, rollupShareObject: Object = null): void {
if (program) {
collectTscFiles(program, rollupShareObject);
return;
}
mergeRollUpFiles(rollupFileList, rollupObject);
}
export function collectTscFiles(program: ts.Program, rollupShareObject: Object = null): void {
const programAllFiles: readonly ts.SourceFile[] = program.getSourceFiles();
let projectRootPath: string = projectConfig.projectRootPath;
if (!projectRootPath) {
return;
}
projectRootPath = toUnixPath(projectRootPath);
const isMacOrWin = isWindows() || isMac();
const recordInfo = MemoryMonitor.recordStage(MemoryDefine.COLLECT_TSC_FILES_ALL_RESOLVED_MODULES);
programAllFiles.forEach(sourceFile => {
const fileName = toUnixPath(sourceFile.fileName);
sourceFileDependencies.set(fileName, sourceFile.resolvedModules);
if (!(fileName.startsWith(projectRootPath + '/') || isOtherProjectResolvedModulesFilePaths(rollupShareObject, fileName))) {
return;
}
allSourceFilePaths.add(fileName);
allModuleIds.add(sourceFile.fileName);
if (filesBuildInfo.size > 0 &&
Reflect.get(sourceFile, 'version') !== filesBuildInfo.get(isMacOrWin ? tryToLowerCasePath(fileName) : fileName)) {
allResolvedModules.add(fileName);
}
});
MemoryMonitor.stopRecordStage(recordInfo);
}
function isOtherProjectResolvedModulesFilePaths(rollupShareObject: Object, fileName: string): boolean {
if (!!rollupShareObject && rollupShareObject.projectConfig && !!rollupShareObject.projectConfig.rootPathSet) {
const rootPathSet: string[] | Set<string> = rollupShareObject.projectConfig.rootPathSet;
if (Array.isArray(rootPathSet)) {
for (let i = 0; i < rootPathSet.length; i++) {
const pathNormalization: string = toUnixPath(rootPathSet[i]) + '/';
if (fileName.startsWith(pathNormalization)) {
return true;
}
}
} else {
return false;
}
}
return false;
}
export function mergeRollUpFiles(rollupFileList: IterableIterator<string>, rollupObject: Object): void {
const recordInfo = MemoryMonitor.recordStage(MemoryDefine.MERGE_ROLL_UP_FILES_LOCAL_PACKAGE_SET);
for (const moduleId of rollupFileList) {
if (moduleId.indexOf('\x00') < 0 && fileInfoCache.fileExists(moduleId)) {
allSourceFilePaths.add(toUnixPath(moduleId));
allModuleIds.add(moduleId);
addLocalPackageSet(moduleId, rollupObject);
}
}
MemoryMonitor.stopRecordStage(recordInfo);
}
export function addLocalPackageSet(moduleId: string, rollupObject: Object): void {
const moduleInfo: Object = rollupObject.getModuleInfo(moduleId);
const metaInfo: Object = moduleInfo.meta;
if (metaInfo.isLocalDependency) {
if (projectConfig.useNormalizedOHMUrl && metaInfo.pkgName) {
localPackageSet.add(metaInfo.pkgName);
}
if (!projectConfig.useNormalizedOHMUrl && metaInfo.moduleName) {
localPackageSet.add(metaInfo.moduleName);
}
}
}
export function emitBuildInfo(): void {
globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile);
}
function processHarSourceFiles(rootFileNames: string[]): void {
let emit: string | undefined = undefined;
let writeFile = (fileName: string, text: string, writeByteOrderMark: boolean): void => {
emit = text;
};
[...allResolvedModules, ...rootFileNames].forEach(moduleFile => {
if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar) ||
projectConfig?.projectArkOption?.bundle?.bundledDeclare) {
try {
if ((/\.d\.e?ts$/).test(moduleFile)) {
generateSourceFilesInHar(moduleFile, fs.readFileSync(moduleFile, 'utf-8'), path.extname(moduleFile),
projectConfig, projectConfig.modulePathMap);
} else if ((/\.e?ts$/).test(moduleFile) && (!toUnixPath(moduleFile).includes('/oh_modules/') ||
projectConfig?.projectArkOption?.bundle?.bundledDeclare)) {
emit = undefined;
let sourcefile = globalProgram.program.getSourceFile(moduleFile);
if (sourcefile) {
globalProgram.program.emit(sourcefile, writeFile, undefined, true, undefined, true, true);
}
if (emit) {
generateSourceFilesInHar(moduleFile, emit, '.d' + path.extname(moduleFile), projectConfig, projectConfig.modulePathMap);
}
}
} catch (err) {}
}
});
}
function processBuildHap(cacheFile: string, rootFileNames: string[], parentEvent: CompileEvent,
rollupShareObject: Object): void {
ts.PerformanceDotting?.startAdvanced('diagnostic');
const semanticRecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP_GET_SEMANTIC_DIAGNOSTICS);
const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram
.getSyntacticDiagnostics()
.concat(globalProgram.builderProgram.getSemanticDiagnostics());
MemoryMonitor.stopRecordStage(semanticRecordInfo);
ts.PerformanceDotting?.stopAdvanced('diagnostic');
const emitBuildRecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP_EMIT_BUILD_INFO);
emitBuildInfo();
let errorCodeLogger: Object | undefined = rollupShareObject?.getHvigorConsoleLogger ?
rollupShareObject?.getHvigorConsoleLogger(TSC_SYSTEM_CODE) : undefined;
allDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger);
});
MemoryMonitor.stopRecordStage(emitBuildRecordInfo);
if (!projectConfig.xtsMode) {
fse.ensureDirSync(projectConfig.cachePath);
fs.writeFileSync(cacheFile, JSON.stringify({
'runtimeOS': projectConfig.runtimeOS,
'sdkInfo': projectConfig.sdkInfo,
'fileList': cache
}, null, 2));
}
if (projectConfig.compileHar || projectConfig.compileShared) {
processHarSourceFiles(rootFileNames);
printDeclarationDiagnostics(errorCodeLogger);
}
}
function printDeclarationDiagnostics(errorCodeLogger?: Object | undefined): void {
globalProgram.program.getSourceFiles().forEach((sourceFile: ts.SourceFile) => {
if ((/\.d\.e?ts$/).test(sourceFile.fileName) || (/\.js$/).test(sourceFile.fileName)) {
return;
}
if (toUnixPath(sourceFile.fileName).includes('/oh_modules/')) {
return;
}
globalProgram.builderProgram.getDeclarationDiagnostics(sourceFile, undefined, true).forEach((diagnostic: ts.Diagnostic) => {
printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger);
});
});
}
function containFormError(message: string): boolean {
if (/can't support form application./.test(message)) {
return true;
}
return false;
}
function matchJSGrammarErrorMessage(message: string): boolean {
const regex = /'.*?' can't support atomicservice application\./;
return regex.test(message);
}
let fileToIgnoreDiagnostics: Set<string> | undefined = undefined;
function collectFileToThrowDiagnostics(file: string, fileToThrowDiagnostics: Set<string>): void {
const normalizedFilePath: string = path.resolve(file);
const unixFilePath: string = toUnixPath(file);
if (fileToThrowDiagnostics.has(unixFilePath)) {
return;
}
fileToThrowDiagnostics.add(unixFilePath);
if ((/\.(c|m)?js$/).test(file) ||
!cache[normalizedFilePath] || cache[normalizedFilePath].children.length === 0) {
return;
}
cache[normalizedFilePath].children.forEach(file => {
collectFileToThrowDiagnostics(file, fileToThrowDiagnostics);
});
}
export function collectFileToIgnoreDiagnostics(rootFileNames: string[]): void {
if (getArkTSLinterMode() === ArkTSLinterMode.NOT_USE) {
return;
}
if (process.env.watchMode === 'true') {
for (let [file, resolvedModules] of resolvedModulesCache) {
createOrUpdateCache(resolvedModules, file);
}
}
let fileToThrowDiagnostics: Set<string> = new Set<string>();
rootFileNames.forEach(file => {
if (!(/\.(c|m)?js$/).test(file)) {
collectFileToThrowDiagnostics(file, fileToThrowDiagnostics);
}
});
let resolvedTypeReferenceDirectivesFiles: Set<string> = new Set<string>();
globalProgram.program.getResolvedTypeReferenceDirectives().forEach(
(elem: ts.ResolvedTypeReferenceDirective | undefined) => {
elem && elem.resolvedFileName && resolvedTypeReferenceDirectivesFiles.add(elem.resolvedFileName);
});
const ignoreDiagnosticsRecordInfo = MemoryMonitor.recordStage(MemoryDefine.FILE_TO_IGNORE_DIAGNOSTICS);
fileToIgnoreDiagnostics = new Set<string>();
globalProgram.program.getSourceFiles().forEach(sourceFile => {
sourceFile.fileName &&
(!isInSDK(sourceFile.fileName) || (/\.(c|m)?js$/).test(sourceFile.fileName)) &&
!resolvedTypeReferenceDirectivesFiles.has(sourceFile.fileName) &&
fileToIgnoreDiagnostics.add(toUnixPath(sourceFile.fileName));
});
fileToThrowDiagnostics.forEach(file => {
fileToIgnoreDiagnostics.delete(file);
});
const atomicJsFiles = new Set<string>();
for (const file of fileToIgnoreDiagnostics) {
if (isAtomicJsFile(file)) {
atomicJsFiles.add(file);
}
}
atomicJsFiles.forEach(file => fileToIgnoreDiagnostics.delete(file));
MemoryMonitor.stopRecordStage(ignoreDiagnosticsRecordInfo);
}
interface MessageCollection {
positionMessage: string,
message: string,
logMessage: string
}
export function printDiagnostic(diagnostic: ts.Diagnostic, flag?: ErrorCodeModule, errorCodeLogger?: Object | undefined): void {
if (projectConfig.ignoreWarning) {
return;
}
if (fileToIgnoreDiagnostics && diagnostic.file && diagnostic.file.fileName &&
fileToIgnoreDiagnostics.has(toUnixPath(diagnostic.file.fileName))) {
return;
}
if (fileToIgnoreDiagnostics && diagnostic.file && diagnostic.file.fileName &&
isAtomicJsFile(diagnostic.file.fileName) && !matchJSGrammarErrorMessage(diagnostic.messageText)) {
return;
}
const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (validateError(message)) {
if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
updateErrorFileCache(diagnostic);
}
if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) {
return;
}
if (validateNewExtend(message)) {
diagnostic.category = ts.DiagnosticCategory.warning;
}
const logPrefix: string = diagnostic.category === ts.DiagnosticCategory.Error ? 'ERROR' : 'WARN';
const etsCheckerLogger = fastBuildLogger || logger;
let logMessage: string;
if (logPrefix === 'ERROR') {
checkerResult.count += 1;
} else {
warnCheckerResult.count += 1;
}
let positionMessage: string = '';
if (diagnostic.file) {
const { line, character }: ts.LineAndCharacter =
diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
positionMessage = `File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}`;
logMessage = `ArkTS:${logPrefix} ${positionMessage}\n ${message}\n`;
} else {
logMessage = `ArkTS:${logPrefix}: ${message}`;
}
if (errorCodeLogger) {
const msgCollection: MessageCollection = { positionMessage, message, logMessage };
printErrorCode(diagnostic, etsCheckerLogger, msgCollection, errorCodeLogger, flag);
} else {
if (diagnostic.category === ts.DiagnosticCategory.Error) {
etsCheckerLogger.error('\u001b[31m' + logMessage);
} else {
etsCheckerLogger.warn('\u001b[33m' + logMessage);
}
}
}
}
function isAtomicJsFile(fileName: string): boolean {
const isJsOrModuleFile: boolean = /\.(c|m)?js$/.test(fileName);
if (!isJsOrModuleFile) {
return false;
}
const isAtomicServiceBundle: boolean = projectConfig.bundleType === ATOMICSERVICE_BUNDLE_TYPE;
if (!isAtomicServiceBundle) {
return false;
}
const isNonSdkFile: boolean = !isInSDK(fileName);
if (!isNonSdkFile) {
return false;
}
const normalizedFileName: string = fileName.replace(/\\/g, '/');
const normalizedProjectPath: string = projectConfig.projectPath.replace(/\\/g, '/');
const isInProjectPath: boolean = normalizedFileName.startsWith(normalizedProjectPath);
if (!isInProjectPath) {
return false;
}
return true;
}
function printErrorCode(diagnostic: ts.Diagnostic, etsCheckerLogger: Object,
msgCollection: MessageCollection, errorCodeLogger: Object, flag: ErrorCodeModule | undefined): void {
const { positionMessage, message, logMessage } = msgCollection;
if (diagnostic.category !== ts.DiagnosticCategory.Error) {
etsCheckerLogger.warn('\u001b[33m' + logMessage);
return;
}
if (flag === ErrorCodeModule.TSC &&
validateUseErrorCodeLogger(ErrorCodeModule.TSC, diagnostic.code)) {
const errorCode = ts.getErrorCode(diagnostic);
errorCodeLogger.printError(errorCode);
return;
}
if (flag === ErrorCodeModule.LINTER || (flag === ErrorCodeModule.TSC &&
validateUseErrorCodeLogger(ErrorCodeModule.LINTER, diagnostic.code))) {
const linterErrorInfo: HvigorErrorInfo = transfromErrorCode(diagnostic.code, positionMessage, message);
errorCodeLogger.printError(linterErrorInfo);
return;
}
if (flag === ErrorCodeModule.UI || (flag === ErrorCodeModule.TSC &&
validateUseErrorCodeLogger(ErrorCodeModule.UI, diagnostic.code))) {
let uiErrorInfo: HvigorErrorInfo | SdkHvigorErrorInfo | undefined;
if (DIAGNOSTIC_SDK_CODE_MAP.get(String(diagnostic.code))) {
uiErrorInfo = sdkBuildErrorInfoFromDiagnostic(positionMessage, message);
} else {
uiErrorInfo = buildErrorInfoFromDiagnostic(diagnostic.code, positionMessage, message);
}
if (!uiErrorInfo) {
etsCheckerLogger.error('\u001b[31m' + logMessage);
} else {
errorCodeLogger.printError(uiErrorInfo);
}
return;
}
etsCheckerLogger.error('\u001b[31m' + logMessage);
}
function validateUseErrorCodeLogger(flag: ErrorCodeModule, code: number): boolean {
if (!ts.getErrorCodeArea || !ts.getErrorCode) {
return false;
}
if (flag === ErrorCodeModule.TSC) {
return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.TSC;
} else if (flag === ErrorCodeModule.LINTER) {
return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.LINTER;
} else if (flag === ErrorCodeModule.UI) {
return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.UI;
}
return false;
}
function validateError(message: string): boolean {
const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/;
const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/;
if (matchMessage(message, props, propInfoReg) ||
matchMessage(message, props, stateInfoReg)) {
return false;
}
return true;
}
* validate whether ets diagnostic has collected the error about new Extend function
* @param {string} message
* @return {*} {boolean}
*/
function validateNewExtend(message: string): boolean {
const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/;
if (matchMessage(message, newExtendCollection, stateInfoReg)) {
return true;
}
return false;
}
function matchMessage(message: string, nameArr: any, reg: RegExp): boolean {
if (reg.test(message)) {
const match: string[] = message.match(reg);
if (match[1] && nameArr.has(match[1])) {
return true;
}
}
return false;
}
function updateErrorFileCache(diagnostic: ts.Diagnostic): void {
if (!diagnostic.file) {
return;
}
let cacheInfo: CacheFileName = cache[path.resolve(diagnostic.file.fileName)];
if (cacheInfo) {
cacheInfo.error = true;
if (!cacheInfo.errorCodes) {
cacheInfo.errorCodes = [];
}
cacheInfo.errorCodes.includes(diagnostic.code) || cacheInfo.errorCodes.push(diagnostic.code);
}
}
function filterInput(rootFileNames: string[]): string[] {
return rootFileNames.filter((file: string) => {
const needUpdate: NeedUpdateFlag = { flag: false };
const alreadyCheckedFiles: Set<string> = new Set();
checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles);
if (!needUpdate.flag) {
storedFileInfo.changeFiles.push(path.resolve(file));
}
return needUpdate.flag;
});
}
function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set<string>): void {
if (alreadyCheckedFiles.has(file)) {
return;
} else {
alreadyCheckedFiles.add(file);
}
if (needUpdate.flag) {
return;
}
const value: CacheFileName = cache[file];
const mtimeMs: number = fileInfoCache.getMtimeMs(file);
if (value) {
if (value.error || value.mtimeMs !== mtimeMs) {
needUpdate.flag = true;
return;
}
for (let i = 0; i < value.children.length; ++i) {
if (fs.existsSync(value.children[i])) {
checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles);
} else {
needUpdate.flag = true;
}
}
} else {
cache[file] = { mtimeMs, children: [], parent: [], error: false };
needUpdate.flag = true;
}
}
const fileExistsCache: Map<string, boolean> = new Map<string, boolean>();
const dirExistsCache: Map<string, boolean> = new Map<string, boolean>();
const getCanonicalFileName = (fileName: string): string =>
ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
let moduleResolutionCache: ts.ModuleResolutionCache | undefined = undefined;
const moduleResolutionHost: ts.ModuleResolutionHost = {
fileExists: (fileName: string): boolean => {
let exists = fileExistsCache.get(fileName);
if (exists === undefined) {
exists = ts.sys.fileExists(fileName);
fileExistsCache.set(fileName, exists);
}
return exists;
},
directoryExists: (directoryName: string): boolean => {
let exists = dirExistsCache.get(directoryName);
if (exists === undefined) {
exists = ts.sys.directoryExists(directoryName);
dirExistsCache.set(directoryName, exists);
}
return exists;
},
readFile(fileName: string): string | undefined {
return ts.sys.readFile(fileName);
},
realpath(path: string): string {
return ts.sys.realpath(path);
},
trace(s: string): void {
console.info(s);
}
};
export const moduleResolutionHostTest = moduleResolutionHost;
export function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | ts.FileReference[]): ts.ResolvedTypeReferenceDirective[] {
if (typeDirectiveNames.length === 0) {
return [];
}
const etsCheckerLogger = fastBuildLogger || logger;
const resolvedTypeReferenceCache: ts.ResolvedTypeReferenceDirective[] = [];
const cache: Map<string, ts.ResolvedTypeReferenceDirective> = new Map<string, ts.ResolvedTypeReferenceDirective>();
const containingFile: string = path.join(projectConfig.modulePath, 'build-profile.json5');
for (const entry of typeDirectiveNames) {
const typeName = isString(entry) ? entry : entry.fileName.toLowerCase();
if (!cache.has(typeName)) {
const resolvedFile = ts.resolveTypeReferenceDirective(typeName, containingFile, compilerOptions, moduleResolutionHost);
if (!resolvedFile || !resolvedFile.resolvedTypeReferenceDirective) {
etsCheckerLogger.warn('\u001b[33m', `ArkTS:Cannot find type definition file for: ${typeName}\n`);
}
const result: ts.ResolvedTypeReferenceDirective = resolvedFile.resolvedTypeReferenceDirective;
cache.set(typeName, result);
resolvedTypeReferenceCache.push(result);
}
}
return resolvedTypeReferenceCache;
}
export const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map();
export function isOhExport(packageName: string | undefined, resolvedFileName: string | undefined): boolean {
if (packageName === undefined || resolvedFileName === undefined) {
return true;
}
const ohExportsMap: Map<string, object> | undefined = projectConfig?.depName2OhExports;
if (!ohExportsMap?.has(packageName)) {
return true;
}
const exportPaths: Set<string> = ohExportsMap.get(packageName)['oh-exports'];
return exportPaths.has(resolvedFileName);
}
export function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] {
ts.PerformanceDotting?.startAdvanced('resolveModuleNames');
const resolvedModules: ts.ResolvedModuleFull[] = [];
const cacheFileContent: ts.ResolvedModuleFull[] = resolvedModulesCache.get(path.resolve(containingFile));
if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile)) ||
!(cacheFileContent && cacheFileContent.length === moduleNames.length)) {
for (const moduleName of moduleNames) {
const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost,
moduleResolutionCache);
if (result.resolvedModule && !isOhExport(result.resolvedModule?.packageId?.name, result.resolvedModule?.resolvedFileName)) {
result.resolvedModule.isNotOhExport = true;
}
if (result.resolvedModule) {
if (result.resolvedModule.resolvedFileName &&
path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) {
const resultDETSPath: string =
result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS);
if (ts.sys.fileExists(resultDETSPath)) {
resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS));
} else {
resolvedModules.push(result.resolvedModule);
}
} else {
resolvedModules.push(result.resolvedModule);
}
} else if (new RegExp(`^@(${sdkConfigPrefix})\\.`, 'i').test(moduleName.trim())) {
let apiFileExist: boolean = false;
for (let i = 0; i < sdkConfigs.length; i++) {
const sdkConfig = sdkConfigs[i];
const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(sdkConfig.apiPath, moduleName, ['.d.ts', '.d.ets']);
const modulePath: string = resolveModuleInfo.modulePath;
const isDETS: boolean = resolveModuleInfo.isEts;
if (systemModules.includes(moduleName + (isDETS ? '.d.ets' : '.d.ts')) && ts.sys.fileExists(modulePath)) {
resolvedModules.push(getResolveModule(modulePath, isDETS ? '.d.ets' : '.d.ts'));
apiFileExist = true;
break;
}
}
if (!apiFileExist) {
resolvedModules.push(null);
}
} else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) {
const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
if (ts.sys.fileExists(modulePath)) {
resolvedModules.push(getResolveModule(modulePath, '.ets'));
} else {
resolvedModules.push(null);
}
} else if (/\.ts$/.test(moduleName)) {
const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
if (ts.sys.fileExists(modulePath)) {
resolvedModules.push(getResolveModule(modulePath, '.ts'));
} else {
resolvedModules.push(null);
}
} else {
const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts');
const systemDETSModulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ets');
const kitModulePath: string = path.resolve(__dirname, '../../../kits', moduleName + '.d.ts');
const kitSystemDETSModulePath: string = path.resolve(__dirname, '../../../kits', moduleName + '.d.ets');
const suffix: string = /\.js$/.test(moduleName) ? '' : '.js';
const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix);
const fileModulePath: string =
path.resolve(__dirname, '../node_modules', moduleName + '/index.js');
const DETSModulePath: string = path.resolve(path.dirname(containingFile),
/\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS);
if (ts.sys.fileExists(modulePath)) {
resolvedModules.push(getResolveModule(modulePath, '.d.ts'));
} else if (ts.sys.fileExists(systemDETSModulePath)) {
resolvedModules.push(getResolveModule(systemDETSModulePath, '.d.ets'));
} else if (ts.sys.fileExists(kitModulePath)) {
resolvedModules.push(getResolveModule(kitModulePath, '.d.ts'));
} else if (ts.sys.fileExists(kitSystemDETSModulePath)) {
resolvedModules.push(getResolveModule(kitSystemDETSModulePath, '.d.ets'));
} else if (ts.sys.fileExists(jsModulePath)) {
resolvedModules.push(getResolveModule(jsModulePath, '.js'));
} else if (ts.sys.fileExists(fileModulePath)) {
resolvedModules.push(getResolveModule(fileModulePath, '.js'));
} else if (ts.sys.fileExists(DETSModulePath)) {
resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets'));
} else {
let srcIndex: number = 0;
if (!!projectConfig.projectPath) {
srcIndex = projectConfig.projectPath.indexOf('src' + path.sep + 'main');
}
let DETSModulePathFromModule: string;
if (srcIndex > 0) {
DETSModulePathFromModule = path.resolve(
projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS);
if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) {
resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets'));
} else {
resolvedModules.push(null);
}
} else {
resolvedModules.push(null);
}
}
}
if (projectConfig.hotReload && resolvedModules.length &&
resolvedModules[resolvedModules.length - 1]) {
hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName));
}
if (collectShouldPackedFiles(resolvedModules)) {
allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName);
}
}
if (!projectConfig.xtsMode) {
createOrUpdateCache(resolvedModules, path.resolve(containingFile));
}
resolvedModulesCache.set(path.resolve(containingFile), resolvedModules);
ts.PerformanceDotting?.stopAdvanced('resolveModuleNames');
return resolvedModules;
}
ts.PerformanceDotting?.stopAdvanced('resolveModuleNames');
return resolvedModulesCache.get(path.resolve(containingFile));
}
export interface ResolveModuleInfo {
modulePath: string;
isEts: boolean;
}
function collectShouldPackedFiles(resolvedModules: ts.ResolvedModuleFull[]): boolean | RegExpMatchArray {
return (projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] &&
resolvedModules[resolvedModules.length - 1].resolvedFileName &&
(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/) ||
path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/\.d\.e?ts$/) &&
path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(
new RegExp('\\' + path.sep + 'src' + '\\' + path.sep + 'main' + '\\' + path.sep)));
}
function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void {
const children: string[] = [];
const error: boolean = false;
resolvedModules.forEach(moduleObj => {
if (moduleObj && moduleObj.resolvedFileName && /\.(ets|ts)$/.test(moduleObj.resolvedFileName)) {
const file: string = path.resolve(moduleObj.resolvedFileName);
const mtimeMs: number = fileInfoCache.getMtimeMs(file);
children.push(file);
const value: CacheFileName = cache[file];
if (value) {
value.mtimeMs = mtimeMs;
value.error = error;
value.parent = value.parent || [];
value.parent.push(path.resolve(containingFile));
value.parent = [...new Set(value.parent)];
} else {
cache[file] = { mtimeMs, children: [], parent: [containingFile], error };
}
}
});
cache[path.resolve(containingFile)] = {
mtimeMs: fileInfoCache.getMtimeMs(containingFile), children,
parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ?
cache[path.resolve(containingFile)].parent : [], error
};
}
export function createWatchCompilerHost(rootFileNames: string[],
reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function,
isPipe: boolean = false, resolveModulePaths: string[] = null): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> {
if (projectConfig.hotReload) {
rootFileNames.forEach(fileName => {
hotReloadSupportFiles.add(fileName);
});
}
if (!(isPipe && process.env.compileTool === 'rollup')) {
setCompilerOptions(resolveModulePaths);
}
const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', WATCH_COMPILER_BUILD_INFO_SUFFIX);
const watchCompilerOptions = { ...compilerOptions, tsBuildInfoFile: buildInfoPath };
const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
const host = ts.createWatchCompilerHost(
[...rootFileNames, ...readDeaclareFiles()], watchCompilerOptions,
ts.sys, createProgram, reportDiagnostic,
(diagnostic: ts.Diagnostic) => {
if ([6031, 6032].includes(diagnostic.code)) {
if (!isPipe) {
process.env.watchTs = 'start';
resetErrorCount();
}
}
if ([6193, 6194].includes(diagnostic.code)) {
if (!isPipe) {
process.env.watchTs = 'end';
if (fastBuildLogger) {
fastBuildLogger.debug(TS_WATCH_END_MSG);
tsWatchEmitter.emit(TS_WATCH_END_MSG);
}
}
delayPrintLogCount();
}
});
host.readFile = (fileName: string) => {
if (!fs.existsSync(fileName)) {
return undefined;
}
if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
let content: string = processContent(fs.readFileSync(fileName).toString(), fileName);
const extendFunctionInfo: extendInfo[] = [];
content = instanceInsteadThis(content, fileName, extendFunctionInfo, props);
return content;
}
return fs.readFileSync(fileName).toString();
};
host.resolveModuleNames = resolveModuleNames;
host.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives;
return host;
}
export function watchChecker(rootFileNames: string[], newLogger: any = null, resolveModulePaths: string[] = null): void {
fastBuildLogger = newLogger;
globalProgram.watchProgram = ts.createWatchProgram(
createWatchCompilerHost(rootFileNames, printDiagnostic, () => { }, () => { }, false, resolveModulePaths));
}
export function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[],
props: Set<string>): string {
extendFunctionInfo.reverse().forEach((item) => {
const subStr: string = content.substring(item.start, item.end);
const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => {
return item1 + item.compName + 'Instance' + item2;
});
content = content.slice(0, item.start) + insert + content.slice(item.end);
});
return content;
}
export function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull {
return {
resolvedFileName: modulePath,
isExternalLibraryImport: false,
extension: type
};
}
export const dollarCollection: Set<string> = new Set();
export const extendCollection: Set<string> = new Set();
const newExtendCollection: Set<string> = new Set();
export const importModuleCollection: Set<string> = new Set();
function checkUISyntax(sourceFile: ts.SourceFile, fileName: string, extendFunctionInfo: extendInfo[],
props: Set<string>): void {
if (/\.ets$/.test(fileName) && !/\.d.ets$/.test(fileName)) {
if (process.env.compileMode === 'moduleJson' ||
path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) {
collectComponents(sourceFile);
collectionCustomizeStyles(sourceFile);
parseAllNode(sourceFile, sourceFile, extendFunctionInfo);
dollarCollection.forEach((item) => {
props.add(item);
});
extendCollection.forEach((item) => {
props.add(item);
});
}
}
}
function collectionCustomizeStyles(node: ts.Node): void {
if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) &&
(isUIDecorator(node, COMPONENT_STYLES_DECORATOR) || isUIDecorator(node, COMPONENT_EXTEND_DECORATOR)) &&
node.name && ts.isIdentifier(node.name)) {
BUILDIN_STYLE_NAMES.add(node.name.escapedText.toString());
}
if (ts.isSourceFile(node)) {
node.statements.forEach((item: ts.Node) => {
return collectionCustomizeStyles(item);
});
} else if (ts.isStructDeclaration(node)) {
node.members.forEach((item: ts.Node) => {
return collectionCustomizeStyles(item);
});
}
}
function isUIDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration |
ts.StructDeclaration | ts.ClassDeclaration, 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(/\(.*\)$/, '').trim();
if (originalDecortor === decortorName) {
return true;
} else {
return false;
}
}
}
return false;
}
function collectComponents(node: ts.SourceFile): void {
if (process.env.watchMode !== 'true' && node.identifiers && node.identifiers.size) {
for (const key of node.identifiers.keys()) {
if (JS_BIND_COMPONENTS.has(key)) {
appComponentCollection.get(path.join(node.fileName)).add(key);
}
}
}
}
function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void {
if (ts.isStructDeclaration(node)) {
if (node.members) {
node.members.forEach(item => {
if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) {
const propertyName: string = item.name.getText();
const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item);
if (decorators && decorators.length) {
for (let i = 0; i < decorators.length; i++) {
const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim();
if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {
dollarCollection.add('$' + propertyName);
}
}
}
}
});
}
}
if (process.env.watchMode !== 'true' && ts.isIfStatement(node)) {
appComponentCollection.get(path.join(sourceFileNode.fileName)).add(COMPONENT_IF);
}
if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION ||
(ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) &&
hasDecorator(node, COMPONENT_BUILDER_DECORATOR) ||
ts.isMethodDeclaration(node) && hasDecorator(node, COMPONENT_LOCAL_BUILDER_DECORATOR)) {
if (node.body && node.body.statements && node.body.statements.length) {
const checkProp: ts.NodeArray<ts.Statement> = node.body.statements;
checkProp.forEach((item, index) => {
traverseBuild(item, index);
});
}
}
if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) {
if (node.body && node.body.statements && node.body.statements.length &&
!isOriginalExtend(node.body)) {
extendFunctionInfo.push({
start: node.pos,
end: node.end,
compName: isExtendFunction(node, { decoratorName: '', componentName: '' })
});
}
}
ts.forEachChild(node, (child: ts.Node) => parseAllNode(child, sourceFileNode, extendFunctionInfo));
}
function isForeachAndLzayForEach(node: ts.Node): boolean {
return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) &&
FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] &&
ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body);
}
function getComponentName(node: ts.Node): string {
let temp = node.expression;
let name: string;
while (temp) {
if (ts.isIdentifier(temp) && temp.parent && (ts.isCallExpression(temp.parent) ||
ts.isEtsComponentExpression(temp.parent))) {
name = temp.escapedText.toString();
break;
}
temp = temp.expression;
}
return name;
}
function traverseBuild(node: ts.Node, index: number): void {
if (ts.isExpressionStatement(node)) {
const parentComponentName: string = getComponentName(node);
node = node.expression;
while (node) {
if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) &&
ts.isIdentifier(node.expression) && !DOLLAR_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) {
node.body.statements.forEach((item: ts.Statement, indexBlock: number) => {
traverseBuild(item, indexBlock);
});
break;
} else if (isForeachAndLzayForEach(node)) {
node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => {
traverseBuild(item, indexBlock);
});
break;
} else {
loopNodeFindDoubleDollar(node, parentComponentName);
if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) &&
ts.isIdentifier(node.expression)) {
node.body.statements.forEach((item: ts.Statement, indexBlock: number) => {
traverseBuild(item, indexBlock);
});
break;
}
}
node = node.expression;
}
} else if (ts.isIfStatement(node)) {
ifInnerDollarAttribute(node);
}
}
function ifInnerDollarAttribute(node: ts.IfStatement): void {
if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) {
node.thenStatement.statements.forEach((item, indexIfBlock) => {
traverseBuild(item, indexIfBlock);
});
}
if (node.elseStatement) {
elseInnerDollarAttribute(node);
}
}
function elseInnerDollarAttribute(node: ts.IfStatement): void {
if (ts.isIfStatement(node.elseStatement) && node.elseStatement.thenStatement && ts.isBlock(node.elseStatement.thenStatement)) {
traverseBuild(node.elseStatement, 0);
} else if (ts.isBlock(node.elseStatement) && node.elseStatement.statements) {
node.elseStatement.statements.forEach((item, indexElseBlock) => {
traverseBuild(item, indexElseBlock);
});
}
}
function isPropertiesAddDoubleDollar(node: ts.Node): boolean {
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) {
return true;
} else if (ts.isEtsComponentExpression(node) && ts.isIdentifier(node.expression) &&
DOLLAR_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) {
return true;
} else {
return false;
}
}
function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void {
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
const argument: ts.NodeArray<ts.Node> = node.arguments;
const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name;
if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) {
argument.forEach((item: ts.Node) => {
doubleDollarCollection(item);
});
}
} else if (isPropertiesAddDoubleDollar(node)) {
node.arguments.forEach((item: ts.Node) => {
if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) {
item.properties.forEach((param: ts.Node) => {
if (isObjectPram(param, parentComponentName)) {
doubleDollarCollection(param.initializer);
}
});
} else if (ts.isPropertyAccessExpression(item) && (handleComponentDollarBlock(node as ts.CallExpression, parentComponentName) ||
STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()))) {
doubleDollarCollection(item);
}
});
}
}
function handleComponentDollarBlock(node: ts.CallExpression, parentComponentName: string): boolean {
return ts.isCallExpression(node) && ts.isIdentifier(node.expression) &&
DOLLAR_BLOCK_INTERFACE.has(parentComponentName) && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(node.expression.escapedText.toString());
}
function doubleDollarCollection(item: ts.Node): void {
if (item.getText().startsWith($$)) {
while (item.expression) {
item = item.expression;
}
dollarCollection.add(item.getText());
}
}
function isObjectPram(param: ts.Node, parentComponentName: string): boolean {
return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) &&
param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText());
}
function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean {
return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) ||
STYLE_ADD_DOUBLE_DOLLAR.has(propertyName);
}
function processContent(source: string, id: string): string {
if (fastBuildLogger) {
source = visualTransform(source, id, fastBuildLogger);
}
source = preprocessExtend(source, extendCollection);
source = preprocessNewExtend(source, newExtendCollection);
return source;
}
function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set<string>): void {
if (shouldResolvedFiles.has(file)) {
return;
}
shouldResolvedFiles.add(file);
if (cache && cache[file] && cache[file].parent) {
cache[file].parent.forEach((item) => {
judgeFileShouldResolved(item, shouldResolvedFiles);
});
cache[file].parent = [];
}
if (cache && cache[file] && cache[file].children) {
cache[file].children.forEach((item) => {
judgeFileShouldResolved(item, shouldResolvedFiles);
});
cache[file].children = [];
}
}
export function incrementWatchFile(watchModifiedFiles: string[],
watchRemovedFiles: string[]): void {
const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles];
if (changedFiles.length) {
shouldResolvedFiles.clear();
}
changedFiles.forEach((file) => {
judgeFileShouldResolved(file, shouldResolvedFiles);
});
}
export function runArkTSLinter(errorCodeLogger?: Object | undefined, parentEvent?: CompileEvent): void {
const originProgram: ts.BuilderProgram = globalProgram.builderProgram;
const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance();
const arkTSLinterDiagnostics = doArkTSLinter(getArkTSVersion(),
getArkTSLinterMode(),
originProgram,
printArkTSLinterDiagnostic,
!projectConfig.xtsMode,
buildInfoWriteFile,
errorCodeLogger);
ts.PerformanceDotting?.startAdvanced('updateErrorFile');
if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
arkTSLinterDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
updateErrorFileCache(diagnostic);
});
timePrinterInstance.appendTime(ts.TimePhase.UPDATE_ERROR_FILE);
}
ts.PerformanceDotting?.stopAdvanced('updateErrorFile');
timePrinterInstance.printTimes();
ts.ArkTSLinterTimePrinter.destroyInstance();
}
function printArkTSLinterDiagnostic(diagnostic: ts.Diagnostic, errorCodeLogger?: Object | undefined): void {
if (diagnostic.category === ts.DiagnosticCategory.Error && (isInOhModuleFile(diagnostic) || isEtsDeclFileInSdk(diagnostic))) {
const originalCategory = diagnostic.category;
diagnostic.category = ts.DiagnosticCategory.Warning;
printDiagnostic(diagnostic);
diagnostic.category = originalCategory;
return;
}
printDiagnostic(diagnostic, ErrorCodeModule.LINTER, errorCodeLogger);
}
function isEtsDeclFileInSdk(diagnostics: ts.Diagnostic): boolean {
if (diagnostics.file?.fileName === undefined) {
return false;
}
return isInSDK(diagnostics.file.fileName) && diagnostics.file.fileName.endsWith('.ets');
}
function isInOhModuleFile(diagnostics: ts.Diagnostic): boolean {
return (diagnostics.file !== undefined) &&
((diagnostics.file.fileName.indexOf('/oh_modules/') !== -1) || diagnostics.file.fileName.indexOf('\\oh_modules\\') !== -1);
}
function isInSDK(fileName: string | undefined): boolean {
if (projectConfig.etsLoaderPath === undefined || fileName === undefined) {
return false;
}
const sdkPath = path.resolve(projectConfig.etsLoaderPath, '../../../');
return path.resolve(fileName).startsWith(sdkPath);
}
export function getArkTSLinterMode(): ArkTSLinterMode {
if (!partialUpdateConfig.executeArkTSLinter) {
return ArkTSLinterMode.NOT_USE;
}
if (!partialUpdateConfig.standardArkTSLinter) {
return ArkTSLinterMode.COMPATIBLE_MODE;
}
if (isStandardMode()) {
return ArkTSLinterMode.STANDARD_MODE;
}
return ArkTSLinterMode.COMPATIBLE_MODE;
}
export function isStandardMode(): boolean {
const STANDARD_MODE_COMPATIBLE_SDK_VERSION = 10;
if (projectConfig &&
projectConfig.compatibleSdkVersion &&
projectConfig.compatibleSdkVersion >= STANDARD_MODE_COMPATIBLE_SDK_VERSION) {
return true;
}
return false;
}
function getArkTSVersion(): ArkTSVersion {
if (projectConfig.arkTSVersion === '1.0') {
return ArkTSVersion.ArkTS_1_0;
} else if (projectConfig.arkTSVersion === '1.1') {
return ArkTSVersion.ArkTS_1_1;
} else if (projectConfig.arkTSVersion !== undefined) {
const arkTSVersionLogger = fastBuildLogger || logger;
arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version\n');
}
if (partialUpdateConfig.arkTSVersion === '1.0') {
return ArkTSVersion.ArkTS_1_0;
} else if (partialUpdateConfig.arkTSVersion === '1.1') {
return ArkTSVersion.ArkTS_1_1;
} else if (partialUpdateConfig.arkTSVersion !== undefined) {
const arkTSVersionLogger = fastBuildLogger || logger;
arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version in metadata\n');
}
return ArkTSVersion.ArkTS_1_1;
}
enum TargetESVersion {
ES2017 = 'ES2017',
ES2021 = 'ES2021',
}
function getTargetESVersion(): TargetESVersion {
const targetESVersion = projectConfig?.projectArkOption?.tscConfig?.targetESVersion;
if (targetESVersion === 'ES2017') {
return TargetESVersion.ES2017;
} else if (targetESVersion === 'ES2021') {
return TargetESVersion.ES2021;
} else if (targetESVersion !== undefined) {
const targetESVersionLogger = fastBuildLogger || logger;
targetESVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid Target ES version\n');
}
return TargetESVersion.ES2021;
}
export function getMaxFlowDepth(): number {
let maxFlowDepth: number | undefined = projectConfig?.projectArkOption?.tscConfig?.maxFlowDepth;
if (maxFlowDepth === undefined) {
maxFlowDepth = MAX_FLOW_DEPTH_DEFAULT_VALUE;
} else if (maxFlowDepth < MAX_FLOW_DEPTH_DEFAULT_VALUE || maxFlowDepth > MAX_FLOW_DEPTH_MAXIMUM_VALUE) {
const maxFlowDepthLogger = fastBuildLogger || logger;
maxFlowDepth = MAX_FLOW_DEPTH_DEFAULT_VALUE;
maxFlowDepthLogger.warn('\u001b[33m' + 'ArkTS: Invalid maxFlowDepth for control flow analysis.' +
`The value of maxFlowDepth ranges from ${MAX_FLOW_DEPTH_DEFAULT_VALUE} to ${MAX_FLOW_DEPTH_MAXIMUM_VALUE}.\n` +
'If the modification does not take effect, set maxFlowDepth to the default value.');
}
return maxFlowDepth;
}
interface TargetESVersionLib {
ES2017: string[],
ES2021: string[],
}
const targetESVersionLib: TargetESVersionLib = {
ES2017: ['ES2020'],
ES2021: ['ES2021'],
};
function getTargetESVersionLib(): string[] {
const targetESVersion = projectConfig?.projectArkOption?.tscConfig?.targetESVersion;
if (targetESVersion === 'ES2017') {
return targetESVersionLib.ES2017;
} else if (targetESVersion === 'ES2021') {
return targetESVersionLib.ES2021;
} else if (targetESVersion !== undefined) {
const targetESVersionLogger = fastBuildLogger || logger;
targetESVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid Target ES version\n');
}
return targetESVersionLib.ES2021;
}
function initEtsStandaloneCheckerConfig(logger, config): void {
fastBuildLogger = logger;
if (config.packageManagerType === 'ohpm') {
config.packageDir = 'oh_modules';
config.packageJson = 'oh-package.json5';
} else {
config.packageDir = 'node_modules';
config.packageJson = 'package.json';
}
if (config.aceModuleJsonPath && fs.existsSync(config.aceModuleJsonPath)) {
process.env.compileMode = 'moduleJson';
}
Object.assign(projectConfig, config);
}
function resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode: string): void {
resetProjectConfig();
resetEtsCheck();
fastBuildLogger = beforeInitFastBuildLogger;
process.env.compileMode = beforeInitCompileMode;
}
export function etsStandaloneChecker(entryObj, logger, projectConfig): void {
const beforeInitFastBuildLogger = fastBuildLogger;
const beforeInitCompileMode = process.env.compileMode;
initEtsStandaloneCheckerConfig(logger, projectConfig);
const rootFileNames: string[] = [];
const resolveModulePaths: string[] = [];
Object.values(entryObj).forEach((fileName: string) => {
rootFileNames.push(path.resolve(fileName));
});
if (projectConfig.resolveModulePaths && Array.isArray(projectConfig.resolveModulePaths)) {
resolveModulePaths.push(...projectConfig.resolveModulePaths);
}
const filterFiles: string[] = filterInput(rootFileNames);
languageService = createLanguageService(filterFiles, resolveModulePaths);
const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance();
timePrinterInstance.setArkTSTimePrintSwitch(false);
timePrinterInstance.appendTime(ts.TimePhase.START);
globalProgram.builderProgram = languageService.getBuilderProgram( true);
globalProgram.program = globalProgram.builderProgram.getProgram();
props = languageService.getProps();
timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM);
collectFileToIgnoreDiagnostics(filterFiles);
runArkTSLinter();
const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram
.getSyntacticDiagnostics()
.concat(globalProgram.builderProgram.getSemanticDiagnostics());
globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile);
allDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
printDiagnostic(diagnostic);
});
resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode);
}
export function resetEtsCheckTypeScript(): void {
if (globalProgram.program) {
globalProgram.program.releaseTypeChecker();
}
resetGlobalProgram();
languageService = null;
}
export function resetEtsCheck(): void {
cache = {};
props = new Set();
needReCheckForChangedDepUsers = false;
resetEtsCheckTypeScript();
allResolvedModules.clear();
checkerResult.count = 0;
warnCheckerResult.count = 0;
resolvedModulesCache.clear();
dollarCollection.clear();
extendCollection.clear();
newExtendCollection.clear();
allSourceFilePaths.clear();
allModuleIds.clear();
filesBuildInfo.clear();
fileExistsCache.clear();
dirExistsCache.clear();
moduleResolutionCache?.clear();
targetESVersionChanged = false;
fileToIgnoreDiagnostics = undefined;
maxMemoryInServiceChecker = 0;
}
function performDeclarationMerging(inputDir: string, isByteCodeHar: boolean): void {
const entryDeclarationFiles: string[] = [];
const modulePath: string | undefined = projectConfig.modulePath;
if (projectConfig.ohExports && projectConfig.ohExports.length > 0) {
if (!modulePath) {
logger.debug('modulePath is not set, skip declaration merging');
return;
}
projectConfig.ohExports.forEach(element => {
const declareFile = element.replace(modulePath, inputDir).replace(/\.(ets|ts)$/, '.d.$1');
entryDeclarationFiles.push(declareFile);
});
} else {
if (!modulePath) {
logger.debug('modulePath is not set, skip declaration merging');
return;
}
const pkgJsonPath = path.join(modulePath, 'oh-package.json5');
if (!fs.existsSync(pkgJsonPath)) {
logger.debug('oh-package.json5 not found, skip declaration merging');
return;
}
try {
const pkgContent = JSON5.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
const mainField = pkgContent.main;
if (!mainField) {
logger.debug('main field not found in oh-package.json5, skip declaration merging');
return;
}
const entryFileName = mainField.replace(/\.(ets|ts)$/, '.d.$1');
const entryFilePath = path.join(inputDir, entryFileName);
entryDeclarationFiles.push(entryFilePath);
} catch (error) {
logger.debug(`Failed to parse oh-package.json5: ${error.message}`);
return;
}
}
if (entryDeclarationFiles.length === 0) {
logger.debug('No entry declaration files found for merging');
return;
}
const projectPath = (projectConfig.byteCodeHar || projectConfig.compileShared)
? path.join(projectConfig.declaredFilesPath, '..')
: projectConfig.cachePath;
try {
const { DeclarationMerger } = require('./declaration_merger');
DeclarationMerger.mergeDeclarationFiles({
entryFiles: entryDeclarationFiles,
projectPath: projectPath,
isByteCodeHar: isByteCodeHar,
moduleRootPath: projectConfig.moduleRootPath,
packageDir: projectConfig.packageDir,
systemModules: systemModules
});
} catch (error) {
logger.debug(`Failed to merge declaration files: ${error.message}`);
throw error;
}
}