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 allModulesPaths, 29 partialUpdateConfig, 30 resetProjectConfig, 31 resetGlobalProgram 32} from '../main'; 33import { 34 preprocessExtend, 35 preprocessNewExtend 36} from './validate_ui_syntax'; 37import { 38 INNER_COMPONENT_MEMBER_DECORATORS, 39 COMPONENT_DECORATORS_PARAMS, 40 COMPONENT_BUILD_FUNCTION, 41 STYLE_ADD_DOUBLE_DOLLAR, 42 $$, 43 PROPERTIES_ADD_DOUBLE_DOLLAR, 44 $$_BLOCK_INTERFACE, 45 COMPONENT_EXTEND_DECORATOR, 46 COMPONENT_BUILDER_DECORATOR, 47 ESMODULE, 48 EXTNAME_D_ETS, 49 EXTNAME_JS, 50 FOREACH_LAZYFOREACH, 51 COMPONENT_IF, 52 TS_WATCH_END_MSG, 53 FORM_TAG_CHECK_NAME, 54 FORM_TAG_CHECK_ERROR, 55 CROSSPLATFORM_TAG_CHECK_NAME, 56 CROSSPLATFORM_TAG_CHECK_ERROER, 57 DEPRECATED_TAG_CHECK_NAME, 58 DEPRECATED_TAG_CHECK_WARNING, 59 FA_TAG_CHECK_NAME, 60 FA_TAG_HUMP_CHECK_NAME, 61 FA_TAG_CHECK_ERROR, 62 STAGE_TAG_CHECK_NAME, 63 STAGE_TAG_HUMP_CHECK_NAME, 64 STAGE_TAG_CHECK_ERROR, 65 STAGE_COMPILE_MODE, 66 ATOMICSERVICE_BUNDLE_TYPE, 67 ATOMICSERVICE_TAG_CHECK_NAME, 68 ATOMICSERVICE_TAG_CHECK_ERROER, 69 SINCE_TAG_NAME, 70 ATOMICSERVICE_TAG_CHECK_VERSION, 71 TS_BUILD_INFO_SUFFIX, 72 HOT_RELOAD_BUILD_INFO_SUFFIX, 73 FIND_MODULE_WARNING, 74 SYSCAP_TAG_CHECK_NAME, 75 SYSCAP_TAG_CHECK_WARNING, 76} from './pre_define'; 77import { getName } from './process_component_build'; 78import { 79 INNER_COMPONENT_NAMES, 80 JS_BIND_COMPONENTS 81} from './component_map'; 82import { logger } from './compile_info'; 83import { 84 hasDecorator, 85 isString, 86 generateSourceFilesInHar, 87 startTimeStatisticsLocation, 88 stopTimeStatisticsLocation, 89 resolveModuleNamesTime, 90 CompilationTimeStatistics, 91 storedFileInfo, 92 getRollupCacheStoreKey, 93 getRollupCacheKey, 94 clearRollupCacheStore, 95 toUnixPath, 96 isWindows, 97 isMac, 98 tryToLowerCasePath 99} from './utils'; 100import { isExtendFunction, isOriginalExtend } from './process_ui_syntax'; 101import { visualTransform } from './process_visual'; 102import { tsWatchEmitter } from './fast_build/ets_ui/rollup-plugin-ets-checker'; 103import { 104 doArkTSLinter, 105 ArkTSLinterMode, 106 ArkTSProgram, 107 ArkTSVersion, 108 getReverseStrictBuilderProgram, 109 wasOptionsStrict 110} from './do_arkTS_linter'; 111 112export interface LanguageServiceCache { 113 service?: ts.LanguageService; 114 pkgJsonFileHash?: string; 115} 116 117export const SOURCE_FILES: Map<string, ts.SourceFile> = new Map(); 118 119function collectSourceFilesMap(program: ts.Program): void { 120 program.getSourceFiles().forEach((sourceFile: ts.SourceFile) => { 121 SOURCE_FILES.set(path.normalize(sourceFile.fileName), sourceFile); 122 }); 123} 124 125export function readDeaclareFiles(): string[] { 126 const declarationsFileNames: string[] = []; 127 fs.readdirSync(path.resolve(__dirname, '../declarations')) 128 .forEach((fileName: string) => { 129 if (/\.d\.ts$/.test(fileName)) { 130 declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName)); 131 } 132 }); 133 return declarationsFileNames; 134} 135 136const buildInfoWriteFile: ts.WriteFileCallback = (fileName: string, data: string) => { 137 if (fileName.endsWith(TS_BUILD_INFO_SUFFIX)) { 138 let fd: number = fs.openSync(fileName, 'w'); 139 fs.writeSync(fd, data, undefined, 'utf8'); 140 fs.closeSync(fd); 141 }; 142} 143// The collection records the file name and the corresponding version, where the version is the hash value of the text in last compilation. 144const filesBuildInfo: Map<string, string> = new Map(); 145 146export const compilerOptions: ts.CompilerOptions = ts.readConfigFile( 147 path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 148function setCompilerOptions(resolveModulePaths: string[]): void { 149 const allPath: Array<string> = ['*']; 150 const basePath: string = path.resolve(projectConfig.projectPath); 151 if (process.env.compileTool === 'rollup' && resolveModulePaths && resolveModulePaths.length) { 152 resolveModulePaths.forEach((item: string) => { 153 if (!(/oh_modules$/.test(item) || /node_modules$/.test(item))) { 154 allPath.push(path.join(path.relative(basePath, item), '*')); 155 } 156 }); 157 } else { 158 if (!projectConfig.aceModuleJsonPath) { 159 allPath.push('../../../../../*'); 160 allPath.push('../../*'); 161 } else { 162 allPath.push('../../../../*'); 163 allPath.push('../*'); 164 } 165 } 166 const suffix: string = projectConfig.hotReload ? HOT_RELOAD_BUILD_INFO_SUFFIX : TS_BUILD_INFO_SUFFIX; 167 const buildInfoPath: string = path.resolve(projectConfig.cachePath, '..', suffix); 168 Object.assign(compilerOptions, { 169 'allowJs': false, 170 'emitNodeModulesFiles': true, 171 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve, 172 'module': ts.ModuleKind.CommonJS, 173 'moduleResolution': ts.ModuleResolutionKind.NodeJs, 174 'noEmit': true, 175 'target': ts.ScriptTarget.ES2017, 176 'baseUrl': basePath, 177 'paths': { 178 '*': allPath 179 }, 180 'lib': ['lib.es2020.d.ts'], 181 'types': projectConfig.compilerTypes, 182 'etsLoaderPath': projectConfig.etsLoaderPath, 183 'needDoArkTsLinter': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE, 184 'isCompatibleVersion': getArkTSLinterMode() === ArkTSLinterMode.COMPATIBLE_MODE, 185 'skipTscOhModuleCheck': partialUpdateConfig.skipTscOhModuleCheck, 186 'skipArkTSStaticBlocksCheck': partialUpdateConfig.skipArkTSStaticBlocksCheck, 187 // options incremental && tsBuildInfoFile are required for applying incremental ability of typescript 188 'incremental': true, 189 'tsBuildInfoFile': buildInfoPath 190 }); 191 if (projectConfig.compileMode === ESMODULE) { 192 Object.assign(compilerOptions, { 193 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Remove, 194 'module': ts.ModuleKind.ES2020 195 }); 196 } 197 if (projectConfig.packageDir === 'oh_modules') { 198 Object.assign(compilerOptions, { 'packageManagerType': 'ohpm' }); 199 } 200 readTsBuildInfoFileInCrementalMode(buildInfoPath, projectConfig); 201} 202 203/** 204 * Read the source code information in the project of the last compilation process, and then use it 205 * to determine whether the file has been modified during this compilation process. 206 */ 207function readTsBuildInfoFileInCrementalMode(buildInfoPath: string, projectConfig: Object): void { 208 if (!fs.existsSync(buildInfoPath) || !(projectConfig.compileHar || projectConfig.compileShared)) { 209 return; 210 } 211 212 type FileInfoType = { 213 version: string; 214 affectsGlobalScope: boolean; 215 } 216 type ProgramType = { 217 fileNames: string[]; 218 fileInfos: (FileInfoType | string)[]; 219 } 220 let buildInfoProgram: ProgramType = undefined; 221 try { 222 const content: { program: ProgramType } = JSON.parse(fs.readFileSync(buildInfoPath, 'utf-8')); 223 buildInfoProgram = content.program; 224 if (!buildInfoProgram || !buildInfoProgram.fileNames || !buildInfoProgram.fileInfos) { 225 throw new Error('.tsbuildinfo content is invalid'); 226 } 227 } catch (err) { 228 fastBuildLogger.warn('\u001b[33m' + 'ArkTS: Failed to parse .tsbuildinfo file. Error message: ' + err.message.toString()); 229 return; 230 } 231 const buildInfoDirectory: string = path.dirname(buildInfoPath); 232 /** 233 * For the windos and mac platform, the file path in tsbuildinfo is in lowercase, while buildInfoDirectory is the original path (including uppercase). 234 * Therefore, the path needs to be converted to lowercase, and then perform path comparison. 235 */ 236 const isMacOrWin = isWindows() || isMac(); 237 const fileNames: string[] = buildInfoProgram.fileNames; 238 const fileInfos: (FileInfoType | string)[] = buildInfoProgram.fileInfos; 239 fileInfos.forEach((fileInfo, index) => { 240 const version: string = typeof fileInfo === 'string' ? fileInfo : fileInfo.version; 241 const absPath: string = path.resolve(buildInfoDirectory, fileNames[index]); 242 filesBuildInfo.set(isMacOrWin ? tryToLowerCasePath(absPath) : absPath, version); 243 }); 244} 245 246function getJsDocNodeCheckConfigItem(tagName: string[], message: string, type: ts.DiagnosticCategory, 247 tagNameShouldExisted: boolean, checkValidCallback?: (jsDocTag: ts.JSDocTag, config: ts.JsDocNodeCheckConfigItem) => boolean, 248 checkJsDocSpecialValidCallback?: (jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem) => boolean): ts.JsDocNodeCheckConfigItem { 249 return { 250 tagName: tagName, 251 message: message, 252 needConditionCheck: false, 253 type: type, 254 specifyCheckConditionFuncName: '', 255 tagNameShouldExisted: tagNameShouldExisted, 256 checkValidCallback: checkValidCallback, 257 checkJsDocSpecialValidCallback: checkJsDocSpecialValidCallback 258 }; 259} 260 261function checkSyscapAbility(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { 262 let currentSyscapValue: string = ''; 263 for (let i = 0; i < jsDocTags.length; i++) { 264 const jsDocTag: ts.JSDocTag = jsDocTags[i]; 265 if (jsDocTag.tagName.escapedText.toString() === SYSCAP_TAG_CHECK_NAME) { 266 currentSyscapValue = jsDocTag.comment as string; 267 break; 268 } 269 } 270 return projectConfig.syscapIntersectionSet && !projectConfig.syscapIntersectionSet.has(currentSyscapValue); 271} 272 273function getJsDocNodeCheckConfig(fileName: string, sourceFileName: string): ts.JsDocNodeCheckConfig { 274 let needCheckResult: boolean = false; 275 const checkConfigArray: ts.JsDocNodeCheckConfigItem[] = []; 276 const apiName: string = path.basename(fileName); 277 const sourceBaseName: string = path.basename(sourceFileName); 278 if (/(?<!\.d)\.ts$/g.test(fileName) && isArkuiDependence(sourceFileName) && 279 sourceBaseName !== 'common_ts_ets_api.d.ts' && sourceBaseName !== 'global.d.ts') { 280 checkConfigArray.push(getJsDocNodeCheckConfigItem([], FIND_MODULE_WARNING, ts.DiagnosticCategory.Warning, true)); 281 } 282 if (!systemModules.includes(apiName) && (allModulesPaths.includes(path.normalize(sourceFileName)) || isArkuiDependence(sourceFileName))) { 283 checkConfigArray.push(getJsDocNodeCheckConfigItem([DEPRECATED_TAG_CHECK_NAME], DEPRECATED_TAG_CHECK_WARNING, ts.DiagnosticCategory.Warning, false)); 284 checkConfigArray.push(getJsDocNodeCheckConfigItem([SYSCAP_TAG_CHECK_NAME], SYSCAP_TAG_CHECK_WARNING.replace('$DT', projectConfig.deviceTypesMessage), ts.DiagnosticCategory.Warning, false, null, checkSyscapAbility)); 285 if (isCardFile(fileName)) { 286 needCheckResult = true; 287 checkConfigArray.push(getJsDocNodeCheckConfigItem([FORM_TAG_CHECK_NAME], FORM_TAG_CHECK_ERROR, ts.DiagnosticCategory.Error, true)); 288 } 289 if (projectConfig.isCrossplatform) { 290 needCheckResult = true; 291 checkConfigArray.push(getJsDocNodeCheckConfigItem([CROSSPLATFORM_TAG_CHECK_NAME], CROSSPLATFORM_TAG_CHECK_ERROER, ts.DiagnosticCategory.Error, true)); 292 } 293 if (process.env.compileMode === STAGE_COMPILE_MODE) { 294 needCheckResult = true; 295 checkConfigArray.push(getJsDocNodeCheckConfigItem([FA_TAG_CHECK_NAME, FA_TAG_HUMP_CHECK_NAME], FA_TAG_CHECK_ERROR, ts.DiagnosticCategory.Warning, false)); 296 } else if (process.env.compileMode !== '') { 297 needCheckResult = true; 298 checkConfigArray.push(getJsDocNodeCheckConfigItem([STAGE_TAG_CHECK_NAME, STAGE_TAG_HUMP_CHECK_NAME], STAGE_TAG_CHECK_ERROR, 299 ts.DiagnosticCategory.Warning, false)); 300 } 301 if (projectConfig.bundleType === ATOMICSERVICE_BUNDLE_TYPE && projectConfig.compileSdkVersion >= ATOMICSERVICE_TAG_CHECK_VERSION) { 302 needCheckResult = true; 303 checkConfigArray.push(getJsDocNodeCheckConfigItem([ATOMICSERVICE_TAG_CHECK_NAME], ATOMICSERVICE_TAG_CHECK_ERROER, 304 ts.DiagnosticCategory.Error, true)); 305 } 306 } 307 308 return { 309 nodeNeedCheck: needCheckResult, 310 checkConfig: checkConfigArray 311 }; 312} 313 314interface extendInfo { 315 start: number, 316 end: number, 317 compName: string 318} 319 320function createHash(str: string): string { 321 const hash = crypto.createHash('sha256'); 322 hash.update(str); 323 return hash.digest('hex'); 324} 325 326export const fileHashScriptVersion: (fileName: string) => string = (fileName: string) => { 327 if (!fs.existsSync(fileName)) { 328 return '0'; 329 } 330 return createHash(fs.readFileSync(fileName).toString()); 331} 332 333export function createLanguageService(rootFileNames: string[], resolveModulePaths: string[], 334 compilationTime: CompilationTimeStatistics = null, rollupShareObject?: any): ts.LanguageService { 335 setCompilerOptions(resolveModulePaths); 336 const servicesHost: ts.LanguageServiceHost = { 337 getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()], 338 getScriptVersion: fileHashScriptVersion, 339 getScriptSnapshot: function(fileName) { 340 if (!fs.existsSync(fileName)) { 341 return undefined; 342 } 343 if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) { 344 startTimeStatisticsLocation(compilationTime ? compilationTime.scriptSnapshotTime : undefined); 345 appComponentCollection.set(path.join(fileName), new Set()); 346 let content: string = processContent(fs.readFileSync(fileName).toString(), fileName); 347 const extendFunctionInfo: extendInfo[] = []; 348 content = instanceInsteadThis(content, fileName, extendFunctionInfo, this.uiProps); 349 stopTimeStatisticsLocation(compilationTime ? compilationTime.scriptSnapshotTime : undefined); 350 return ts.ScriptSnapshot.fromString(content); 351 } 352 return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); 353 }, 354 getCurrentDirectory: () => process.cwd(), 355 getCompilationSettings: () => compilerOptions, 356 getDefaultLibFileName: options => ts.getDefaultLibFilePath(options), 357 fileExists: ts.sys.fileExists, 358 readFile: ts.sys.readFile, 359 readDirectory: ts.sys.readDirectory, 360 resolveModuleNames: resolveModuleNames, 361 resolveTypeReferenceDirectives: resolveTypeReferenceDirectives, 362 directoryExists: ts.sys.directoryExists, 363 getDirectories: ts.sys.getDirectories, 364 getJsDocNodeCheckedConfig: (fileCheckedInfo: ts.FileCheckModuleInfo, sourceFileName: string) => { 365 return getJsDocNodeCheckConfig(fileCheckedInfo.currentFileName, sourceFileName); 366 }, 367 getFileCheckedModuleInfo: (containFilePath: string) => { 368 return { 369 fileNeedCheck: true, 370 checkPayload: undefined, 371 currentFileName: containFilePath, 372 }; 373 }, 374 uiProps: [], 375 clearProps: function() { 376 dollarCollection.clear(); 377 decoratorParamsCollection.clear(); 378 extendCollection.clear(); 379 this.uiProps.length = 0; 380 } 381 }; 382 383 if (process.env.watchMode === 'true') { 384 return ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); 385 } 386 387 return getOrCreateLanguageService(servicesHost, rootFileNames, rollupShareObject); 388} 389 390function getOrCreateLanguageService(servicesHost: ts.LanguageServiceHost, rootFileNames: string[], 391 rollupShareObject?: any): ts.LanguageService { 392 let cacheStoreKey: string = getRollupCacheStoreKey(projectConfig); 393 let cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + 'service'; 394 clearRollupCacheStore(rollupShareObject?.cacheStoreManager, cacheStoreKey); 395 396 let cache: LanguageServiceCache | undefined = 397 rollupShareObject?.cacheStoreManager?.mount(cacheStoreKey).getCache(cacheServiceKey); 398 let service: ts.LanguageService | undefined = cache?.service; 399 const currentHash: string | undefined = rollupShareObject?.projectConfig?.pkgJsonFileHash; 400 const lastHash: string | undefined= cache?.pkgJsonFileHash; 401 const shouldRebuild: boolean | undefined = currentHash && lastHash && currentHash !== lastHash; 402 if (!service || shouldRebuild) { 403 service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); 404 } else { 405 // Found language service from cache, update root files 406 let updateRootFileNames = [...rootFileNames, ...readDeaclareFiles()]; 407 service.updateRootFiles(updateRootFileNames); 408 } 409 410 const newCache: LanguageServiceCache = {service: service, pkgJsonFileHash: currentHash}; 411 rollupShareObject?.cacheStoreManager?.mount(cacheStoreKey).setCache(cacheServiceKey, newCache); 412 return service; 413} 414 415interface CacheFileName { 416 mtimeMs: number, 417 children: string[], 418 parent: string[], 419 error: boolean 420} 421interface NeedUpdateFlag { 422 flag: boolean; 423} 424interface CheckerResult { 425 count: number 426} 427 428interface WarnCheckerResult { 429 count: number 430} 431 432interface WholeCache { 433 runtimeOS: string, 434 sdkInfo: string, 435 fileList: Cache 436} 437type Cache = Record<string, CacheFileName>; 438export let cache: Cache = {}; 439export const hotReloadSupportFiles: Set<string> = new Set(); 440export const shouldResolvedFiles: Set<string> = new Set(); 441export const appComponentCollection: Map<string, Set<string>> = new Map(); 442const allResolvedModules: Set<string> = new Set(); 443// all files of tsc and rollup for obfuscation scanning. 444export const allSourceFilePaths: Set<string> = new Set(); 445export let props: string[] = []; 446 447export let fastBuildLogger = null; 448 449export const checkerResult: CheckerResult = { count: 0 }; 450export const warnCheckerResult: WarnCheckerResult = { count: 0 }; 451export let languageService: ts.LanguageService = null; 452export function serviceChecker(rootFileNames: string[], newLogger: Object = null, resolveModulePaths: string[] = null, 453 compilationTime: CompilationTimeStatistics = null, rollupShareObject?: any): void { 454 fastBuildLogger = newLogger; 455 let cacheFile: string = null; 456 if (projectConfig.xtsMode || process.env.watchMode === 'true') { 457 if (projectConfig.hotReload) { 458 rootFileNames.forEach(fileName => { 459 hotReloadSupportFiles.add(fileName); 460 }); 461 } 462 languageService = createLanguageService(rootFileNames, resolveModulePaths, compilationTime); 463 props = languageService.getProps(); 464 } else { 465 cacheFile = path.resolve(projectConfig.cachePath, '../.ts_checker_cache'); 466 const wholeCache: WholeCache = fs.existsSync(cacheFile) ? 467 JSON.parse(fs.readFileSync(cacheFile).toString()) : 468 { 'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {} }; 469 if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) { 470 cache = wholeCache.fileList; 471 } else { 472 cache = {}; 473 } 474 languageService = createLanguageService(rootFileNames, resolveModulePaths, compilationTime, rollupShareObject); 475 } 476 startTimeStatisticsLocation(compilationTime ? compilationTime.createProgramTime : undefined); 477 globalProgram.builderProgram = languageService.getBuilderProgram(); 478 globalProgram.program = globalProgram.builderProgram.getProgram(); 479 props = languageService.getProps(); 480 stopTimeStatisticsLocation(compilationTime ? compilationTime.createProgramTime : undefined); 481 482 collectAllFiles(globalProgram.program); 483 startTimeStatisticsLocation(compilationTime ? compilationTime.runArkTSLinterTime : undefined); 484 runArkTSLinter(rollupShareObject); 485 stopTimeStatisticsLocation(compilationTime ? compilationTime.runArkTSLinterTime : undefined); 486 487 if (process.env.watchMode !== 'true') { 488 processBuildHap(cacheFile, rootFileNames, compilationTime); 489 } 490} 491// collect the compiled files of tsc and rollup for obfuscation scanning. 492export function collectAllFiles(program?: ts.Program, rollupFileList?: IterableIterator<string>): void { 493 if (program) { 494 collectTscFiles(program); 495 return; 496 } 497 mergeRollUpFiles(rollupFileList); 498} 499 500export function collectTscFiles(program: ts.Program): void { 501 const programAllFiles: readonly ts.SourceFile[] = program.getSourceFiles(); 502 let projectRootPath: string = projectConfig.projectRootPath; 503 if (!projectRootPath) { 504 return; 505 } 506 projectRootPath = toUnixPath(projectRootPath); 507 const isMacOrWin = isWindows() || isMac(); 508 programAllFiles.forEach(sourceFile => { 509 const fileName = toUnixPath(sourceFile.fileName); 510 if (!fileName.startsWith(projectRootPath)) { 511 return; 512 } 513 allSourceFilePaths.add(fileName); 514 // For the windos and mac platform, the file path in filesBuildInfo is in lowercase, while fileName of sourceFile is the original path (including uppercase). 515 if (filesBuildInfo.size > 0 && 516 Reflect.get(sourceFile, 'version') !== filesBuildInfo.get(isMacOrWin ? tryToLowerCasePath(fileName) : fileName)) { 517 allResolvedModules.add(fileName); 518 } 519 }); 520} 521 522export function mergeRollUpFiles(rollupFileList: IterableIterator<string>) { 523 for (const moduleId of rollupFileList) { 524 if (fs.existsSync(moduleId)) { 525 allSourceFilePaths.add(toUnixPath(moduleId)); 526 } 527 } 528} 529 530export function emitBuildInfo(): void { 531 globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile); 532} 533 534function processBuildHap(cacheFile: string, rootFileNames: string[], compilationTime: CompilationTimeStatistics): void { 535 startTimeStatisticsLocation(compilationTime ? compilationTime.diagnosticTime : undefined); 536 const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram 537 .getSyntacticDiagnostics() 538 .concat(globalProgram.builderProgram.getSemanticDiagnostics()); 539 stopTimeStatisticsLocation(compilationTime ? compilationTime.diagnosticTime : undefined); 540 emitBuildInfo(); 541 allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 542 printDiagnostic(diagnostic); 543 }); 544 if (!projectConfig.xtsMode) { 545 fse.ensureDirSync(projectConfig.cachePath); 546 fs.writeFileSync(cacheFile, JSON.stringify({ 547 'runtimeOS': projectConfig.runtimeOS, 548 'sdkInfo': projectConfig.sdkInfo, 549 'fileList': cache 550 }, null, 2)); 551 } 552 if (projectConfig.compileHar || projectConfig.compileShared) { 553 let emit: string | undefined = undefined; 554 let writeFile = (fileName: string, text: string, writeByteOrderMark: boolean) => { 555 emit = text; 556 } 557 [...allResolvedModules, ...rootFileNames].forEach(moduleFile => { 558 if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar)) { 559 try { 560 if ((/\.d\.e?ts$/).test(moduleFile)) { 561 generateSourceFilesInHar(moduleFile, fs.readFileSync(moduleFile, 'utf-8'), path.extname(moduleFile), 562 projectConfig); 563 } else { 564 emit = undefined; 565 let sourcefile = globalProgram.program.getSourceFile(moduleFile); 566 if (sourcefile) { 567 globalProgram.program.emit(sourcefile, writeFile, undefined, true, undefined, true); 568 } 569 if (emit) { 570 generateSourceFilesInHar(moduleFile, emit, '.d' + path.extname(moduleFile), projectConfig); 571 } 572 } 573 } catch (err) { } 574 } 575 }); 576 printDeclarationDiagnostics(); 577 } 578} 579 580function printDeclarationDiagnostics(): void { 581 globalProgram.builderProgram.getDeclarationDiagnostics().forEach((diagnostic: ts.Diagnostic) => { 582 printDiagnostic(diagnostic); 583 }); 584} 585 586export function isArkuiDependence(file: string): boolean { 587 const fileDir: string = path.dirname(file); 588 const declarationsPath: string = path.resolve(__dirname, '../declarations').replace(/\\/g, '/'); 589 const componentPath: string = path.resolve(__dirname, '../../../component').replace(/\\/g, '/'); 590 if (fileDir === declarationsPath || fileDir === componentPath) { 591 return true; 592 } 593 return false; 594} 595 596function isCardFile(file: string): boolean { 597 for (const key in projectConfig.cardEntryObj) { 598 if (path.normalize(projectConfig.cardEntryObj[key]) === path.normalize(file)) { 599 return true; 600 } 601 } 602 return false; 603} 604 605function containFormError(message: string): boolean { 606 if (/can't support form application./.test(message)) { 607 return true; 608 } 609 return false; 610} 611 612export function printDiagnostic(diagnostic: ts.Diagnostic): void { 613 const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); 614 if (validateError(message)) { 615 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 616 updateErrorFileCache(diagnostic); 617 } 618 619 if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) { 620 return; 621 } 622 623 const logPrefix: string = diagnostic.category === ts.DiagnosticCategory.Error ? 'ERROR' : 'WARN'; 624 const etsCheckerLogger = fastBuildLogger ? fastBuildLogger : logger; 625 let logMessage: string; 626 if (logPrefix === 'ERROR') { 627 checkerResult.count += 1; 628 } else { 629 warnCheckerResult.count += 1; 630 } 631 if (diagnostic.file) { 632 const { line, character }: ts.LineAndCharacter = 633 diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); 634 logMessage = `ArkTS:${logPrefix} File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`; 635 } else { 636 logMessage = `ArkTS:${logPrefix}: ${message}`; 637 } 638 639 if (diagnostic.category === ts.DiagnosticCategory.Error) { 640 etsCheckerLogger.error('\u001b[31m' + logMessage); 641 } else { 642 etsCheckerLogger.warn('\u001b[33m' + logMessage); 643 } 644 } 645} 646 647function validateError(message: string): boolean { 648 const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/; 649 const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/; 650 if (matchMessage(message, props, propInfoReg) || 651 matchMessage(message, props, stateInfoReg)) { 652 return false; 653 } 654 return true; 655} 656function matchMessage(message: string, nameArr: any, reg: RegExp): boolean { 657 if (reg.test(message)) { 658 const match: string[] = message.match(reg); 659 if (match[1] && nameArr.includes(match[1])) { 660 return true; 661 } 662 } 663 return false; 664} 665 666function updateErrorFileCache(diagnostic: ts.Diagnostic): void { 667 if (diagnostic.file && cache[path.resolve(diagnostic.file.fileName)]) { 668 cache[path.resolve(diagnostic.file.fileName)].error = true; 669 } 670} 671 672function filterInput(rootFileNames: string[]): string[] { 673 return rootFileNames.filter((file: string) => { 674 const needUpdate: NeedUpdateFlag = { flag: false }; 675 const alreadyCheckedFiles: Set<string> = new Set(); 676 checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles); 677 if (!needUpdate.flag) { 678 storedFileInfo.changeFiles.push(path.resolve(file)); 679 } 680 return needUpdate.flag; 681 }); 682} 683 684function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set<string>): void { 685 if (alreadyCheckedFiles.has(file)) { 686 return; 687 } else { 688 alreadyCheckedFiles.add(file); 689 } 690 691 if (needUpdate.flag) { 692 return; 693 } 694 695 const value: CacheFileName = cache[file]; 696 const mtimeMs: number = fs.statSync(file).mtimeMs; 697 if (value) { 698 if (value.error || value.mtimeMs !== mtimeMs) { 699 needUpdate.flag = true; 700 return; 701 } 702 for (let i = 0; i < value.children.length; ++i) { 703 if (fs.existsSync(value.children[i])) { 704 checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles); 705 } else { 706 needUpdate.flag = true; 707 } 708 } 709 } else { 710 cache[file] = { mtimeMs, children: [], parent: [], error: false }; 711 needUpdate.flag = true; 712 } 713} 714 715const moduleResolutionHost: ts.ModuleResolutionHost = { 716 fileExists(fileName: string): boolean { 717 return ts.sys.fileExists(fileName); 718 }, 719 readFile(fileName: string): string | undefined { 720 return ts.sys.readFile(fileName); 721 }, 722 realpath(path: string): string { 723 return ts.sys.realpath(path); 724 }, 725 trace(s: string): void { 726 console.info(s); 727 } 728} 729 730export function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | ts.FileReference[]): ts.ResolvedTypeReferenceDirective[] { 731 if (typeDirectiveNames.length === 0) { 732 return []; 733 } 734 735 const resolvedTypeReferenceCache: ts.ResolvedTypeReferenceDirective[] = []; 736 const cache: Map<string, ts.ResolvedTypeReferenceDirective> = new Map<string, ts.ResolvedTypeReferenceDirective>(); 737 const containingFile: string = path.join(projectConfig.modulePath, "build-profile.json5"); 738 739 for (let entry of typeDirectiveNames) { 740 const typeName = isString(entry) ? entry : entry.fileName.toLowerCase(); 741 if (!cache.has(typeName)) { 742 const resolvedFile = ts.resolveTypeReferenceDirective(typeName, containingFile, compilerOptions, moduleResolutionHost); 743 if (!resolvedFile || !resolvedFile.resolvedTypeReferenceDirective) { 744 logger.error('\u001b[31m', `ArkTS:Cannot find type definition file for: ${typeName}\n`); 745 } 746 const result: ts.ResolvedTypeReferenceDirective = resolvedFile.resolvedTypeReferenceDirective; 747 cache.set(typeName, result); 748 resolvedTypeReferenceCache.push(result); 749 } 750 } 751 return resolvedTypeReferenceCache; 752} 753 754const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map(); 755 756export function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] { 757 startTimeStatisticsLocation(resolveModuleNamesTime); 758 const resolvedModules: ts.ResolvedModuleFull[] = []; 759 if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile)) 760 || !(resolvedModulesCache[path.resolve(containingFile)] && 761 resolvedModulesCache[path.resolve(containingFile)].length === moduleNames.length)) { 762 for (const moduleName of moduleNames) { 763 const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost); 764 if (result.resolvedModule) { 765 if (result.resolvedModule.resolvedFileName && 766 path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) { 767 const resultDETSPath: string = 768 result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS); 769 if (ts.sys.fileExists(resultDETSPath)) { 770 resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS)); 771 } else { 772 resolvedModules.push(result.resolvedModule); 773 } 774 } else { 775 resolvedModules.push(result.resolvedModule); 776 } 777 } else if (new RegExp(`^@(${sdkConfigPrefix})\\.`, 'i').test(moduleName.trim())) { 778 let apiFileExist: boolean = false; 779 for (let i = 0; i < sdkConfigs.length; i++) { 780 const sdkConfig = sdkConfigs[i]; 781 const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(sdkConfig.apiPath, moduleName, ['.d.ts', '.d.ets']); 782 const modulePath: string = resolveModuleInfo.modulePath; 783 const isDETS: boolean = resolveModuleInfo.isEts; 784 if (systemModules.includes(moduleName + (isDETS ? '.d.ets' : '.d.ts')) && ts.sys.fileExists(modulePath)) { 785 resolvedModules.push(getResolveModule(modulePath, isDETS ? '.d.ets' : '.d.ts')); 786 apiFileExist = true; 787 break; 788 } 789 } 790 if (!apiFileExist) { 791 resolvedModules.push(null); 792 } 793 } else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) { 794 const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); 795 if (ts.sys.fileExists(modulePath)) { 796 resolvedModules.push(getResolveModule(modulePath, '.ets')); 797 } else { 798 resolvedModules.push(null); 799 } 800 } else if (/\.ts$/.test(moduleName)) { 801 const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); 802 if (ts.sys.fileExists(modulePath)) { 803 resolvedModules.push(getResolveModule(modulePath, '.ts')); 804 } else { 805 resolvedModules.push(null); 806 } 807 } else { 808 const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts'); 809 const systemDETSModulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ets'); 810 const kitModulePath: string = path.resolve(__dirname, '../../../kits', moduleName + '.d.ts'); 811 const kitSystemDETSModulePath: string = path.resolve(__dirname, '../../../kits', moduleName + '.d.ets'); 812 const suffix: string = /\.js$/.test(moduleName) ? '' : '.js'; 813 const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix); 814 const fileModulePath: string = 815 path.resolve(__dirname, '../node_modules', moduleName + '/index.js'); 816 const DETSModulePath: string = path.resolve(path.dirname(containingFile), 817 /\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS); 818 if (ts.sys.fileExists(modulePath)) { 819 resolvedModules.push(getResolveModule(modulePath, '.d.ts')); 820 } else if (ts.sys.fileExists(systemDETSModulePath)) { 821 resolvedModules.push(getResolveModule(systemDETSModulePath, '.d.ets')); 822 } else if (ts.sys.fileExists(kitModulePath)) { 823 resolvedModules.push(getResolveModule(kitModulePath, '.d.ts')); 824 } else if (ts.sys.fileExists(kitSystemDETSModulePath)) { 825 resolvedModules.push(getResolveModule(kitSystemDETSModulePath, '.d.ets')); 826 } else if (ts.sys.fileExists(jsModulePath)) { 827 resolvedModules.push(getResolveModule(jsModulePath, '.js')); 828 } else if (ts.sys.fileExists(fileModulePath)) { 829 resolvedModules.push(getResolveModule(fileModulePath, '.js')); 830 } else if (ts.sys.fileExists(DETSModulePath)) { 831 resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets')); 832 } else { 833 const srcIndex: number = projectConfig.projectPath.indexOf('src' + path.sep + 'main'); 834 let DETSModulePathFromModule: string; 835 if (srcIndex > 0) { 836 DETSModulePathFromModule = path.resolve( 837 projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS); 838 if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) { 839 resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets')); 840 } else { 841 resolvedModules.push(null); 842 } 843 } else { 844 resolvedModules.push(null); 845 } 846 } 847 } 848 if (projectConfig.hotReload && resolvedModules.length && 849 resolvedModules[resolvedModules.length - 1]) { 850 hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName)); 851 } 852 if (collectShouldPackedFiles(resolvedModules)) { 853 allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName); 854 } 855 } 856 if (!projectConfig.xtsMode) { 857 createOrUpdateCache(resolvedModules, path.resolve(containingFile)); 858 } 859 resolvedModulesCache[path.resolve(containingFile)] = resolvedModules; 860 stopTimeStatisticsLocation(resolveModuleNamesTime); 861 return resolvedModules; 862 } 863 stopTimeStatisticsLocation(resolveModuleNamesTime); 864 return resolvedModulesCache[path.resolve(containingFile)]; 865} 866 867export function getRealModulePath(apiDirs: string[], moduleName: string, exts: string[]): ResolveModuleInfo { 868 const resolveResult: ResolveModuleInfo = { 869 modulePath: '', 870 isEts: true 871 }; 872 for (let i = 0; i < apiDirs.length; i++) { 873 const dir = apiDirs[i]; 874 for (let i = 0; i < exts.length; i++) { 875 const ext = exts[i]; 876 const moduleDir = path.resolve(dir, moduleName + ext); 877 if (!fs.existsSync(moduleDir)) { 878 continue; 879 } 880 resolveResult.modulePath = moduleDir; 881 if (ext === '.d.ts') { 882 resolveResult.isEts = false; 883 } 884 } 885 } 886 return resolveResult; 887} 888 889export interface ResolveModuleInfo { 890 modulePath: string; 891 isEts: boolean; 892} 893 894function collectShouldPackedFiles(resolvedModules: ts.ResolvedModuleFull[]): boolean | RegExpMatchArray { 895 return (projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] && 896 resolvedModules[resolvedModules.length - 1].resolvedFileName && 897 (path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/) || 898 path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/\.d\.e?ts$/) && 899 path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match( 900 new RegExp('\\' + path.sep + 'src' + '\\' + path.sep + 'main' + '\\' + path.sep))); 901} 902 903function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void { 904 const children: string[] = []; 905 const error: boolean = false; 906 resolvedModules.forEach(moduleObj => { 907 if (moduleObj && moduleObj.resolvedFileName && /(?<!\.d)\.(ets|ts)$/.test(moduleObj.resolvedFileName)) { 908 const file: string = path.resolve(moduleObj.resolvedFileName); 909 const mtimeMs: number = fs.statSync(file).mtimeMs; 910 children.push(file); 911 const value: CacheFileName = cache[file]; 912 if (value) { 913 value.mtimeMs = mtimeMs; 914 value.error = error; 915 value.parent = value.parent || []; 916 value.parent.push(path.resolve(containingFile)); 917 value.parent = [...new Set(value.parent)]; 918 } else { 919 cache[file] = { mtimeMs, children: [], parent: [containingFile], error }; 920 } 921 } 922 }); 923 cache[path.resolve(containingFile)] = { 924 mtimeMs: fs.statSync(containingFile).mtimeMs, children, 925 parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ? 926 cache[path.resolve(containingFile)].parent : [], error 927 }; 928} 929 930export function createWatchCompilerHost(rootFileNames: string[], 931 reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function, 932 isPipe: boolean = false, resolveModulePaths: string[] = null): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> { 933 if (projectConfig.hotReload) { 934 rootFileNames.forEach(fileName => { 935 hotReloadSupportFiles.add(fileName); 936 }); 937 } 938 setCompilerOptions(resolveModulePaths); 939 const createProgram = ts.createSemanticDiagnosticsBuilderProgram; 940 const host = ts.createWatchCompilerHost( 941 [...rootFileNames, ...readDeaclareFiles()], compilerOptions, 942 ts.sys, createProgram, reportDiagnostic, 943 (diagnostic: ts.Diagnostic) => { 944 if ([6031, 6032].includes(diagnostic.code)) { 945 if (!isPipe) { 946 process.env.watchTs = 'start'; 947 resetErrorCount(); 948 } 949 } 950 // End of compilation in watch mode flag. 951 if ([6193, 6194].includes(diagnostic.code)) { 952 if (!isPipe) { 953 process.env.watchTs = 'end'; 954 if (fastBuildLogger) { 955 fastBuildLogger.debug(TS_WATCH_END_MSG); 956 tsWatchEmitter.emit(TS_WATCH_END_MSG); 957 } 958 } 959 delayPrintLogCount(); 960 } 961 }); 962 host.readFile = (fileName: string) => { 963 if (!fs.existsSync(fileName)) { 964 return undefined; 965 } 966 if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) { 967 let content: string = processContent(fs.readFileSync(fileName).toString(), fileName); 968 const extendFunctionInfo: extendInfo[] = []; 969 content = instanceInsteadThis(content, fileName, extendFunctionInfo, props); 970 return content; 971 } 972 return fs.readFileSync(fileName).toString(); 973 }; 974 host.resolveModuleNames = resolveModuleNames; 975 host.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives; 976 return host; 977} 978 979export function watchChecker(rootFileNames: string[], newLogger: any = null, resolveModulePaths: string[] = null): void { 980 fastBuildLogger = newLogger; 981 globalProgram.watchProgram = ts.createWatchProgram( 982 createWatchCompilerHost(rootFileNames, printDiagnostic, () => { }, () => { }, false, resolveModulePaths)); 983} 984 985export function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[], 986 props: string[]): string { 987 checkUISyntax(content, fileName, extendFunctionInfo, props); 988 extendFunctionInfo.reverse().forEach((item) => { 989 const subStr: string = content.substring(item.start, item.end); 990 const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => { 991 return item1 + item.compName + 'Instance' + item2; 992 }); 993 content = content.slice(0, item.start) + insert + content.slice(item.end); 994 }); 995 return content; 996} 997 998function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull { 999 return { 1000 resolvedFileName: modulePath, 1001 isExternalLibraryImport: false, 1002 extension: type 1003 }; 1004} 1005 1006export const dollarCollection: Set<string> = new Set(); 1007export const decoratorParamsCollection: Set<string> = new Set(); 1008export const extendCollection: Set<string> = new Set(); 1009export const importModuleCollection: Set<string> = new Set(); 1010 1011function checkUISyntax(source: string, fileName: string, extendFunctionInfo: extendInfo[], props: string[]): void { 1012 if (/\.ets$/.test(fileName)) { 1013 if (process.env.compileMode === 'moduleJson' || 1014 path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) { 1015 const sourceFile: ts.SourceFile = ts.createSourceFile(fileName, source, 1016 ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 1017 collectComponents(sourceFile); 1018 parseAllNode(sourceFile, sourceFile, extendFunctionInfo); 1019 props.push(...dollarCollection, ...decoratorParamsCollection, ...extendCollection); 1020 } 1021 } 1022} 1023 1024function collectComponents(node: ts.SourceFile): void { 1025 // @ts-ignore 1026 if (process.env.watchMode !== 'true' && node.identifiers && node.identifiers.size) { 1027 // @ts-ignore 1028 for (const key of node.identifiers.keys()) { 1029 if (JS_BIND_COMPONENTS.has(key)) { 1030 appComponentCollection.get(path.join(node.fileName)).add(key); 1031 } 1032 } 1033 } 1034} 1035 1036function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void { 1037 if (ts.isStructDeclaration(node)) { 1038 if (node.members) { 1039 node.members.forEach(item => { 1040 if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { 1041 const propertyName: string = item.name.getText(); 1042 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(item); 1043 if (decorators && decorators.length) { 1044 for (let i = 0; i < decorators.length; i++) { 1045 const decoratorName: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); 1046 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 1047 dollarCollection.add('$' + propertyName); 1048 } 1049 if (isDecoratorCollection(decorators[i], decoratorName)) { 1050 decoratorParamsCollection.add(decorators[i].expression.arguments[0].getText()); 1051 } 1052 } 1053 } 1054 } 1055 }); 1056 } 1057 } 1058 if (process.env.watchMode !== 'true' && ts.isIfStatement(node)) { 1059 appComponentCollection.get(path.join(sourceFileNode.fileName)).add(COMPONENT_IF); 1060 } 1061 if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION || 1062 (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) && 1063 hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { 1064 if (node.body && node.body.statements && node.body.statements.length) { 1065 const checkProp: ts.NodeArray<ts.Statement> = node.body.statements; 1066 checkProp.forEach((item, index) => { 1067 traverseBuild(item, index); 1068 }); 1069 } 1070 } 1071 if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) { 1072 if (node.body && node.body.statements && node.body.statements.length && 1073 !isOriginalExtend(node.body)) { 1074 extendFunctionInfo.push({ 1075 start: node.pos, 1076 end: node.end, 1077 compName: isExtendFunction(node, { decoratorName: '', componentName: '' }) 1078 }); 1079 } 1080 } 1081 node.getChildren().forEach((item: ts.Node) => parseAllNode(item, sourceFileNode, extendFunctionInfo)); 1082} 1083 1084function isForeachAndLzayForEach(node: ts.Node): boolean { 1085 return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && 1086 FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] && 1087 ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body); 1088} 1089 1090function traverseBuild(node: ts.Node, index: number): void { 1091 if (ts.isExpressionStatement(node)) { 1092 let parentComponentName: string = getName(node); 1093 if (!INNER_COMPONENT_NAMES.has(parentComponentName) && node.parent && node.parent.statements && index >= 1 && 1094 node.parent.statements[index - 1].expression && node.parent.statements[index - 1].expression.expression) { 1095 parentComponentName = node.parent.statements[index - 1].expression.expression.escapedText; 1096 } 1097 node = node.expression; 1098 if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 1099 ts.isIdentifier(node.expression) && !$$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { 1100 node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { 1101 traverseBuild(item, indexBlock); 1102 }); 1103 } else if (isForeachAndLzayForEach(node)) { 1104 node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => { 1105 traverseBuild(item, indexBlock); 1106 }); 1107 } else { 1108 loopNodeFindDoubleDollar(node, parentComponentName); 1109 if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 1110 ts.isIdentifier(node.expression)) { 1111 node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { 1112 traverseBuild(item, indexBlock); 1113 }); 1114 } 1115 } 1116 } else if (ts.isIfStatement(node)) { 1117 if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) { 1118 node.thenStatement.statements.forEach((item, indexIfBlock) => { 1119 traverseBuild(item, indexIfBlock); 1120 }); 1121 } 1122 if (node.elseStatement && ts.isBlock(node.elseStatement) && node.elseStatement.statements) { 1123 node.elseStatement.statements.forEach((item, indexElseBlock) => { 1124 traverseBuild(item, indexElseBlock); 1125 }); 1126 } 1127 } 1128} 1129 1130function isPropertiesAddDoubleDollar(node: ts.Node): boolean { 1131 if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) { 1132 return true; 1133 } else if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 1134 ts.isIdentifier(node.expression) && $$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { 1135 return true; 1136 } else { 1137 return false; 1138 } 1139} 1140function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void { 1141 while (node) { 1142 if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { 1143 const argument: ts.NodeArray<ts.Node> = node.arguments; 1144 const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name; 1145 if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) { 1146 argument.forEach((item: ts.Node) => { 1147 doubleDollarCollection(item); 1148 }); 1149 } 1150 } else if (isPropertiesAddDoubleDollar(node)) { 1151 node.arguments.forEach((item: ts.Node) => { 1152 if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { 1153 item.properties.forEach((param: ts.Node) => { 1154 if (isObjectPram(param, parentComponentName)) { 1155 doubleDollarCollection(param.initializer); 1156 } 1157 }); 1158 } 1159 if (STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()) && ts.isPropertyAccessExpression(item)) { 1160 doubleDollarCollection(item); 1161 } 1162 }); 1163 } 1164 node = node.expression; 1165 } 1166} 1167 1168function doubleDollarCollection(item: ts.Node): void { 1169 if (item.getText().startsWith($$)) { 1170 while (item.expression) { 1171 item = item.expression; 1172 } 1173 dollarCollection.add(item.getText()); 1174 } 1175} 1176 1177function isObjectPram(param: ts.Node, parentComponentName: string): boolean { 1178 return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && 1179 param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 1180 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText()); 1181} 1182 1183function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean { 1184 return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 1185 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) || 1186 STYLE_ADD_DOUBLE_DOLLAR.has(propertyName); 1187} 1188 1189function isDecoratorCollection(item: ts.Decorator, decoratorName: string): boolean { 1190 return COMPONENT_DECORATORS_PARAMS.has(decoratorName) && 1191 // @ts-ignore 1192 item.expression.arguments && item.expression.arguments.length && 1193 // @ts-ignore 1194 ts.isIdentifier(item.expression.arguments[0]); 1195} 1196 1197function processContent(source: string, id: string): string { 1198 if (fastBuildLogger) { 1199 source = visualTransform(source, id, fastBuildLogger); 1200 } 1201 source = preprocessExtend(source, extendCollection); 1202 source = preprocessNewExtend(source, extendCollection); 1203 return source; 1204} 1205 1206function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set<string>): void { 1207 if (shouldResolvedFiles.has(file)) { 1208 return; 1209 } 1210 shouldResolvedFiles.add(file); 1211 if (cache && cache[file] && cache[file].parent) { 1212 cache[file].parent.forEach((item) => { 1213 judgeFileShouldResolved(item, shouldResolvedFiles); 1214 }); 1215 cache[file].parent = []; 1216 } 1217 if (cache && cache[file] && cache[file].children) { 1218 cache[file].children.forEach((item) => { 1219 judgeFileShouldResolved(item, shouldResolvedFiles); 1220 }); 1221 cache[file].children = []; 1222 } 1223} 1224 1225export function incrementWatchFile(watchModifiedFiles: string[], 1226 watchRemovedFiles: string[]): void { 1227 const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles]; 1228 if (changedFiles.length) { 1229 shouldResolvedFiles.clear(); 1230 } 1231 changedFiles.forEach((file) => { 1232 judgeFileShouldResolved(file, shouldResolvedFiles); 1233 }); 1234} 1235 1236function runArkTSLinter(rollupShareObject?: Object): void { 1237 let wasStrict: boolean = wasOptionsStrict(globalProgram.program.getCompilerOptions()); 1238 let originProgram: ArkTSProgram = { 1239 builderProgram: globalProgram.builderProgram, 1240 wasStrict: wasStrict 1241 }; 1242 let reverseStrictProgram: ArkTSProgram = { 1243 builderProgram: getReverseStrictBuilderProgram(rollupShareObject, globalProgram.program, wasStrict), 1244 wasStrict: !wasStrict 1245 }; 1246 const arkTSLinterDiagnostics = doArkTSLinter(getArkTSVersion(), 1247 getArkTSLinterMode(), 1248 originProgram, 1249 reverseStrictProgram, 1250 printArkTSLinterDiagnostic, 1251 !projectConfig.xtsMode, 1252 buildInfoWriteFile); 1253 1254 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 1255 arkTSLinterDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 1256 updateErrorFileCache(diagnostic); 1257 }); 1258 } 1259} 1260 1261function printArkTSLinterDiagnostic(diagnostic: ts.Diagnostic): void { 1262 if (diagnostic.category === ts.DiagnosticCategory.Error && (isInOhModuleFile(diagnostic) || isInSDK(diagnostic))) { 1263 const originalCategory = diagnostic.category; 1264 diagnostic.category = ts.DiagnosticCategory.Warning; 1265 printDiagnostic(diagnostic); 1266 diagnostic.category = originalCategory; 1267 return; 1268 } 1269 printDiagnostic(diagnostic); 1270} 1271 1272function isInOhModuleFile(diagnostics: ts.Diagnostic): boolean { 1273 return (diagnostics.file !== undefined) && 1274 ((diagnostics.file.fileName.indexOf('/oh_modules/') !== -1) || diagnostics.file.fileName.indexOf('\\oh_modules\\') !== -1); 1275} 1276 1277function isInSDK(diagnostics: ts.Diagnostic): boolean { 1278 const fileName = diagnostics.file?.fileName; 1279 if (projectConfig.etsLoaderPath === undefined || fileName === undefined) { 1280 return false; 1281 } 1282 const sdkPath = path.resolve(projectConfig.etsLoaderPath, '../../../'); 1283 return path.resolve(fileName).startsWith(sdkPath); 1284} 1285 1286export function getArkTSLinterMode(): ArkTSLinterMode { 1287 if (!partialUpdateConfig.executeArkTSLinter) { 1288 return ArkTSLinterMode.NOT_USE; 1289 } 1290 1291 if (!partialUpdateConfig.standardArkTSLinter) { 1292 return ArkTSLinterMode.COMPATIBLE_MODE; 1293 } 1294 1295 if (isStandardMode()) { 1296 return ArkTSLinterMode.STANDARD_MODE; 1297 } 1298 return ArkTSLinterMode.COMPATIBLE_MODE; 1299} 1300 1301export function isStandardMode(): boolean { 1302 const STANDARD_MODE_COMPATIBLE_SDK_VERSION = 10; 1303 if (projectConfig && 1304 projectConfig.compatibleSdkVersion && 1305 projectConfig.compatibleSdkVersion >= STANDARD_MODE_COMPATIBLE_SDK_VERSION) { 1306 return true; 1307 } 1308 return false; 1309} 1310 1311function getArkTSVersion(): ArkTSVersion { 1312 if (projectConfig.arkTSVersion === '1.0') { 1313 return ArkTSVersion.ArkTS_1_0; 1314 } else if (projectConfig.arkTSVersion === '1.1') { 1315 return ArkTSVersion.ArkTS_1_1; 1316 } else if (projectConfig.arkTSVersion !== undefined) { 1317 const arkTSVersionLogger = fastBuildLogger ? fastBuildLogger : logger; 1318 arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version\n'); 1319 } 1320 1321 if (partialUpdateConfig.arkTSVersion === '1.0') { 1322 return ArkTSVersion.ArkTS_1_0; 1323 } else if (partialUpdateConfig.arkTSVersion === '1.1') { 1324 return ArkTSVersion.ArkTS_1_1; 1325 } else if (partialUpdateConfig.arkTSVersion !== undefined) { 1326 const arkTSVersionLogger = fastBuildLogger ? fastBuildLogger : logger; 1327 arkTSVersionLogger.warn('\u001b[33m' + 'ArkTS: Invalid ArkTS version in metadata\n'); 1328 } 1329 1330 return ArkTSVersion.ArkTS_1_1; 1331} 1332 1333function initEtsStandaloneCheckerConfig(logger, config): void { 1334 fastBuildLogger = logger; 1335 if (config.packageManagerType === 'ohpm') { 1336 config.packageDir = 'oh_modules'; 1337 config.packageJson = 'oh-package.json5'; 1338 } else { 1339 config.packageDir = 'node_modules'; 1340 config.packageJson = 'package.json'; 1341 } 1342 if (config.aceModuleJsonPath && fs.existsSync(config.aceModuleJsonPath)) { 1343 process.env.compileMode = 'moduleJson'; 1344 } 1345 Object.assign(projectConfig, config); 1346} 1347 1348function resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode: string) { 1349 resetProjectConfig(); 1350 resetGlobalProgram(); 1351 resetEtsCheck(); 1352 fastBuildLogger = beforeInitFastBuildLogger; 1353 process.env.compileMode = beforeInitCompileMode; 1354} 1355 1356export function etsStandaloneChecker(entryObj, logger, projectConfig): void { 1357 const beforeInitFastBuildLogger = fastBuildLogger; 1358 const beforeInitCompileMode = process.env.compileMode; 1359 initEtsStandaloneCheckerConfig(logger, projectConfig); 1360 const rootFileNames: string[] = []; 1361 const resolveModulePaths: string[] = []; 1362 Object.values(entryObj).forEach((fileName: string) => { 1363 rootFileNames.push(path.resolve(fileName)); 1364 }); 1365 if (projectConfig.resolveModulePaths && Array.isArray(projectConfig.resolveModulePaths)) { 1366 resolveModulePaths.push(...projectConfig.resolveModulePaths); 1367 } 1368 const filterFiles: string[] = filterInput(rootFileNames); 1369 languageService = createLanguageService(filterFiles, resolveModulePaths); 1370 globalProgram.builderProgram = languageService.getBuilderProgram(); 1371 globalProgram.program = globalProgram.builderProgram.getProgram(); 1372 props = languageService.getProps(); 1373 runArkTSLinter(); 1374 const allDiagnostics: ts.Diagnostic[] = globalProgram.builderProgram 1375 .getSyntacticDiagnostics() 1376 .concat(globalProgram.builderProgram.getSemanticDiagnostics()); 1377 globalProgram.builderProgram.emitBuildInfo(buildInfoWriteFile); 1378 1379 allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 1380 printDiagnostic(diagnostic); 1381 }); 1382 resetEtsStandaloneCheckerConfig(beforeInitFastBuildLogger, beforeInitCompileMode); 1383} 1384 1385export function resetEtsCheck(): void { 1386 cache = {}; 1387 props = []; 1388 languageService = null; 1389 allResolvedModules.clear(); 1390 checkerResult.count = 0; 1391 warnCheckerResult.count = 0; 1392 resolvedModulesCache.clear(); 1393 dollarCollection.clear(); 1394 decoratorParamsCollection.clear(); 1395 extendCollection.clear(); 1396 allSourceFilePaths.clear(); 1397 filesBuildInfo.clear(); 1398} 1399