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