1/* 2 * Copyright (c) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import fs from 'fs'; 17import path from 'path'; 18import * as ts from 'typescript'; 19import * as crypto from 'crypto'; 20const fse = require('fs-extra'); 21 22import { 23 projectConfig, 24 systemModules, 25 globalProgram, 26 sdkConfigs, 27 sdkConfigPrefix, 28 partialUpdateConfig, 29 resetProjectConfig, 30 resetGlobalProgram 31} from '../main'; 32import { 33 preprocessExtend, 34 preprocessNewExtend 35} from './validate_ui_syntax'; 36import { 37 INNER_COMPONENT_MEMBER_DECORATORS, 38 COMPONENT_DECORATORS_PARAMS, 39 COMPONENT_BUILD_FUNCTION, 40 STYLE_ADD_DOUBLE_DOLLAR, 41 $$, 42 PROPERTIES_ADD_DOUBLE_DOLLAR, 43 DOLLAR_BLOCK_INTERFACE, 44 COMPONENT_EXTEND_DECORATOR, 45 COMPONENT_BUILDER_DECORATOR, 46 ESMODULE, 47 EXTNAME_D_ETS, 48 EXTNAME_JS, 49 EXTNAME_ETS, 50 FOREACH_LAZYFOREACH, 51 COMPONENT_IF, 52 TS_WATCH_END_MSG, 53 TS_BUILD_INFO_SUFFIX, 54 HOT_RELOAD_BUILD_INFO_SUFFIX, 55 WATCH_COMPILER_BUILD_INFO_SUFFIX, 56 COMPONENT_STYLES_DECORATOR 57} from './pre_define'; 58import { 59 INNER_COMPONENT_NAMES, 60 JS_BIND_COMPONENTS, 61 BUILDIN_STYLE_NAMES 62} from './component_map'; 63import { logger } from './compile_info'; 64import { 65 hasDecorator, 66 isString, 67 generateSourceFilesInHar, 68 storedFileInfo, 69 toUnixPath, 70 isWindows, 71 isMac, 72 tryToLowerCasePath, 73 getRollupCache, 74 setRollupCache 75} from './utils'; 76import { 77 isExtendFunction, 78 isOriginalExtend 79} from './process_ui_syntax'; 80import { visualTransform } from './process_visual'; 81import { tsWatchEmitter } from './fast_build/ets_ui/rollup-plugin-ets-checker'; 82import { 83 doArkTSLinter, 84 ArkTSLinterMode, 85 ArkTSVersion, 86 transfromErrorCode, 87} from './do_arkTS_linter'; 88import { 89 getJsDocNodeCheckConfig, 90 isCardFile, 91 getRealModulePath, 92 getJsDocNodeConditionCheckResult 93} from './fast_build/system_api/api_check_utils'; 94import { sourceFileDependencies } from './fast_build/ark_compiler/common/ob_config_resolver'; 95import { MemoryMonitor } from './fast_build/meomry_monitor/rollup-plugin-memory-monitor'; 96import { MemoryDefine } from './fast_build/meomry_monitor/memory_define'; 97import { 98 CompileEvent 99} from './performance'; 100import { 101 LINTER_SUBSYSTEM_CODE, 102 HvigorErrorInfo 103} from './hvigor_error_code/hvigor_error_info'; 104import { ErrorCodeModule } from './hvigor_error_code/const/error_code_module'; 105import { buildErrorInfoFromDiagnostic } from './hvigor_error_code/utils'; 106import { concatenateEtsOptions, getExternalComponentPaths } from './external_component_map'; 107import { 108 getArkTSEvoDeclFilePath, 109 redirectToDeclFileForInterop 110} from './fast_build/ark_compiler/interop/process_arkts_evolution'; 111import { 112 FileManager, 113 getApiPathForInterop, 114 isMixCompile 115} from './fast_build/ark_compiler/interop/interop_manager'; 116import { 117 ARKTS_1_1, 118 ARKTS_1_2 119} from './fast_build/ark_compiler/interop/pre_define'; 120 121 122export interface LanguageServiceCache { 123 service?: ts.LanguageService; 124 pkgJsonFileHash?: string; 125 targetESVersion?: ts.ScriptTarget; 126 maxFlowDepth?: number; 127 preTsImportSendable?: boolean; 128 preSkipOhModulesLint?: boolean; 129 preMixCompile?: boolean; 130} 131 132export const SOURCE_FILES: Map<string, ts.SourceFile> = new Map(); 133export let localPackageSet: Set<string> = new Set(); 134export const TSC_SYSTEM_CODE = '105'; 135export const fileCache: Map<string, string> = new Map(); 136 137export const MAX_FLOW_DEPTH_DEFAULT_VALUE = 2000; 138export const MAX_FLOW_DEPTH_MAXIMUM_VALUE = 65535; 139 140export function readDeaclareFiles(): string[] { 141 const declarationsFileNames: string[] = []; 142 fs.readdirSync(path.resolve(__dirname, '../declarations')) 143 .forEach((fileName: string) => { 144 if (/\.d\.ts$/.test(fileName)) { 145 declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName)); 146 } 147 }); 148 return declarationsFileNames; 149} 150 151const buildInfoWriteFile: ts.WriteFileCallback = (fileName: string, data: string) => { 152 if (fileName.includes(TS_BUILD_INFO_SUFFIX)) { 153 const fd: number = fs.openSync(fileName, 'w'); 154 fs.writeSync(fd, data, undefined, 'utf8'); 155 fs.closeSync(fd); 156 } 157}; 158// The collection records the file name and the corresponding version, where the version is the hash value of the text in last compilation. 159const filesBuildInfo: Map<string, string> = new Map(); 160 161export let compilerOptions = ts.readConfigFile( 162 path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 163const componentPaths: string[] | undefined = getExternalComponentPaths(); 164if (componentPaths) { 165 for (const componentPath of componentPaths) { 166 if (!fs.existsSync(componentPath)) { 167 continue; 168 } 169 const externalCompilerOptions: ts.CompilerOptions = ts.readConfigFile( 170 path.resolve(componentPath, 'externalconfig.json'), ts.sys.readFile 171 ).config.compilerOptions; 172 concatenateEtsOptions(compilerOptions, externalCompilerOptions); 173 } 174} 175function setCompilerOptions(resolveModulePaths: string[]): void { 176 const allPath: Array<string> = ['*']; 177 const basePath: string = path.resolve(projectConfig.projectPath); 178 if (process.env.compileTool === 'rollup' && resolveModulePaths && resolveModulePaths.length) { 179 resolveModulePaths.forEach((item: string) => { 180 if (!(/oh_modules$/.test(item) || /node_modules$/.test(item))) { 181 allPath.push(path.join(path.relative(basePath, item), '*')); 182 } 183 }); 184 } else { 185 if (!projectConfig.aceModuleJsonPath) { 186 allPath.push('../../../../../*'); 187 allPath.push('../../*'); 188 } else { 189 allPath.push('../../../../*'); 190 allPath.push('../*'); 191 } 192 } 193 if (isMixCompile()) { 194 compilerOptions.preserveValueImports = true; 195 } 196 const suffix: string = projectConfig.hotReload ? HOT_RELOAD_BUILD_INFO_SUFFIX : TS_BUILD_INFO_SUFFIX; 197 const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', suffix); 198 checkArkTSVersion(); 199 Object.assign(compilerOptions, { 200 'allowJs': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE ? true : false, 201 'checkJs': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE ? false : undefined, 202 'emitNodeModulesFiles': true, 203 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve, 204 'module': ts.ModuleKind.CommonJS, 205 'moduleResolution': ts.ModuleResolutionKind.NodeJs, 206 'noEmit': true, 207 'target': convertConfigTarget(getTargetESVersion()), 208 'maxFlowDepth': getMaxFlowDepth(), 209 'baseUrl': basePath, 210 'paths': { 211 '*': allPath 212 }, 213 'lib': convertConfigLib(getTargetESVersionLib()), 214 'types': projectConfig.compilerTypes, 215 'etsAnnotationsEnable': projectConfig.allowEtsAnnotations, 216 'etsLoaderPath': projectConfig.etsLoaderPath, 217 'needDoArkTsLinter': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE, 218 'isCompatibleVersion': getArkTSLinterMode() === ArkTSLinterMode.COMPATIBLE_MODE, 219 'skipTscOhModuleCheck': partialUpdateConfig.skipTscOhModuleCheck, 220 'skipArkTSStaticBlocksCheck': partialUpdateConfig.skipArkTSStaticBlocksCheck, 221 // options incremental && tsBuildInfoFile are required for applying incremental ability of typescript 222 'incremental': true, 223 'tsBuildInfoFile': buildInfoPath, 224 'tsImportSendableEnable': tsImportSendable, 225 'skipPathsInKeyForCompilationSettings': reuseLanguageServiceForDepChange, 226 'compatibleSdkVersionStage': projectConfig.compatibleSdkVersionStage, 227 'compatibleSdkVersion': projectConfig.compatibleSdkVersion, 228 'skipOhModulesLint': skipOhModulesLint, 229 'mixCompile': mixCompile 230 }); 231 if (projectConfig.compileMode === ESMODULE) { 232 Object.assign(compilerOptions, { 233 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Remove, 234 'module': ts.ModuleKind.ES2020 235 }); 236 } 237 if (projectConfig.packageDir === 'oh_modules') { 238 Object.assign(compilerOptions, {'packageManagerType': 'ohpm'}); 239 } 240 readTsBuildInfoFileInCrementalMode(buildInfoPath, projectConfig); 241} 242 243function checkArkTSVersion(): void { 244 const etsCheckerLogger = fastBuildLogger || logger; 245 if (getArkTSVersion() === ArkTSVersion.ArkTS_1_0 && tsImportSendable) { 246 const logMessage: string = 'ArkTS: ArkTSVersion1.0 does not support tsImportSendable in any condition'; 247 etsCheckerLogger.error('\u001b[31m' + logMessage); 248 tsImportSendable = false; 249 } 250} 251 252// Change target to enum's value,e.g: "es2021" => ts.ScriptTarget.ES2021 253function convertConfigTarget(target: number | string): number | string { 254 if ((typeof target === 'number') && (target in ts.ScriptTarget)) { 255 return target; 256 } 257 return ts.convertCompilerOptionsFromJson({ 'target': target }, '').options.target; 258} 259 260// Change lib to libMap's value,e.g: "es2021" => "lib.es2021.d.ts" 261function convertConfigLib(libs: string[]): string[] { 262 let converted: boolean = true; 263 let libMapValues: string[] = Array.from(ts.libMap.values()); 264 for (let i = 0; i < libs.length; i++) { 265 if (!libMapValues.includes(libs[i])) { 266 converted = false; 267 break; 268 } 269 } 270 if (converted) { 271 return libs; 272 } 273 return ts.convertCompilerOptionsFromJson({ 'lib': libs }, '').options.lib; 274} 275 276/** 277 * Read the source code information in the project of the last compilation process, and then use it 278 * to determine whether the file has been modified during this compilation process. 279 */ 280function readTsBuildInfoFileInCrementalMode(buildInfoPath: string, projectConfig: Object): void { 281 if (!fs.existsSync(buildInfoPath) || !(projectConfig.compileHar || projectConfig.compileShared)) { 282 return; 283 } 284 285 type FileInfoType = { 286 version: string; 287 affectsGlobalScope: boolean; 288 }; 289 type ProgramType = { 290 fileNames: string[]; 291 fileInfos: (FileInfoType | string)[]; 292 }; 293 let buildInfoProgram: ProgramType; 294 try { 295 const content: {program: ProgramType} = JSON.parse(fs.readFileSync(buildInfoPath, 'utf-8')); 296 buildInfoProgram = content.program; 297 if (!buildInfoProgram || !buildInfoProgram.fileNames || !buildInfoProgram.fileInfos) { 298 throw new Error('.tsbuildinfo content is invalid'); 299 } 300 } catch (err) { 301 fastBuildLogger.warn('\u001b[33m' + 'ArkTS: Failed to parse .tsbuildinfo file. Error message: ' + err.message.toString()); 302 return; 303 } 304 const buildInfoDirectory: string = path.dirname(buildInfoPath); 305 /** 306 * For the windos and mac platform, the file path in tsbuildinfo is in lowercase, while buildInfoDirectory is the original path (including uppercase). 307 * Therefore, the path needs to be converted to lowercase, and then perform path comparison. 308 */ 309 const isMacOrWin = isWindows() || isMac(); 310 const fileNames: string[] = buildInfoProgram.fileNames; 311 const fileInfos: (FileInfoType | string)[] = buildInfoProgram.fileInfos; 312 fileInfos.forEach((fileInfo, index) => { 313 const version: string = typeof fileInfo === 'string' ? fileInfo : fileInfo.version; 314 const absPath: string = path.resolve(buildInfoDirectory, fileNames[index]); 315 filesBuildInfo.set(isMacOrWin ? tryToLowerCasePath(absPath) : absPath, version); 316 }); 317} 318 319interface extendInfo { 320 start: number, 321 end: number, 322 compName: string 323} 324 325function createHash(str: string): string { 326 const hash = crypto.createHash('sha256'); 327 hash.update(str); 328 return hash.digest('hex'); 329} 330 331export function getFileContentWithHash(fileName: string): string { 332 let fileContent: string | undefined = fileCache.get(fileName); 333 if (fileContent === undefined) { 334 fileContent = fs.readFileSync(fileName).toString(); 335 fileCache.set(fileName, fileContent); 336 // Provide the hash value for hvigor's remote cache, and let them handle the cleanup. 337 setHashValueByFilePath?.(fileName, createHash(fileContent)); 338 } 339 return fileContent; 340} 341 342export const fileHashScriptVersion: (fileName: string) => string = (fileName: string) => { 343 if (!fs.existsSync(fileName)) { 344 return '0'; 345 } 346 347 let fileContent: string = getFileContentWithHash(fileName); 348 let cacheInfo: CacheFileName = cache[path.resolve(fileName)]; 349 350 // Error code corresponding to message `Cannot find module xx or its corresponding type declarations` 351 const errorCodeRequireRecheck: number = 2307; 352 353 if (cacheInfo && cacheInfo.error === true && cacheInfo.errorCodes && cacheInfo.errorCodes.includes(errorCodeRequireRecheck)) { 354 // If this file had errors that require recheck in the last compilation, 355 // mark the file as modified by modifying its hash value, thereby triggering tsc to recheck. 356 fileContent += Date.now().toString(); 357 return createHash(fileContent); 358 } 359 return getHashByFilePath?.(fileName) ?? createHash(fileContent); 360}; 361 362// Reuse the last language service when dependency in oh-package.json5 changes to enhance performance in incremental building. 363// Setting this to false will create a new language service on dependency changes, like a full rebuild. 364const reuseLanguageServiceForDepChange: boolean = true; 365// When dependency changes and reusing the last language service, enable this flag to recheck code dependent on those dependencies. 366export let needReCheckForChangedDepUsers: boolean = false; 367let setHashValueByFilePath: Function | undefined = undefined; 368let getHashByFilePath: Function | undefined = undefined; 369 370export function createLanguageService(rootFileNames: string[], resolveModulePaths: string[], 371 parentEvent?: CompileEvent, rollupShareObject?: Object): ts.LanguageService { 372 setHashValueByFilePath = rollupShareObject?.setHashValueByFilePath; 373 getHashByFilePath = rollupShareObject?.getHashByFilePath; 374 setCompilerOptions(resolveModulePaths); 375 const servicesHost: ts.LanguageServiceHost = { 376 getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()], 377 getScriptVersion: fileHashScriptVersion, 378 getScriptSnapshot: function(fileName) { 379 if (!fs.existsSync(fileName)) { 380 return undefined; 381 } 382 let fileContent: string = getFileContentWithHash(fileName); 383 if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) { 384 ts.PerformanceDotting.startAdvanced('scriptSnapshot'); 385 appComponentCollection.set(path.join(fileName), new Set()); 386 let content: string = processContent(fileContent, fileName); 387 const extendFunctionInfo: extendInfo[] = []; 388 content = instanceInsteadThis(content, fileName, extendFunctionInfo, this.uiProps); 389 ts.PerformanceDotting.stopAdvanced('scriptSnapshot'); 390 return ts.ScriptSnapshot.fromString(content); 391 } 392 return ts.ScriptSnapshot.fromString(fileContent); 393 }, 394 getCurrentDirectory: () => process.cwd(), 395 getCompilationSettings: () => compilerOptions, 396 getDefaultLibFileName: options => ts.getDefaultLibFilePath(options), 397 fileExists: ts.sys.fileExists, 398 readFile: ts.sys.readFile, 399 readDirectory: ts.sys.readDirectory, 400 resolveModuleNames: resolveModuleNames, 401 resolveTypeReferenceDirectives: resolveTypeReferenceDirectives, 402 directoryExists: ts.sys.directoryExists, 403 getDirectories: ts.sys.getDirectories, 404 getJsDocNodeCheckedConfig: (fileCheckedInfo: ts.FileCheckModuleInfo, sourceFileName: string) => { 405 return getJsDocNodeCheckConfig(fileCheckedInfo.currentFileName, sourceFileName); 406 }, 407 getFileCheckedModuleInfo: (containFilePath: string) => { 408 return { 409 fileNeedCheck: true, 410 checkPayload: undefined, 411 currentFileName: containFilePath 412 }; 413 }, 414 getJsDocNodeConditionCheckedResult: (jsDocFileCheckedInfo: ts.FileCheckModuleInfo, jsDocInfos: ts.JsDocTagInfo[], jsDocs?: ts.JSDoc[]) => { 415 return getJsDocNodeConditionCheckResult(jsDocFileCheckedInfo, jsDocInfos, jsDocs); 416 }, 417 uiProps: new Set(), 418 clearProps: function() { 419 dollarCollection.clear(); 420 extendCollection.clear(); 421 newExtendCollection.clear(); 422 this.uiProps = new Set(); 423 }, 424 // TSC will re-do resolution if this callback return true. 425 hasInvalidatedResolutions: (filePath: string): boolean => { 426 return reuseLanguageServiceForDepChange && needReCheckForChangedDepUsers; 427 }, 428 clearFileCache: function() { 429 fileCache.clear(); 430 }, 431 isStaticSourceFile: (fileName: string): boolean => { 432 const languageVersion = FileManager.getInstance().getLanguageVersionByFilePath(fileName); 433 return languageVersion?.languageVersion === ARKTS_1_2; 434 }, 435 }; 436 ts.PerformanceDotting?.setPerformanceSwitch(projectConfig?.perf); 437 438 if (process.env.watchMode === 'true') { 439 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ETS_CHECKER_CREATE_LANGUAGE_SERVICE); 440 const tsLanguageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); 441 MemoryMonitor.stopRecordStage(recordInfo); 442 return tsLanguageService; 443 } 444 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.ETS_CHECKER_CREATE_LANGUAGE_SERVICE); 445 const tsLanguageService = getOrCreateLanguageService(servicesHost, rootFileNames, rollupShareObject); 446 MemoryMonitor.stopRecordStage(recordInfo); 447 return tsLanguageService; 448} 449 450export let targetESVersionChanged: boolean = false; 451 452function getOrCreateLanguageService(servicesHost: ts.LanguageServiceHost, rootFileNames: string[], 453 rollupShareObject?: Object): ts.LanguageService { 454 let cacheKey: string = 'service'; 455 let cache: LanguageServiceCache | undefined = getRollupCache(rollupShareObject, projectConfig, cacheKey); 456 457 let service: ts.LanguageService | undefined = cache?.service; 458 const currentHash: string | undefined = rollupShareObject?.projectConfig?.pkgJsonFileHash; 459 const currentTargetESVersion: ts.ScriptTarget = compilerOptions.target; 460 const currentMaxFlowDepth: number | undefined = compilerOptions.maxFlowDepth; 461 const lastHash: string | undefined = cache?.pkgJsonFileHash; 462 const lastTargetESVersion: ts.ScriptTarget | undefined = cache?.targetESVersion; 463 const lastMaxFlowDepth: number | undefined = cache?.maxFlowDepth; 464 const hashDiffers: boolean | undefined = currentHash && lastHash && currentHash !== lastHash; 465 const shouldRebuildForDepDiffers: boolean | undefined = reuseLanguageServiceForDepChange ? 466 (hashDiffers && !rollupShareObject?.depInfo?.enableIncre) : hashDiffers; 467 const targetESVersionDiffers: boolean | undefined = lastTargetESVersion && currentTargetESVersion && lastTargetESVersion !== currentTargetESVersion; 468 const maxFlowDepthDiffers: boolean | undefined = lastMaxFlowDepth && currentMaxFlowDepth && lastMaxFlowDepth !== currentMaxFlowDepth; 469 const tsImportSendableDiff: boolean = (cache?.preTsImportSendable === undefined && !tsImportSendable) ? 470 false : 471 cache?.preTsImportSendable !== tsImportSendable; 472 const skipOhModulesLintDiff: boolean = (cache?.preSkipOhModulesLint === undefined && !skipOhModulesLint) ? 473 false : cache?.preSkipOhModulesLint !== skipOhModulesLint; 474 const mixCompileDiff: boolean = (cache?.preMixCompile === undefined && !mixCompile) ? 475 false : cache?.preMixCompile !== mixCompile; 476 const shouldRebuild: boolean | undefined = shouldRebuildForDepDiffers || targetESVersionDiffers || 477 tsImportSendableDiff || maxFlowDepthDiffers || skipOhModulesLintDiff || mixCompileDiff; 478 if (reuseLanguageServiceForDepChange && hashDiffers && rollupShareObject?.depInfo?.enableIncre) { 479 needReCheckForChangedDepUsers = true; 480 } 481 482 if (!service || shouldRebuild) { 483 rebuildProgram(targetESVersionDiffers, tsImportSendableDiff, maxFlowDepthDiffers, skipOhModulesLintDiff, mixCompileDiff); 484 service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); 485 } else { 486 // Found language service from cache, update root files 487 const updateRootFileNames = [...rootFileNames, ...readDeaclareFiles()]; 488 service.updateRootFiles(updateRootFileNames); 489 } 490 491 const newCache: LanguageServiceCache = { 492 service: service, 493 pkgJsonFileHash: currentHash, 494 targetESVersion: currentTargetESVersion, 495 maxFlowDepth: currentMaxFlowDepth, 496 preTsImportSendable: tsImportSendable, 497 preSkipOhModulesLint: skipOhModulesLint, 498 preMixCompile: mixCompile 499 }; 500 setRollupCache(rollupShareObject, projectConfig, cacheKey, newCache); 501 return service; 502} 503 504function rebuildProgram(targetESVersionDiffers: boolean | undefined, tsImportSendableDiff: boolean, 505 maxFlowDepthDiffers: boolean | undefined, skipOhModulesLintDiff: boolean, mixCompileDiff: boolean): void { 506 if (targetESVersionDiffers) { 507 // If the targetESVersion is changed, we need to delete the build info cahce files 508 deleteBuildInfoCache(compilerOptions.tsBuildInfoFile); 509 targetESVersionChanged = true; 510 } else if (tsImportSendableDiff || maxFlowDepthDiffers || skipOhModulesLintDiff || mixCompileDiff) { 511 // When tsImportSendable or maxFlowDepth is changed, we need to delete the build info cahce files 512 deleteBuildInfoCache(compilerOptions.tsBuildInfoFile); 513 } 514} 515 516function deleteBuildInfoCache(tsBuildInfoFilePath: string): void { 517 // The file name of tsBuildInfoLinterFile is '.tsbuildinfo.linter', so we need to add '.linter' after tsBuildInfoFilePath 518 const tsBuildInfoLinterFilePath: string = tsBuildInfoFilePath + '.linter'; 519 deleteFile(tsBuildInfoFilePath); 520 deleteFile(tsBuildInfoLinterFilePath); 521} 522 523function deleteFile(filePath: string): void { 524 if (fs.existsSync(filePath)) { 525 fs.unlinkSync(filePath); 526 } 527} 528 529interface CacheFileName { 530 mtimeMs: number, 531 children: string[], 532 parent: string[], 533 error: boolean, 534 errorCodes?: number[] 535} 536interface NeedUpdateFlag { 537 flag: boolean; 538} 539interface CheckerResult { 540 count: number 541} 542 543interface WarnCheckerResult { 544 count: number 545} 546 547interface WholeCache { 548 runtimeOS: string, 549 sdkInfo: string, 550 fileList: Cache 551} 552type Cache = Record<string, CacheFileName>; 553export let cache: Cache = {}; 554export const hotReloadSupportFiles: Set<string> = new Set(); 555export const shouldResolvedFiles: Set<string> = new Set(); 556export const appComponentCollection: Map<string, Set<string>> = new Map(); 557const allResolvedModules: Set<string> = new Set(); 558// all files of tsc and rollup for obfuscation scanning. 559export const allSourceFilePaths: Set<string> = new Set(); 560// Used to collect file paths that have not been converted toUnixPath. 561export const allModuleIds: Set<string> = new Set(); 562export let props: Set<string> = new Set(); 563 564export let fastBuildLogger = null; 565 566export const checkerResult: CheckerResult = { count: 0 }; 567export const warnCheckerResult: WarnCheckerResult = { count: 0 }; 568export let languageService: ts.LanguageService = null; 569let tsImportSendable: boolean = false; 570let skipOhModulesLint: boolean = false; 571let mixCompile: boolean = false; 572export let maxMemoryInServiceChecker: number = 0; 573export function serviceChecker(rootFileNames: string[], newLogger: Object = null, resolveModulePaths: string[] = null, 574 parentEvent?: CompileEvent, rollupShareObject?: Object): void { 575 fastBuildLogger = newLogger; 576 let cacheFile: string = null; 577 tsImportSendable = rollupShareObject?.projectConfig.tsImportSendable; 578 skipOhModulesLint = rollupShareObject?.projectConfig.skipOhModulesLint; 579 mixCompile = rollupShareObject?.projectConfig.mixCompile; 580 if (projectConfig.xtsMode || process.env.watchMode === 'true') { 581 if (projectConfig.hotReload) { 582 rootFileNames.forEach(fileName => { 583 hotReloadSupportFiles.add(fileName); 584 }); 585 } 586 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.CREATE_LANGUAGE_SERVICE); 587 languageService = createLanguageService(rootFileNames, resolveModulePaths, parentEvent); 588 MemoryMonitor.stopRecordStage(recordInfo); 589 props = languageService.getProps(); 590 } else { 591 cacheFile = path.resolve(projectConfig.cachePath, '../.ts_checker_cache'); 592 const [isJsonObject, cacheJsonObject]: [boolean, WholeCache | undefined] = isJsonString(cacheFile); 593 const wholeCache: WholeCache = isJsonObject ? cacheJsonObject : { 'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {} }; 594 if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) { 595 cache = wholeCache.fileList; 596 } else { 597 cache = {}; 598 } 599 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.CREATE_LANGUAGE_SERVICE); 600 languageService = createLanguageService(rootFileNames, resolveModulePaths, parentEvent, rollupShareObject); 601 MemoryMonitor.stopRecordStage(recordInfo); 602 } 603 604 const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance(); 605 timePrinterInstance.setArkTSTimePrintSwitch(false); 606 timePrinterInstance.appendTime(ts.TimePhase.START); 607 ts.PerformanceDotting.startAdvanced('createProgram'); 608 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.GET_BUILDER_PROGRAM); 609 610 globalProgram.builderProgram = languageService.getBuilderProgram(/*withLinterProgram*/ true); 611 globalProgram.program = globalProgram.builderProgram.getProgram(); 612 traverseProgramSourceFiles(languageService.getProps()); 613 props = languageService.getProps(); 614 timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM); 615 MemoryMonitor.stopRecordStage(recordInfo); 616 ts.PerformanceDotting.stopAdvanced('createProgram'); 617 618 collectAllFiles(globalProgram.program, undefined, undefined, rollupShareObject); 619 collectFileToIgnoreDiagnostics(rootFileNames); 620 ts.PerformanceDotting.startAdvanced('runArkTSLinterTime'); 621 const runArkTSLinterRecordInfo = MemoryMonitor.recordStage(MemoryDefine.RUN_ARK_TS_LINTER); 622 const errorCodeLogger: Object | undefined = !!rollupShareObject?.getHvigorConsoleLogger ? 623 rollupShareObject?.getHvigorConsoleLogger(LINTER_SUBSYSTEM_CODE) : undefined; 624 runArkTSLinter(errorCodeLogger, parentEvent); 625 MemoryMonitor.stopRecordStage(runArkTSLinterRecordInfo); 626 ts.PerformanceDotting.stopAdvanced('runArkTSLinterTime'); 627 628 if (process.env.watchMode !== 'true') { 629 const processBuildHaprrecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP); 630 processBuildHap(cacheFile, rootFileNames, parentEvent, rollupShareObject); 631 MemoryMonitor.stopRecordStage(processBuildHaprrecordInfo); 632 } 633 634 maxMemoryInServiceChecker = process.memoryUsage().heapUsed; 635 // Release the typeChecker early and perform GC in the following scenarios: 636 // In memory-priority mode or default mode, when the preview mode is disabled in a full compilation scenario, 637 // and it is not a preview, hot reload, or cold reload scenario. The typeChecker is not released early in performance-priority mode. 638 let shouldReleaseTypeChecker: boolean = rollupShareObject?.projectConfig?.executionMode !== 'performance' && globalProgram.program && 639 process.env.watchMode !== 'true' && !projectConfig.isPreview && !projectConfig.hotReload && !projectConfig.coldReload; 640 if (shouldReleaseTypeChecker) { 641 globalProgram.program.releaseTypeChecker(); 642 const allowGC: boolean = global && global.gc && typeof global.gc === 'function'; 643 if (allowGC) { 644 global.gc(); 645 } 646 } 647} 648 649export function traverseProgramSourceFiles(props: Set<string>): void { 650 globalProgram.program.getSourceFiles().forEach((sourceFile: ts.SourceFile) => { 651 checkUISyntax(sourceFile, sourceFile.fileName, [], props); 652 }); 653} 654 655function isJsonString(cacheFile: string): [boolean, WholeCache | undefined] { 656 if (fs.existsSync(cacheFile)) { 657 try { 658 return [true, JSON.parse(fs.readFileSync(cacheFile).toString())]; 659 } catch (e) { 660 return [false, undefined]; 661 } 662 } else { 663 return [false, undefined]; 664 } 665} 666 667// collect the compiled files of tsc and rollup for obfuscation scanning. 668export function collectAllFiles(program?: ts.Program, rollupFileList?: IterableIterator<string>, 669 rollupObject?: Object, rollupShareObject: Object = null): void { 670 if (program) { 671 collectTscFiles(program, rollupShareObject); 672 return; 673 } 674 mergeRollUpFiles(rollupFileList, rollupObject); 675} 676 677export function collectTscFiles(program: ts.Program, rollupShareObject: Object = null): void { 678 const programAllFiles: readonly ts.SourceFile[] = program.getSourceFiles(); 679 let projectRootPath: string = projectConfig.projectRootPath; 680 if (!projectRootPath) { 681 return; 682 } 683 projectRootPath = toUnixPath(projectRootPath); 684 const isMacOrWin = isWindows() || isMac(); 685 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.COLLECT_TSC_FILES_ALL_RESOLVED_MODULES); 686 programAllFiles.forEach(sourceFile => { 687 const fileName = toUnixPath(sourceFile.fileName); 688 // @ts-ignore 689 sourceFileDependencies.set(fileName, sourceFile.resolvedModules); 690 if (!(fileName.startsWith(projectRootPath + '/') || isOtherProjectResolvedModulesFilePaths(rollupShareObject, fileName))) { 691 return; 692 } 693 allSourceFilePaths.add(fileName); 694 allModuleIds.add(sourceFile.fileName); 695 // For the windos and mac platform, the file path in filesBuildInfo is in lowercase, 696 // while fileName of sourceFile is the original path (including uppercase). 697 if (filesBuildInfo.size > 0 && 698 Reflect.get(sourceFile, 'version') !== filesBuildInfo.get(isMacOrWin ? tryToLowerCasePath(fileName) : fileName)) { 699 allResolvedModules.add(fileName); 700 } 701 }); 702 MemoryMonitor.stopRecordStage(recordInfo); 703} 704 705function isOtherProjectResolvedModulesFilePaths(rollupShareObject: Object, fileName: string): boolean { 706 if (!!rollupShareObject && rollupShareObject.projectConfig && !!rollupShareObject.projectConfig.rootPathSet) { 707 const rootPathSet: string[] | Set<string> = rollupShareObject.projectConfig.rootPathSet; 708 if (Array.isArray(rootPathSet)) { 709 for (let i = 0; i < rootPathSet.length; i++) { 710 const pathNormalization: string = toUnixPath(rootPathSet[i]) + '/'; 711 if (fileName.startsWith(pathNormalization)) { 712 return true; 713 } 714 } 715 } else { 716 return false; 717 } 718 } 719 return false; 720} 721 722export function mergeRollUpFiles(rollupFileList: IterableIterator<string>, rollupObject: Object): void { 723 const recordInfo = MemoryMonitor.recordStage(MemoryDefine.MERGE_ROLL_UP_FILES_LOCAL_PACKAGE_SET); 724 for (const moduleId of rollupFileList) { 725 if (fs.existsSync(moduleId)) { 726 allSourceFilePaths.add(toUnixPath(moduleId)); 727 allModuleIds.add(moduleId); 728 addLocalPackageSet(moduleId, rollupObject); 729 } 730 } 731 MemoryMonitor.stopRecordStage(recordInfo); 732} 733 734// collect the modulename or pkgname of all local modules. 735export function addLocalPackageSet(moduleId: string, rollupObject: Object): void { 736 const moduleInfo: Object = rollupObject.getModuleInfo(moduleId); 737 const metaInfo: Object = moduleInfo.meta; 738 if (metaInfo.isLocalDependency) { 739 if (projectConfig.useNormalizedOHMUrl && metaInfo.pkgName) { 740 localPackageSet.add(metaInfo.pkgName); 741 } 742 if (!projectConfig.useNormalizedOHMUrl && metaInfo.moduleName) { 743 localPackageSet.add(metaInfo.moduleName); 744 } 745 } 746} 747 748export function emitBuildInfo(): void { 749 globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile); 750} 751 752function processBuildHap(cacheFile: string, rootFileNames: string[], parentEvent: CompileEvent, 753 rollupShareObject: Object): void { 754 ts.PerformanceDotting.startAdvanced('diagnostic'); 755 const semanticRecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP_GET_SEMANTIC_DIAGNOSTICS); 756 const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram 757 .getSyntacticDiagnostics() 758 .concat(globalProgram.builderProgram.getSemanticDiagnostics()); 759 MemoryMonitor.stopRecordStage(semanticRecordInfo); 760 ts.PerformanceDotting.stopAdvanced('diagnostic'); 761 const emitBuildRecordInfo = MemoryMonitor.recordStage(MemoryDefine.PROCESS_BUILD_HAP_EMIT_BUILD_INFO); 762 emitBuildInfo(); 763 let errorCodeLogger: Object | undefined = rollupShareObject?.getHvigorConsoleLogger ? 764 rollupShareObject?.getHvigorConsoleLogger(TSC_SYSTEM_CODE) : undefined; 765 766 allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 767 printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger); 768 }); 769 MemoryMonitor.stopRecordStage(emitBuildRecordInfo); 770 if (!projectConfig.xtsMode) { 771 fse.ensureDirSync(projectConfig.cachePath); 772 fs.writeFileSync(cacheFile, JSON.stringify({ 773 'runtimeOS': projectConfig.runtimeOS, 774 'sdkInfo': projectConfig.sdkInfo, 775 'fileList': cache 776 }, null, 2)); 777 } 778 if (projectConfig.compileHar || projectConfig.compileShared) { 779 let emit: string | undefined = undefined; 780 let writeFile = (fileName: string, text: string, writeByteOrderMark: boolean): void => { 781 emit = text; 782 }; 783 [...allResolvedModules, ...rootFileNames].forEach(moduleFile => { 784 if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar)) { 785 try { 786 if ((/\.d\.e?ts$/).test(moduleFile)) { 787 generateSourceFilesInHar(moduleFile, fs.readFileSync(moduleFile, 'utf-8'), path.extname(moduleFile), 788 projectConfig, projectConfig.modulePathMap); 789 } else if ((/\.e?ts$/).test(moduleFile)) { 790 emit = undefined; 791 let sourcefile = globalProgram.program.getSourceFile(moduleFile); 792 if (sourcefile) { 793 globalProgram.program.emit(sourcefile, writeFile, undefined, true, undefined, true); 794 } 795 if (emit) { 796 generateSourceFilesInHar(moduleFile, emit, '.d' + path.extname(moduleFile), projectConfig, projectConfig.modulePathMap); 797 } 798 } 799 } catch (err) { } 800 } 801 }); 802 printDeclarationDiagnostics(errorCodeLogger); 803 } 804} 805 806function printDeclarationDiagnostics(errorCodeLogger?: Object | undefined): void { 807 globalProgram.builderProgram.getDeclarationDiagnostics().forEach((diagnostic: ts.Diagnostic) => { 808 printDiagnostic(diagnostic, ErrorCodeModule.TSC, errorCodeLogger); 809 }); 810} 811 812function containFormError(message: string): boolean { 813 if (/can't support form application./.test(message)) { 814 return true; 815 } 816 return false; 817} 818 819let fileToIgnoreDiagnostics: Set<string> | undefined = undefined; 820 821function collectFileToThrowDiagnostics(file: string, fileToThrowDiagnostics: Set<string>): void { 822 const normalizedFilePath: string = path.resolve(file); 823 const unixFilePath: string = toUnixPath(file); 824 if (fileToThrowDiagnostics.has(unixFilePath)) { 825 return; 826 } 827 828 fileToThrowDiagnostics.add(unixFilePath); 829 // Although the cache object filters JavaScript files when collecting dependency relationships, we still include the 830 // filtering of JavaScript files here to avoid potential omissions. 831 if ((/\.(c|m)?js$/).test(file) || 832 !cache[normalizedFilePath] || cache[normalizedFilePath].children.length === 0) { 833 return; 834 } 835 cache[normalizedFilePath].children.forEach(file => { 836 collectFileToThrowDiagnostics(file, fileToThrowDiagnostics); 837 }); 838} 839 840export function collectFileToIgnoreDiagnostics(rootFileNames: string[]): void { 841 if (getArkTSLinterMode() === ArkTSLinterMode.NOT_USE) { 842 return; 843 } 844 845 // In watch mode, the `beforeBuild` phase will clear the parent and children fields in the cache. For files that have 846 // not been modified, the information needs to be restored using the `resolvedModuleNames` variable. 847 if (process.env.watchMode === 'true') { 848 for (let [file, resolvedModules] of resolvedModulesCache) { 849 createOrUpdateCache(resolvedModules, file); 850 } 851 } 852 853 // With arkts linter enabled, `allowJs` option is set to true, resulting JavaScript files themselves and 854 // JavaScript-referenced files are included in the tsc program and checking process, 855 // potentially introducing new errors. For instance, in scenarios where an ets file imports js file imports ts file, 856 // it’s necessary to filter out errors from ts files. 857 let fileToThrowDiagnostics: Set<string> = new Set<string>(); 858 rootFileNames.forEach(file => { 859 if (!(/\.(c|m)?js$/).test(file)) { 860 collectFileToThrowDiagnostics(file, fileToThrowDiagnostics); 861 } 862 }); 863 864 let resolvedTypeReferenceDirectivesFiles: Set<string> = new Set<string>(); 865 globalProgram.program.getResolvedTypeReferenceDirectives().forEach( 866 (elem: ts.ResolvedTypeReferenceDirective | undefined) => { 867 elem && elem.resolvedFileName && resolvedTypeReferenceDirectivesFiles.add(elem.resolvedFileName); 868 }); 869 870 const ignoreDiagnosticsRecordInfo = MemoryMonitor.recordStage(MemoryDefine.FILE_TO_IGNORE_DIAGNOSTICS); 871 fileToIgnoreDiagnostics = new Set<string>(); 872 globalProgram.program.getSourceFiles().forEach(sourceFile => { 873 // Previous projects had js libraries that were available through SDK, so need to filter js-file in SDK, 874 // like: hypium library 875 sourceFile.fileName && 876 (!isInSDK(sourceFile.fileName) || (/\.(c|m)?js$/).test(sourceFile.fileName)) && 877 !resolvedTypeReferenceDirectivesFiles.has(sourceFile.fileName) && 878 fileToIgnoreDiagnostics.add(toUnixPath(sourceFile.fileName)); 879 }); 880 881 fileToThrowDiagnostics.forEach(file => { 882 fileToIgnoreDiagnostics.delete(file); 883 }); 884 MemoryMonitor.stopRecordStage(ignoreDiagnosticsRecordInfo); 885} 886 887interface MessageCollection { 888 positionMessage: string, 889 message: string, 890 logMessage: string 891} 892 893export function printDiagnostic(diagnostic: ts.Diagnostic, flag?: ErrorCodeModule, errorCodeLogger?: Object | undefined): void { 894 if (projectConfig.ignoreWarning) { 895 return; 896 } 897 898 if (fileToIgnoreDiagnostics && diagnostic.file && diagnostic.file.fileName && 899 fileToIgnoreDiagnostics.has(toUnixPath(diagnostic.file.fileName))) { 900 return; 901 } 902 903 const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); 904 if (validateError(message)) { 905 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 906 updateErrorFileCache(diagnostic); 907 } 908 909 if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) { 910 return; 911 } 912 913 // validate whether the message matches new Extend error, and modify its level for compatibility 914 if (validateNewExtend(message)) { 915 diagnostic.category = ts.DiagnosticCategory.warning; 916 } 917 const logPrefix: string = diagnostic.category === ts.DiagnosticCategory.Error ? 'ERROR' : 'WARN'; 918 const etsCheckerLogger = fastBuildLogger || logger; 919 let logMessage: string; 920 if (logPrefix === 'ERROR') { 921 checkerResult.count += 1; 922 } else { 923 warnCheckerResult.count += 1; 924 } 925 let positionMessage: string = ''; 926 if (diagnostic.file) { 927 const { line, character }: ts.LineAndCharacter = 928 diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); 929 positionMessage = `File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}`; 930 logMessage = `ArkTS:${logPrefix} ${positionMessage}\n ${message}\n`; 931 } else { 932 logMessage = `ArkTS:${logPrefix}: ${message}`; 933 } 934 935 if (errorCodeLogger) { 936 const msgCollection: MessageCollection = { positionMessage, message, logMessage }; 937 printErrorCode(diagnostic, etsCheckerLogger, msgCollection, errorCodeLogger, flag); 938 } else { 939 if (diagnostic.category === ts.DiagnosticCategory.Error) { 940 etsCheckerLogger.error('\u001b[31m' + logMessage); 941 } else { 942 etsCheckerLogger.warn('\u001b[33m' + logMessage); 943 } 944 } 945 } 946} 947 948function printErrorCode(diagnostic: ts.Diagnostic, etsCheckerLogger: Object, 949 msgCollection: MessageCollection, errorCodeLogger: Object, flag: ErrorCodeModule | undefined): void { 950 const { positionMessage, message, logMessage } = msgCollection; 951 // If the diagnostic is not an error, log a warning and return early. 952 if (diagnostic.category !== ts.DiagnosticCategory.Error) { 953 etsCheckerLogger.warn('\u001b[33m' + logMessage); 954 return; 955 } 956 957 // Check for TSC error codes 958 if (flag === ErrorCodeModule.TSC && 959 validateUseErrorCodeLogger(ErrorCodeModule.TSC, diagnostic.code)) { 960 const errorCode = ts.getErrorCode(diagnostic); 961 errorCodeLogger.printError(errorCode); 962 return; 963 } 964 965 // Check for LINTER error codes 966 if (flag === ErrorCodeModule.LINTER || (flag === ErrorCodeModule.TSC && 967 validateUseErrorCodeLogger(ErrorCodeModule.LINTER, diagnostic.code))) { 968 const linterErrorInfo: HvigorErrorInfo = transfromErrorCode(diagnostic.code, positionMessage, message); 969 errorCodeLogger.printError(linterErrorInfo); 970 return; 971 } 972 973 // Check for ArkUI error codes 974 if (flag === ErrorCodeModule.UI || (flag === ErrorCodeModule.TSC && 975 validateUseErrorCodeLogger(ErrorCodeModule.UI, diagnostic.code))) { 976 const uiErrorInfo: HvigorErrorInfo | undefined = buildErrorInfoFromDiagnostic( 977 diagnostic.code, positionMessage, message); 978 if (!uiErrorInfo) { 979 etsCheckerLogger.error('\u001b[31m' + logMessage); 980 } else { 981 errorCodeLogger.printError(uiErrorInfo); 982 } 983 return; 984 } 985 986 // If the error is not a TSC/Linter/ArkUI error, log using etsCheckerLogger 987 etsCheckerLogger.error('\u001b[31m' + logMessage); 988} 989 990function validateUseErrorCodeLogger(flag: ErrorCodeModule, code: number): boolean { 991 if (!ts.getErrorCodeArea || !ts.getErrorCode) { 992 return false; 993 } 994 if (flag === ErrorCodeModule.TSC) { 995 return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.TSC; 996 } else if (flag === ErrorCodeModule.LINTER) { 997 return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.LINTER; 998 } else if (flag === ErrorCodeModule.UI) { 999 return ts.getErrorCodeArea(code) === ts.ErrorCodeArea.UI; 1000 } 1001 return false; 1002} 1003 1004function validateError(message: string): boolean { 1005 const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/; 1006 const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/; 1007 if (matchMessage(message, props, propInfoReg) || 1008 matchMessage(message, props, stateInfoReg)) { 1009 return false; 1010 } 1011 return true; 1012} 1013 1014 1015/** 1016 * validate whether ets diagnostic has collected the error about new Extend function 1017 * @param {string} message 1018 * @return {*} {boolean} 1019 */ 1020function validateNewExtend(message: string): boolean { 1021 const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/; 1022 if (matchMessage(message, newExtendCollection, stateInfoReg)) { 1023 return true; 1024 } 1025 return false; 1026} 1027 1028function matchMessage(message: string, nameArr: Set<string>, reg: RegExp): boolean { 1029 if (reg.test(message)) { 1030 const match: string[] = message.match(reg); 1031 if (match[1] && nameArr.has(match[1])) { 1032 return true; 1033 } 1034 } 1035 return false; 1036} 1037 1038function updateErrorFileCache(diagnostic: ts.Diagnostic): void { 1039 if (!diagnostic.file) { 1040 return; 1041 } 1042 1043 let cacheInfo: CacheFileName = cache[path.resolve(diagnostic.file.fileName)]; 1044 if (cacheInfo) { 1045 cacheInfo.error = true; 1046 if (!cacheInfo.errorCodes) { 1047 cacheInfo.errorCodes = []; 1048 } 1049 cacheInfo.errorCodes.includes(diagnostic.code) || cacheInfo.errorCodes.push(diagnostic.code); 1050 } 1051} 1052 1053function filterInput(rootFileNames: string[]): string[] { 1054 return rootFileNames.filter((file: string) => { 1055 const needUpdate: NeedUpdateFlag = { flag: false }; 1056 const alreadyCheckedFiles: Set<string> = new Set(); 1057 checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles); 1058 if (!needUpdate.flag) { 1059 storedFileInfo.changeFiles.push(path.resolve(file)); 1060 } 1061 return needUpdate.flag; 1062 }); 1063} 1064 1065function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set<string>): void { 1066 if (alreadyCheckedFiles.has(file)) { 1067 return; 1068 } else { 1069 alreadyCheckedFiles.add(file); 1070 } 1071 1072 if (needUpdate.flag) { 1073 return; 1074 } 1075 1076 const value: CacheFileName = cache[file]; 1077 const mtimeMs: number = fs.statSync(file).mtimeMs; 1078 if (value) { 1079 if (value.error || value.mtimeMs !== mtimeMs) { 1080 needUpdate.flag = true; 1081 return; 1082 } 1083 for (let i = 0; i < value.children.length; ++i) { 1084 if (fs.existsSync(value.children[i])) { 1085 checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles); 1086 } else { 1087 needUpdate.flag = true; 1088 } 1089 } 1090 } else { 1091 cache[file] = { mtimeMs, children: [], parent: [], error: false }; 1092 needUpdate.flag = true; 1093 } 1094} 1095 1096const fileExistsCache: Map<string, boolean> = new Map<string, boolean>(); 1097const dirExistsCache: Map<string, boolean> = new Map<string, boolean>(); 1098const moduleResolutionHost: ts.ModuleResolutionHost = { 1099 fileExists: (fileName: string): boolean => { 1100 let exists = fileExistsCache.get(fileName); 1101 if (exists === undefined) { 1102 exists = ts.sys.fileExists(fileName); 1103 fileExistsCache.set(fileName, exists); 1104 } 1105 return exists; 1106 }, 1107 directoryExists: (directoryName: string): boolean => { 1108 let exists = dirExistsCache.get(directoryName); 1109 if (exists === undefined) { 1110 exists = ts.sys.directoryExists(directoryName); 1111 dirExistsCache.set(directoryName, exists); 1112 } 1113 return exists; 1114 }, 1115 readFile(fileName: string): string | undefined { 1116 return ts.sys.readFile(fileName); 1117 }, 1118 realpath(path: string): string { 1119 return ts.sys.realpath(path); 1120 }, 1121 trace(s: string): void { 1122 console.info(s); 1123 } 1124}; 1125 1126//This is only for test 1127export const moduleResolutionHostTest = moduleResolutionHost; 1128 1129export function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | ts.FileReference[]): ts.ResolvedTypeReferenceDirective[] { 1130 if (typeDirectiveNames.length === 0) { 1131 return []; 1132 } 1133 1134 const resolvedTypeReferenceCache: ts.ResolvedTypeReferenceDirective[] = []; 1135 const cache: Map<string, ts.ResolvedTypeReferenceDirective> = new Map<string, ts.ResolvedTypeReferenceDirective>(); 1136 const containingFile: string = path.join(projectConfig.modulePath, 'build-profile.json5'); 1137 1138 for (const entry of typeDirectiveNames) { 1139 const typeName = isString(entry) ? entry : entry.fileName.toLowerCase(); 1140 if (!cache.has(typeName)) { 1141 const resolvedFile = ts.resolveTypeReferenceDirective(typeName, containingFile, compilerOptions, moduleResolutionHost); 1142 if (!resolvedFile || !resolvedFile.resolvedTypeReferenceDirective) { 1143 logger.error('\u001b[31m', `ArkTS:Cannot find type definition file for: ${typeName}\n`); 1144 } 1145 const result: ts.ResolvedTypeReferenceDirective = resolvedFile.resolvedTypeReferenceDirective; 1146 cache.set(typeName, result); 1147 resolvedTypeReferenceCache.push(result); 1148 } 1149 } 1150 return resolvedTypeReferenceCache; 1151} 1152 1153// resolvedModulesCache records the files and their dependencies of program. 1154export const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map(); 1155 1156export function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] { 1157 ts.PerformanceDotting.startAdvanced('resolveModuleNames'); 1158 const languageVersion = FileManager.mixCompile ? FileManager.getInstance().getLanguageVersionByFilePath(containingFile).languageVersion : ARKTS_1_1; 1159 const resolvedModules: ts.ResolvedModuleFull[] = []; 1160 const cacheFileContent: ts.ResolvedModuleFull[] = resolvedModulesCache.get(path.resolve(containingFile)); 1161 if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile)) || 1162 !(cacheFileContent && cacheFileContent.length === moduleNames.length)) { 1163 for (const moduleName of moduleNames) { 1164 const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost); 1165 if (result.resolvedModule) { 1166 if (result.resolvedModule.resolvedFileName && 1167 path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) { 1168 const resultDETSPath: string = 1169 result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS); 1170 if (ts.sys.fileExists(resultDETSPath)) { 1171 resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS)); 1172 } else { 1173 resolvedModules.push(result.resolvedModule); 1174 } 1175 } else if (isMixCompile() && result.resolvedModule.resolvedFileName && /\.ets$/.test(result.resolvedModule.resolvedFileName) && 1176 !/\.d\.ets$/.test(result.resolvedModule.resolvedFileName)) { 1177 // When result has a value and the path parsed is the source code file path of module 1.2, 1178 // the parsing result needs to be modified to the glue code path of module 1.2 1179 const queryResult = redirectToDeclFileForInterop(result.resolvedModule.resolvedFileName); 1180 if (queryResult) { 1181 resolvedModules.push(queryResult); 1182 } else { 1183 resolvedModules.push(result.resolvedModule); 1184 } 1185 } else { 1186 resolvedModules.push(result.resolvedModule); 1187 } 1188 } else if (new RegExp(`^@(${sdkConfigPrefix})\\.`, 'i').test(moduleName.trim())) { 1189 const apiPaths = sdkConfigs.flatMap(config => config.apiPath); 1190 isMixCompile() && getApiPathForInterop(apiPaths, languageVersion); 1191 const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(apiPaths, moduleName, ['.d.ts', '.d.ets']); 1192 const modulePath = resolveModuleInfo.modulePath; 1193 const extension = resolveModuleInfo.isEts ? '.d.ets' : '.d.ts'; 1194 const fullModuleName = moduleName + extension; 1195 1196 if (systemModules.includes(fullModuleName) && ts.sys.fileExists(modulePath)) { 1197 resolvedModules.push(getResolveModule(modulePath, extension)); 1198 } else if (languageVersion === ARKTS_1_2) { 1199 resolvedModules.push(getResolveModule(modulePath, extension)); 1200 } else { 1201 resolvedModules.push(null); 1202 } 1203 } else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) { 1204 const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); 1205 if (ts.sys.fileExists(modulePath)) { 1206 resolvedModules.push(getResolveModule(modulePath, '.ets')); 1207 } else { 1208 resolvedModules.push(null); 1209 } 1210 } else if (/\.ts$/.test(moduleName)) { 1211 const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); 1212 if (ts.sys.fileExists(modulePath)) { 1213 resolvedModules.push(getResolveModule(modulePath, '.ts')); 1214 } else { 1215 resolvedModules.push(null); 1216 } 1217 } else { 1218 const aliasConfig = FileManager.getInstance().queryOriginApiName(moduleName, containingFile); 1219 if (aliasConfig) { 1220 const searchPaths = aliasConfig.isStatic 1221 ? Array.from(FileManager.staticSDKDeclPath) 1222 : [...new Set(sdkConfigs.flatMap(config => config.apiPath))]; 1223 const resolveModuleInfo = getRealModulePath(searchPaths, aliasConfig.originalAPIName, ['.d.ts', '.d.ets']); 1224 const modulePath = resolveModuleInfo.modulePath; 1225 const extension = resolveModuleInfo.isEts ? '.d.ets' : '.d.ts'; 1226 resolvedModules.push(getResolveModule(modulePath, extension)); 1227 } else { 1228 const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts'); 1229 const systemDETSModulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ets'); 1230 const kitModulePath: string = path.resolve(__dirname, '../../../kits', moduleName + '.d.ts'); 1231 const kitSystemDETSModulePath: string = path.resolve(__dirname, '../../../kits', moduleName + '.d.ets'); 1232 const suffix: string = /\.js$/.test(moduleName) ? '' : '.js'; 1233 const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix); 1234 const fileModulePath: string = 1235 path.resolve(__dirname, '../node_modules', moduleName + '/index.js'); 1236 const DETSModulePath: string = path.resolve(path.dirname(containingFile), 1237 /\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS); 1238 const arktsEvoDeclFilePath: string = isMixCompile() ? 1239 getArkTSEvoDeclFilePath({ moduleRequest: moduleName, resolvedFileName: '' }) : ''; 1240 if (ts.sys.fileExists(modulePath)) { 1241 resolvedModules.push(getResolveModule(modulePath, '.d.ts')); 1242 } else if (ts.sys.fileExists(systemDETSModulePath)) { 1243 resolvedModules.push(getResolveModule(systemDETSModulePath, '.d.ets')); 1244 } else if (ts.sys.fileExists(kitModulePath)) { 1245 resolvedModules.push(getResolveModule(kitModulePath, '.d.ts')); 1246 } else if (ts.sys.fileExists(kitSystemDETSModulePath)) { 1247 resolvedModules.push(getResolveModule(kitSystemDETSModulePath, '.d.ets')); 1248 } else if (ts.sys.fileExists(jsModulePath)) { 1249 resolvedModules.push(getResolveModule(jsModulePath, '.js')); 1250 } else if (ts.sys.fileExists(fileModulePath)) { 1251 resolvedModules.push(getResolveModule(fileModulePath, '.js')); 1252 } else if (ts.sys.fileExists(DETSModulePath)) { 1253 resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets')); 1254 } else if (isMixCompile() && ts.sys.fileExists(arktsEvoDeclFilePath)) { 1255 resolvedModules.push(getResolveModule(arktsEvoDeclFilePath, '.d.ets')); 1256 } else { 1257 const srcIndex: number = projectConfig.projectPath.indexOf('src' + path.sep + 'main'); 1258 let DETSModulePathFromModule: string; 1259 if (srcIndex > 0) { 1260 DETSModulePathFromModule = path.resolve( 1261 projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS); 1262 if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) { 1263 resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets')); 1264 } else { 1265 resolvedModules.push(null); 1266 } 1267 } else { 1268 resolvedModules.push(null); 1269 } 1270 } 1271 } 1272 } 1273 if (projectConfig.hotReload && resolvedModules.length && 1274 resolvedModules[resolvedModules.length - 1]) { 1275 hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName)); 1276 } 1277 if (collectShouldPackedFiles(resolvedModules)) { 1278 allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName); 1279 } 1280 } 1281 if (!projectConfig.xtsMode) { 1282 createOrUpdateCache(resolvedModules, path.resolve(containingFile)); 1283 } 1284 resolvedModulesCache.set(path.resolve(containingFile), resolvedModules); 1285 ts.PerformanceDotting.stopAdvanced('resolveModuleNames'); 1286 return resolvedModules; 1287 1288 } 1289 ts.PerformanceDotting.stopAdvanced('resolveModuleNames'); 1290 return resolvedModulesCache.get(path.resolve(containingFile)); 1291} 1292 1293export interface ResolveModuleInfo { 1294 modulePath: string; 1295 isEts: boolean; 1296} 1297 1298function collectShouldPackedFiles(resolvedModules: ts.ResolvedModuleFull[]): boolean | RegExpMatchArray { 1299 return (projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] && 1300 resolvedModules[resolvedModules.length - 1].resolvedFileName && 1301 (path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/) || 1302 path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/\.d\.e?ts$/) && 1303 path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match( 1304 new RegExp('\\' + path.sep + 'src' + '\\' + path.sep + 'main' + '\\' + path.sep))); 1305} 1306 1307function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void { 1308 const children: string[] = []; 1309 const error: boolean = false; 1310 resolvedModules.forEach(moduleObj => { 1311 if (moduleObj && moduleObj.resolvedFileName && /\.(ets|ts)$/.test(moduleObj.resolvedFileName)) { 1312 const file: string = path.resolve(moduleObj.resolvedFileName); 1313 const mtimeMs: number = fs.statSync(file).mtimeMs; 1314 children.push(file); 1315 const value: CacheFileName = cache[file]; 1316 if (value) { 1317 value.mtimeMs = mtimeMs; 1318 value.error = error; 1319 value.parent = value.parent || []; 1320 value.parent.push(path.resolve(containingFile)); 1321 value.parent = [...new Set(value.parent)]; 1322 } else { 1323 cache[file] = { mtimeMs, children: [], parent: [containingFile], error }; 1324 } 1325 } 1326 }); 1327 cache[path.resolve(containingFile)] = { 1328 mtimeMs: fs.statSync(containingFile).mtimeMs, children, 1329 parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ? 1330 cache[path.resolve(containingFile)].parent : [], error 1331 }; 1332} 1333 1334export function createWatchCompilerHost(rootFileNames: string[], 1335 reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function, 1336 isPipe: boolean = false, resolveModulePaths: string[] = null): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> { 1337 if (projectConfig.hotReload) { 1338 rootFileNames.forEach(fileName => { 1339 hotReloadSupportFiles.add(fileName); 1340 }); 1341 } 1342 if (!(isPipe && process.env.compileTool === 'rollup')) { 1343 setCompilerOptions(resolveModulePaths); 1344 } 1345 // Change the buildInfo file path, or it will cover the buildInfo file created before. 1346 const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', WATCH_COMPILER_BUILD_INFO_SUFFIX); 1347 const watchCompilerOptions = {...compilerOptions, tsBuildInfoFile: buildInfoPath}; 1348 const createProgram = ts.createSemanticDiagnosticsBuilderProgram; 1349 const host = ts.createWatchCompilerHost( 1350 [...rootFileNames, ...readDeaclareFiles()], watchCompilerOptions, 1351 ts.sys, createProgram, reportDiagnostic, 1352 (diagnostic: ts.Diagnostic) => { 1353 if ([6031, 6032].includes(diagnostic.code)) { 1354 if (!isPipe) { 1355 process.env.watchTs = 'start'; 1356 resetErrorCount(); 1357 } 1358 } 1359 // End of compilation in watch mode flag. 1360 if ([6193, 6194].includes(diagnostic.code)) { 1361 if (!isPipe) { 1362 process.env.watchTs = 'end'; 1363 if (fastBuildLogger) { 1364 fastBuildLogger.debug(TS_WATCH_END_MSG); 1365 tsWatchEmitter.emit(TS_WATCH_END_MSG); 1366 } 1367 } 1368 delayPrintLogCount(); 1369 } 1370 }); 1371 host.readFile = (fileName: string) => { 1372 if (!fs.existsSync(fileName)) { 1373 return undefined; 1374 } 1375 if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) { 1376 let content: string = processContent(fs.readFileSync(fileName).toString(), fileName); 1377 const extendFunctionInfo: extendInfo[] = []; 1378 content = instanceInsteadThis(content, fileName, extendFunctionInfo, props); 1379 return content; 1380 } 1381 return fs.readFileSync(fileName).toString(); 1382 }; 1383 host.resolveModuleNames = resolveModuleNames; 1384 host.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives; 1385 return host; 1386} 1387 1388export function watchChecker(rootFileNames: string[], newLogger: any = null, resolveModulePaths: string[] = null): void { 1389 fastBuildLogger = newLogger; 1390 globalProgram.watchProgram = ts.createWatchProgram( 1391 createWatchCompilerHost(rootFileNames, printDiagnostic, () => { }, () => { }, false, resolveModulePaths)); 1392} 1393 1394export function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[], 1395 props: Set<string>): string { 1396 extendFunctionInfo.reverse().forEach((item) => { 1397 const subStr: string = content.substring(item.start, item.end); 1398 const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => { 1399 return item1 + item.compName + 'Instance' + item2; 1400 }); 1401 content = content.slice(0, item.start) + insert + content.slice(item.end); 1402 }); 1403 return content; 1404} 1405 1406export function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull { 1407 return { 1408 resolvedFileName: modulePath, 1409 isExternalLibraryImport: false, 1410 extension: type 1411 }; 1412} 1413 1414export const dollarCollection: Set<string> = new Set(); 1415export const extendCollection: Set<string> = new Set(); 1416// a new set defined to collect @Extend functions with new format 1417const newExtendCollection: Set<string> = new Set(); 1418export const importModuleCollection: Set<string> = new Set(); 1419 1420function checkUISyntax(sourceFile: ts.SourceFile, fileName: string, extendFunctionInfo: extendInfo[], 1421 props: Set<string>): void { 1422 if (/\.ets$/.test(fileName) && !/\.d.ets$/.test(fileName)) { 1423 if (process.env.compileMode === 'moduleJson' || 1424 path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) { 1425 collectComponents(sourceFile); 1426 collectionCustomizeStyles(sourceFile); 1427 parseAllNode(sourceFile, sourceFile, extendFunctionInfo); 1428 dollarCollection.forEach((item) => { 1429 props.add(item); 1430 }); 1431 extendCollection.forEach((item) => { 1432 props.add(item); 1433 }); 1434 } 1435 } 1436} 1437 1438function collectionCustomizeStyles(node: ts.Node): void { 1439 if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) && 1440 (isUIDecorator(node, COMPONENT_STYLES_DECORATOR) || isUIDecorator(node, COMPONENT_EXTEND_DECORATOR)) && 1441 node.name && ts.isIdentifier(node.name)) { 1442 BUILDIN_STYLE_NAMES.add(node.name.escapedText.toString()); 1443 } 1444 if (ts.isSourceFile(node)) { 1445 node.statements.forEach((item: ts.Node) => { 1446 return collectionCustomizeStyles(item); 1447 }); 1448 } else if (ts.isStructDeclaration(node)) { 1449 node.members.forEach((item: ts.Node) => { 1450 return collectionCustomizeStyles(item); 1451 }); 1452 } 1453} 1454 1455function isUIDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 1456 ts.StructDeclaration | ts.ClassDeclaration, decortorName: string): boolean { 1457 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 1458 if (decorators && decorators.length) { 1459 for (let i = 0; i < decorators.length; i++) { 1460 const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); 1461 if (originalDecortor === decortorName) { 1462 return true; 1463 } else { 1464 return false; 1465 } 1466 } 1467 } 1468 return false; 1469} 1470function collectComponents(node: ts.SourceFile): void { 1471 // @ts-ignore 1472 if (process.env.watchMode !== 'true' && node.identifiers && node.identifiers.size) { 1473 // @ts-ignore 1474 for (const key of node.identifiers.keys()) { 1475 if (JS_BIND_COMPONENTS.has(key)) { 1476 appComponentCollection.get(path.join(node.fileName)).add(key); 1477 } 1478 } 1479 } 1480} 1481 1482function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void { 1483 if (ts.isStructDeclaration(node)) { 1484 if (node.members) { 1485 node.members.forEach(item => { 1486 if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { 1487 const propertyName: string = item.name.getText(); 1488 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); 1489 if (decorators && decorators.length) { 1490 for (let i = 0; i < decorators.length; i++) { 1491 const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); 1492 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 1493 dollarCollection.add('$' + propertyName); 1494 } 1495 } 1496 } 1497 } 1498 }); 1499 } 1500 } 1501 if (process.env.watchMode !== 'true' && ts.isIfStatement(node)) { 1502 appComponentCollection.get(path.join(sourceFileNode.fileName)).add(COMPONENT_IF); 1503 } 1504 if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION || 1505 (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) && 1506 hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { 1507 if (node.body && node.body.statements && node.body.statements.length) { 1508 const checkProp: ts.NodeArray<ts.Statement> = node.body.statements; 1509 checkProp.forEach((item, index) => { 1510 traverseBuild(item, index); 1511 }); 1512 } 1513 } 1514 if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) { 1515 if (node.body && node.body.statements && node.body.statements.length && 1516 !isOriginalExtend(node.body)) { 1517 extendFunctionInfo.push({ 1518 start: node.pos, 1519 end: node.end, 1520 compName: isExtendFunction(node, { decoratorName: '', componentName: '' }) 1521 }); 1522 } 1523 } 1524 ts.forEachChild(node, (child: ts.Node) => parseAllNode(child, sourceFileNode, extendFunctionInfo)); 1525} 1526 1527function isForeachAndLzayForEach(node: ts.Node): boolean { 1528 return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && 1529 FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] && 1530 ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body); 1531} 1532 1533function getComponentName(node: ts.Node): string { 1534 let temp = node.expression; 1535 let name: string; 1536 while (temp) { 1537 if (ts.isIdentifier(temp) && temp.parent && (ts.isCallExpression(temp.parent) || 1538 ts.isEtsComponentExpression(temp.parent))) { 1539 name = temp.escapedText.toString(); 1540 break; 1541 } 1542 temp = temp.expression; 1543 } 1544 return name; 1545} 1546 1547function traverseBuild(node: ts.Node, index: number): void { 1548 if (ts.isExpressionStatement(node)) { 1549 const parentComponentName: string = getComponentName(node); 1550 node = node.expression; 1551 while (node) { 1552 if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 1553 ts.isIdentifier(node.expression) && !DOLLAR_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { 1554 node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { 1555 traverseBuild(item, indexBlock); 1556 }); 1557 break; 1558 } else if (isForeachAndLzayForEach(node)) { 1559 node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => { 1560 traverseBuild(item, indexBlock); 1561 }); 1562 break; 1563 } else { 1564 loopNodeFindDoubleDollar(node, parentComponentName); 1565 if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 1566 ts.isIdentifier(node.expression)) { 1567 node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { 1568 traverseBuild(item, indexBlock); 1569 }); 1570 break; 1571 } 1572 } 1573 node = node.expression; 1574 } 1575 } else if (ts.isIfStatement(node)) { 1576 ifInnerDollarAttribute(node); 1577 } 1578} 1579 1580function ifInnerDollarAttribute(node: ts.IfStatement): void { 1581 if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) { 1582 node.thenStatement.statements.forEach((item, indexIfBlock) => { 1583 traverseBuild(item, indexIfBlock); 1584 }); 1585 } 1586 if (node.elseStatement) { 1587 elseInnerDollarAttribute(node); 1588 } 1589} 1590 1591function elseInnerDollarAttribute(node: ts.IfStatement): void { 1592 if (ts.isIfStatement(node.elseStatement) && node.elseStatement.thenStatement && ts.isBlock(node.elseStatement.thenStatement)) { 1593 traverseBuild(node.elseStatement, 0); 1594 } else if (ts.isBlock(node.elseStatement) && node.elseStatement.statements) { 1595 node.elseStatement.statements.forEach((item, indexElseBlock) => { 1596 traverseBuild(item, indexElseBlock); 1597 }); 1598 } 1599} 1600 1601function isPropertiesAddDoubleDollar(node: ts.Node): boolean { 1602 if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) { 1603 return true; 1604 } else if (ts.isEtsComponentExpression(node) && ts.isIdentifier(node.expression) && 1605 DOLLAR_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { 1606 return true; 1607 } else { 1608 return false; 1609 } 1610} 1611function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void { 1612 if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { 1613 const argument: ts.NodeArray<ts.Node> = node.arguments; 1614 const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name; 1615 if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) { 1616 argument.forEach((item: ts.Node) => { 1617 doubleDollarCollection(item); 1618 }); 1619 } 1620 } else if (isPropertiesAddDoubleDollar(node)) { 1621 node.arguments.forEach((item: ts.Node) => { 1622 if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { 1623 item.properties.forEach((param: ts.Node) => { 1624 if (isObjectPram(param, parentComponentName)) { 1625 doubleDollarCollection(param.initializer); 1626 } 1627 }); 1628 } else if (ts.isPropertyAccessExpression(item) && (handleComponentDollarBlock(node as ts.CallExpression, parentComponentName) || 1629 STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()))) { 1630 doubleDollarCollection(item); 1631 } 1632 }); 1633 } 1634} 1635 1636function handleComponentDollarBlock(node: ts.CallExpression, parentComponentName: string): boolean { 1637 return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && 1638 DOLLAR_BLOCK_INTERFACE.has(parentComponentName) && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 1639 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(node.expression.escapedText.toString()); 1640} 1641 1642function doubleDollarCollection(item: ts.Node): void { 1643 if (item.getText().startsWith($$)) { 1644 while (item.expression) { 1645 item = item.expression; 1646 } 1647 dollarCollection.add(item.getText()); 1648 } 1649} 1650 1651function isObjectPram(param: ts.Node, parentComponentName: string): boolean { 1652 return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && 1653 param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 1654 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText()); 1655} 1656 1657function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean { 1658 return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 1659 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) || 1660 STYLE_ADD_DOUBLE_DOLLAR.has(propertyName); 1661} 1662 1663function processContent(source: string, id: string): string { 1664 if (fastBuildLogger) { 1665 source = visualTransform(source, id, fastBuildLogger); 1666 } 1667 source = preprocessExtend(source, extendCollection); 1668 source = preprocessNewExtend(source, newExtendCollection); 1669 return source; 1670} 1671 1672function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set<string>): void { 1673 if (shouldResolvedFiles.has(file)) { 1674 return; 1675 } 1676 shouldResolvedFiles.add(file); 1677 if (cache && cache[file] && cache[file].parent) { 1678 cache[file].parent.forEach((item) => { 1679 judgeFileShouldResolved(item, shouldResolvedFiles); 1680 }); 1681 cache[file].parent = []; 1682 } 1683 if (cache && cache[file] && cache[file].children) { 1684 cache[file].children.forEach((item) => { 1685 judgeFileShouldResolved(item, shouldResolvedFiles); 1686 }); 1687 cache[file].children = []; 1688 } 1689} 1690 1691export function incrementWatchFile(watchModifiedFiles: string[], 1692 watchRemovedFiles: string[]): void { 1693 const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles]; 1694 if (changedFiles.length) { 1695 shouldResolvedFiles.clear(); 1696 } 1697 changedFiles.forEach((file) => { 1698 judgeFileShouldResolved(file, shouldResolvedFiles); 1699 }); 1700} 1701 1702export function runArkTSLinter(errorCodeLogger?: Object | undefined, parentEvent?: CompileEvent): void { 1703 const originProgram: ts.BuilderProgram = globalProgram.builderProgram; 1704 1705 const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance(); 1706 1707 const arkTSLinterDiagnostics = doArkTSLinter(getArkTSVersion(), 1708 getArkTSLinterMode(), 1709 originProgram, 1710 printArkTSLinterDiagnostic, 1711 !projectConfig.xtsMode, 1712 buildInfoWriteFile, 1713 errorCodeLogger); 1714 1715 ts.PerformanceDotting.startAdvanced('updateErrorFile'); 1716 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 1717 arkTSLinterDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 1718 updateErrorFileCache(diagnostic); 1719 }); 1720 timePrinterInstance.appendTime(ts.TimePhase.UPDATE_ERROR_FILE); 1721 } 1722 ts.PerformanceDotting.stopAdvanced('updateErrorFile'); 1723 timePrinterInstance.printTimes(); 1724 ts.ArkTSLinterTimePrinter.destroyInstance(); 1725} 1726 1727function printArkTSLinterDiagnostic(diagnostic: ts.Diagnostic, errorCodeLogger?: Object | undefined): void { 1728 if (diagnostic.category === ts.DiagnosticCategory.Error && (isInOhModuleFile(diagnostic) || isEtsDeclFileInSdk(diagnostic))) { 1729 const originalCategory = diagnostic.category; 1730 diagnostic.category = ts.DiagnosticCategory.Warning; 1731 printDiagnostic(diagnostic); 1732 diagnostic.category = originalCategory; 1733 return; 1734 } 1735 printDiagnostic(diagnostic, ErrorCodeModule.LINTER, errorCodeLogger); 1736} 1737 1738function isEtsDeclFileInSdk(diagnostics: ts.Diagnostic): boolean { 1739 if (diagnostics.file?.fileName === undefined) { 1740 return false; 1741 } 1742 return isInSDK(diagnostics.file.fileName) && diagnostics.file.fileName.endsWith('.ets'); 1743} 1744 1745function isInOhModuleFile(diagnostics: ts.Diagnostic): boolean { 1746 return (diagnostics.file !== undefined) && 1747 ((diagnostics.file.fileName.indexOf('/oh_modules/') !== -1) || diagnostics.file.fileName.indexOf('\\oh_modules\\') !== -1); 1748} 1749 1750function isInSDK(fileName: string | undefined): boolean { 1751 if (projectConfig.etsLoaderPath === undefined || fileName === undefined) { 1752 return false; 1753 } 1754 const sdkPath = path.resolve(projectConfig.etsLoaderPath, '../../../'); 1755 return path.resolve(fileName).startsWith(sdkPath); 1756} 1757 1758export function getArkTSLinterMode(): ArkTSLinterMode { 1759 if (!partialUpdateConfig.executeArkTSLinter) { 1760 return ArkTSLinterMode.NOT_USE; 1761 } 1762 1763 if (!partialUpdateConfig.standardArkTSLinter) { 1764 return ArkTSLinterMode.COMPATIBLE_MODE; 1765 } 1766 1767 if (isStandardMode()) { 1768 return ArkTSLinterMode.STANDARD_MODE; 1769 } 1770 return ArkTSLinterMode.COMPATIBLE_MODE; 1771} 1772 1773export function isStandardMode(): boolean { 1774 const STANDARD_MODE_COMPATIBLE_SDK_VERSION = 10; 1775 if (projectConfig && 1776 projectConfig.compatibleSdkVersion && 1777 projectConfig.compatibleSdkVersion >= STANDARD_MODE_COMPATIBLE_SDK_VERSION) { 1778 return true; 1779 } 1780 return false; 1781} 1782 1783function getArkTSVersion(): ArkTSVersion { 1784 if (projectConfig.arkTSVersion === '1.0') { 1785 return ArkTSVersion.ArkTS_1_0; 1786 } else if (projectConfig.arkTSVersion === '1.1') { 1787 return ArkTSVersion.ArkTS_1_1; 1788 } else if (projectConfig.arkTSVersion !== undefined) { 1789 const arkTSVersionLogger = fastBuildLogger || logger; 1790 arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version\n'); 1791 } 1792 1793 if (partialUpdateConfig.arkTSVersion === '1.0') { 1794 return ArkTSVersion.ArkTS_1_0; 1795 } else if (partialUpdateConfig.arkTSVersion === '1.1') { 1796 return ArkTSVersion.ArkTS_1_1; 1797 } else if (partialUpdateConfig.arkTSVersion !== undefined) { 1798 const arkTSVersionLogger = fastBuildLogger || logger; 1799 arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version in metadata\n'); 1800 } 1801 1802 return ArkTSVersion.ArkTS_1_1; 1803} 1804 1805enum TargetESVersion { 1806 ES2017 = 'ES2017', 1807 ES2021 = 'ES2021', 1808} 1809 1810function getTargetESVersion(): TargetESVersion { 1811 const targetESVersion = projectConfig?.projectArkOption?.tscConfig?.targetESVersion; 1812 if (targetESVersion === 'ES2017') { 1813 return TargetESVersion.ES2017; 1814 } else if (targetESVersion === 'ES2021') { 1815 return TargetESVersion.ES2021; 1816 } else if (targetESVersion !== undefined) { 1817 const targetESVersionLogger = fastBuildLogger || logger; 1818 targetESVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid Target ES version\n'); 1819 } 1820 return TargetESVersion.ES2021; 1821} 1822 1823export function getMaxFlowDepth(): number { 1824 // The value of maxFlowDepth ranges from 2000 to 65535. 1825 let maxFlowDepth: number | undefined = projectConfig?.projectArkOption?.tscConfig?.maxFlowDepth; 1826 1827 if (maxFlowDepth === undefined) { 1828 maxFlowDepth = MAX_FLOW_DEPTH_DEFAULT_VALUE; 1829 } else if (maxFlowDepth < MAX_FLOW_DEPTH_DEFAULT_VALUE || maxFlowDepth > MAX_FLOW_DEPTH_MAXIMUM_VALUE) { 1830 const maxFlowDepthLogger = fastBuildLogger || logger; 1831 maxFlowDepth = MAX_FLOW_DEPTH_DEFAULT_VALUE; 1832 maxFlowDepthLogger.warn('\u001b[33m' + 'ArkTS: Invalid maxFlowDepth for control flow analysis.' + 1833 `The value of maxFlowDepth ranges from ${MAX_FLOW_DEPTH_DEFAULT_VALUE} to ${MAX_FLOW_DEPTH_MAXIMUM_VALUE}.\n` + 1834 'If the modification does not take effect, set maxFlowDepth to the default value.'); 1835 } 1836 return maxFlowDepth; 1837} 1838 1839interface TargetESVersionLib { 1840 ES2017: string[], 1841 ES2021: string[], 1842} 1843 1844const targetESVersionLib: TargetESVersionLib = { 1845 // When target is es2017, the lib is es2020. 1846 ES2017: ['ES2020'], 1847 ES2021: ['ES2021'], 1848}; 1849 1850function getTargetESVersionLib(): string[] { 1851 const targetESVersion = projectConfig?.projectArkOption?.tscConfig?.targetESVersion; 1852 if (targetESVersion === 'ES2017') { 1853 return targetESVersionLib.ES2017; 1854 } else if (targetESVersion === 'ES2021') { 1855 return targetESVersionLib.ES2021; 1856 } else if (targetESVersion !== undefined) { 1857 const targetESVersionLogger = fastBuildLogger || logger; 1858 targetESVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid Target ES version\n'); 1859 } 1860 return targetESVersionLib.ES2021; 1861} 1862 1863function initEtsStandaloneCheckerConfig(logger, config): void { 1864 fastBuildLogger = logger; 1865 if (config.packageManagerType === 'ohpm') { 1866 config.packageDir = 'oh_modules'; 1867 config.packageJson = 'oh-package.json5'; 1868 } else { 1869 config.packageDir = 'node_modules'; 1870 config.packageJson = 'package.json'; 1871 } 1872 if (config.aceModuleJsonPath && fs.existsSync(config.aceModuleJsonPath)) { 1873 process.env.compileMode = 'moduleJson'; 1874 } 1875 Object.assign(projectConfig, config); 1876} 1877 1878function resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode: string): void { 1879 resetProjectConfig(); 1880 resetEtsCheck(); 1881 fastBuildLogger = beforeInitFastBuildLogger; 1882 process.env.compileMode = beforeInitCompileMode; 1883} 1884 1885export function etsStandaloneChecker(entryObj, logger, projectConfig): void { 1886 const beforeInitFastBuildLogger = fastBuildLogger; 1887 const beforeInitCompileMode = process.env.compileMode; 1888 initEtsStandaloneCheckerConfig(logger, projectConfig); 1889 const rootFileNames: string[] = []; 1890 const resolveModulePaths: string[] = []; 1891 Object.values(entryObj).forEach((fileName: string) => { 1892 rootFileNames.push(path.resolve(fileName)); 1893 }); 1894 if (projectConfig.resolveModulePaths && Array.isArray(projectConfig.resolveModulePaths)) { 1895 resolveModulePaths.push(...projectConfig.resolveModulePaths); 1896 } 1897 const filterFiles: string[] = filterInput(rootFileNames); 1898 languageService = createLanguageService(filterFiles, resolveModulePaths); 1899 const timePrinterInstance = ts.ArkTSLinterTimePrinter.getInstance(); 1900 timePrinterInstance.setArkTSTimePrintSwitch(false); 1901 timePrinterInstance.appendTime(ts.TimePhase.START); 1902 globalProgram.builderProgram = languageService.getBuilderProgram(/*withLinterProgram*/ true); 1903 globalProgram.program = globalProgram.builderProgram.getProgram(); 1904 props = languageService.getProps(); 1905 timePrinterInstance.appendTime(ts.TimePhase.GET_PROGRAM); 1906 collectFileToIgnoreDiagnostics(filterFiles); 1907 runArkTSLinter(); 1908 const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram 1909 .getSyntacticDiagnostics() 1910 .concat(globalProgram.builderProgram.getSemanticDiagnostics()); 1911 globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile); 1912 1913 allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 1914 printDiagnostic(diagnostic); 1915 }); 1916 resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode); 1917} 1918 1919export function resetEtsCheckTypeScript(): void { 1920 if (globalProgram.program) { 1921 globalProgram.program.releaseTypeChecker(); 1922 } else if (languageService) { 1923 languageService.getProgram().releaseTypeChecker(); 1924 } 1925 resetGlobalProgram(); 1926 languageService = null; 1927} 1928 1929export function resetEtsCheck(): void { 1930 cache = {}; 1931 props = new Set(); 1932 needReCheckForChangedDepUsers = false; 1933 resetEtsCheckTypeScript(); 1934 allResolvedModules.clear(); 1935 checkerResult.count = 0; 1936 warnCheckerResult.count = 0; 1937 resolvedModulesCache.clear(); 1938 dollarCollection.clear(); 1939 extendCollection.clear(); 1940 newExtendCollection.clear(); 1941 allSourceFilePaths.clear(); 1942 allModuleIds.clear(); 1943 filesBuildInfo.clear(); 1944 fileExistsCache.clear(); 1945 dirExistsCache.clear(); 1946 targetESVersionChanged = false; 1947 fileToIgnoreDiagnostics = undefined; 1948 maxMemoryInServiceChecker = 0; 1949} 1950