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'; 19const fse = require('fs-extra'); 20 21import { 22 projectConfig, 23 systemModules, 24 globalProgram, 25 sdkConfigs, 26 sdkConfigPrefix, 27 ohosSystemModulePaths, 28 partialUpdateConfig 29} from '../main'; 30import { 31 preprocessExtend, 32 preprocessNewExtend 33} from './validate_ui_syntax'; 34import { 35 INNER_COMPONENT_MEMBER_DECORATORS, 36 COMPONENT_DECORATORS_PARAMS, 37 COMPONENT_BUILD_FUNCTION, 38 STYLE_ADD_DOUBLE_DOLLAR, 39 $$, 40 PROPERTIES_ADD_DOUBLE_DOLLAR, 41 $$_BLOCK_INTERFACE, 42 COMPONENT_EXTEND_DECORATOR, 43 COMPONENT_BUILDER_DECORATOR, 44 ESMODULE, 45 EXTNAME_D_ETS, 46 EXTNAME_JS, 47 FOREACH_LAZYFOREACH, 48 COMPONENT_IF, 49 TS_WATCH_END_MSG 50} from './pre_define'; 51import { getName } from './process_component_build'; 52import { 53 INNER_COMPONENT_NAMES, 54 JS_BIND_COMPONENTS 55} from './component_map'; 56import { 57 props, 58 logger 59} from './compile_info'; 60import { 61 hasDecorator, 62 storedFileInfo 63} from './utils'; 64import { generateSourceFilesInHar } from './utils'; 65import { isExtendFunction, isOriginalExtend } from './process_ui_syntax'; 66import { visualTransform } from './process_visual'; 67import { tsWatchEmitter } from './fast_build/ets_ui/rollup-plugin-ets-checker'; 68import { doArkTSLinter, ArkTSLinterMode } from './do_arkTS_linter'; 69 70export const SOURCE_FILES: Map<string, ts.SourceFile> = new Map(); 71 72function collectSourceFilesMap(program: ts.Program): void { 73 program.getSourceFiles().forEach((sourceFile: ts.SourceFile) => { 74 SOURCE_FILES.set(path.normalize(sourceFile.fileName), sourceFile); 75 }); 76} 77 78export function readDeaclareFiles(): string[] { 79 const declarationsFileNames: string[] = []; 80 fs.readdirSync(path.resolve(__dirname, '../declarations')) 81 .forEach((fileName: string) => { 82 if (/\.d\.ts$/.test(fileName)) { 83 declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName)); 84 } 85 }); 86 return declarationsFileNames; 87} 88 89export const compilerOptions: ts.CompilerOptions = ts.readConfigFile( 90 path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 91function setCompilerOptions(resolveModulePaths: string[]) { 92 const allPath: Array<string> = [ 93 '*' 94 ]; 95 const basePath: string = path.resolve(projectConfig.projectPath); 96 if (process.env.compileTool === 'rollup' && resolveModulePaths && resolveModulePaths.length) { 97 resolveModulePaths.forEach((item: string) => { 98 if (!(/oh_modules$/.test(item) || /node_modules$/.test(item))) { 99 allPath.push(path.join(path.relative(basePath, item), '*')); 100 } 101 }); 102 } else { 103 if (!projectConfig.aceModuleJsonPath) { 104 allPath.push('../../../../../*'); 105 allPath.push('../../*'); 106 } else { 107 allPath.push('../../../../*'); 108 allPath.push('../*'); 109 } 110 } 111 Object.assign(compilerOptions, { 112 'allowJs': false, 113 'emitNodeModulesFiles': true, 114 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve, 115 'module': ts.ModuleKind.CommonJS, 116 'moduleResolution': ts.ModuleResolutionKind.NodeJs, 117 'noEmit': true, 118 'target': ts.ScriptTarget.ES2017, 119 'baseUrl': basePath, 120 'paths': { 121 '*': allPath 122 }, 123 'lib': ['lib.es2020.d.ts'], 124 'types': projectConfig.compilerTypes, 125 'etsLoaderPath': projectConfig.etsLoaderPath, 126 'needDoArkTsLinter': getArkTSLinterMode() !== ArkTSLinterMode.NOT_USE, 127 'isCompatibleVersion': getArkTSLinterMode() === ArkTSLinterMode.COMPATIBLE_MODE, 128 'skipTscOhModuleCheck': partialUpdateConfig.skipTscOhModuleCheck 129 }); 130 if (projectConfig.compileMode === ESMODULE) { 131 Object.assign(compilerOptions, { 132 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Remove, 133 'module': ts.ModuleKind.ES2020 134 }); 135 } 136 if (projectConfig.packageDir === 'oh_modules') { 137 Object.assign(compilerOptions, { 138 'packageManagerType': 'ohpm' 139 }); 140 } 141} 142 143interface InitCheckConfig { 144 tagName: string; 145 message: string; 146 needConditionCheck: boolean; 147 type: ts.DiagnosticCategory; 148 specifyCheckConditionFuncName: string; 149 tagNameShouldExisted: boolean; 150} 151 152interface CheckJSDocTagNameConfig { 153 needCheck: boolean; 154 checkConfig: InitCheckConfig[]; 155} 156 157function getInitCheckConfig(tagName: string, message: string): InitCheckConfig { 158 return { 159 tagName: tagName, 160 message: message, 161 needConditionCheck: false, 162 type: ts.DiagnosticCategory.Error, 163 specifyCheckConditionFuncName: '', 164 tagNameShouldExisted: true 165 }; 166} 167 168function getCheckJSDocTagNameConfig(fileName: string, sourceFileName: string): CheckJSDocTagNameConfig { 169 let needCheckResult: boolean = false; 170 const checkConfigArray: InitCheckConfig[] = []; 171 if (ohosSystemModulePaths.includes(path.normalize(sourceFileName)) || isArkuiDependence(sourceFileName)) { 172 if (isCardFile(fileName)) { 173 needCheckResult = true; 174 checkConfigArray.push(getInitCheckConfig('form', "'{0}' can't support form application.")); 175 } 176 if (projectConfig.isCrossplatform) { 177 needCheckResult = true; 178 checkConfigArray.push(getInitCheckConfig('crossplatform', "'{0}' can't support crossplatform application.")); 179 } 180 } 181 182 return { 183 needCheck: needCheckResult, 184 checkConfig: checkConfigArray 185 }; 186} 187 188interface extendInfo { 189 start: number, 190 end: number, 191 compName: string 192} 193 194export const files: ts.MapLike<{ version: number }> = {}; 195 196export function createLanguageService(rootFileNames: string[], resolveModulePaths: string[]): ts.LanguageService { 197 setCompilerOptions(resolveModulePaths); 198 rootFileNames.forEach((fileName: string) => { 199 files[fileName] = {version: 0}; 200 }); 201 const servicesHost: ts.LanguageServiceHost = { 202 getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()], 203 getScriptVersion: fileName => { 204 if (!files[path.resolve(fileName)]) { 205 files[path.resolve(fileName)] = {version: 0}; 206 } 207 return files[path.resolve(fileName)].version.toString(); 208 }, 209 getScriptSnapshot: fileName => { 210 if (!fs.existsSync(fileName)) { 211 return undefined; 212 } 213 if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) { 214 appComponentCollection.set(path.join(fileName), new Set()); 215 let content: string = processContent(fs.readFileSync(fileName).toString(), fileName); 216 const extendFunctionInfo: extendInfo[] = []; 217 content = instanceInsteadThis(content, fileName, extendFunctionInfo); 218 return ts.ScriptSnapshot.fromString(content); 219 } 220 return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); 221 }, 222 getCurrentDirectory: () => process.cwd(), 223 getCompilationSettings: () => compilerOptions, 224 getDefaultLibFileName: options => ts.getDefaultLibFilePath(options), 225 fileExists: ts.sys.fileExists, 226 readFile: ts.sys.readFile, 227 readDirectory: ts.sys.readDirectory, 228 resolveModuleNames: resolveModuleNames, 229 resolveTypeReferenceDirectives: resolveTypeReferenceDirectives, 230 directoryExists: ts.sys.directoryExists, 231 getDirectories: ts.sys.getDirectories, 232 getTagNameNeededCheckByFile: (fileName: string, sourceFileName: string) => { 233 return getCheckJSDocTagNameConfig(fileName, sourceFileName); 234 } 235 }; 236 return ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); 237} 238 239interface CacheFileName { 240 mtimeMs: number, 241 children: string[], 242 parent: string[], 243 error: boolean 244} 245interface NeedUpdateFlag { 246 flag: boolean; 247} 248interface CheckerResult { 249 count: number 250} 251 252interface WarnCheckerResult { 253 count: number 254} 255 256interface WholeCache { 257 runtimeOS: string, 258 sdkInfo: string, 259 fileList: Cache 260} 261type Cache = Record<string, CacheFileName>; 262export let cache: Cache = {}; 263export const hotReloadSupportFiles: Set<string> = new Set(); 264export const shouldResolvedFiles: Set<string> = new Set(); 265export const appComponentCollection: Map<string, Set<string>> = new Map(); 266const allResolvedModules: Set<string> = new Set(); 267 268export let fastBuildLogger = null; 269 270export const checkerResult: CheckerResult = {count: 0}; 271export const warnCheckerResult: WarnCheckerResult = {count: 0}; 272export let languageService: ts.LanguageService = null; 273export function serviceChecker(rootFileNames: string[], newLogger: any = null, resolveModulePaths: string[] = null): void { 274 fastBuildLogger = newLogger; 275 let cacheFile: string = null; 276 if (projectConfig.xtsMode || process.env.watchMode === 'true') { 277 if (projectConfig.hotReload) { 278 rootFileNames.forEach(fileName => { 279 hotReloadSupportFiles.add(fileName); 280 }); 281 } 282 languageService = createLanguageService(rootFileNames, resolveModulePaths); 283 } else { 284 cacheFile = path.resolve(projectConfig.cachePath, '../.ts_checker_cache'); 285 const wholeCache: WholeCache = fs.existsSync(cacheFile) ? 286 JSON.parse(fs.readFileSync(cacheFile).toString()) : 287 {'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {}}; 288 if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) { 289 cache = wholeCache.fileList; 290 } else { 291 cache = {}; 292 } 293 const filterFiles: string[] = filterInput(rootFileNames); 294 languageService = createLanguageService(filterFiles, resolveModulePaths); 295 } 296 globalProgram.program = languageService.getProgram(); 297 runArkTSLinter(); 298 collectSourceFilesMap(globalProgram.program); 299 if (process.env.watchMode !== 'true') { 300 processBuildHap(cacheFile, rootFileNames); 301 } 302} 303 304function processBuildHap(cacheFile: string, rootFileNames: string[]): void { 305 const allDiagnostics: ts.Diagnostic[] = globalProgram.program 306 .getSyntacticDiagnostics() 307 .concat(globalProgram.program.getSemanticDiagnostics()) 308 .concat(globalProgram.program.getDeclarationDiagnostics()); 309 allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 310 printDiagnostic(diagnostic); 311 }); 312 if (!projectConfig.xtsMode) { 313 fse.ensureDirSync(projectConfig.cachePath); 314 fs.writeFileSync(cacheFile, JSON.stringify({ 315 'runtimeOS': projectConfig.runtimeOS, 316 'sdkInfo': projectConfig.sdkInfo, 317 'fileList': cache 318 }, null, 2)); 319 } 320 if (projectConfig.compileHar || projectConfig.compileShared) { 321 [...allResolvedModules, ...rootFileNames].forEach(moduleFile => { 322 if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar)) { 323 try { 324 if ((/\.d\.e?ts$/).test(moduleFile)) { 325 generateSourceFilesInHar(moduleFile, fs.readFileSync(moduleFile, 'utf-8'), path.extname(moduleFile), 326 projectConfig); 327 } else { 328 const emit: any = languageService.getEmitOutput(moduleFile, true, true); 329 if (emit.outputFiles[0]) { 330 generateSourceFilesInHar(moduleFile, emit.outputFiles[0].text, '.d' + path.extname(moduleFile), 331 projectConfig); 332 } else { 333 console.warn(this.yellow, 334 "ArkTS:WARN doesn't generate .d" + path.extname(moduleFile) + ' for ' + moduleFile, this.reset); 335 } 336 } 337 } catch (err) {} 338 } 339 }); 340 } 341} 342 343function isArkuiDependence(file: string): boolean { 344 return /compiler\/declarations/.test(file) || /ets-loader\/declarations/.test(file); 345} 346 347function isCardFile(file: string): boolean { 348 for (const key in projectConfig.cardEntryObj) { 349 if (path.normalize(projectConfig.cardEntryObj[key]) === path.normalize(file)) { 350 return true; 351 } 352 } 353 return false; 354} 355 356function containFormError(message: string): boolean { 357 if (/can't support form application./.test(message)) { 358 return true; 359 } 360 return false; 361} 362 363export function printDiagnostic(diagnostic: ts.Diagnostic): void { 364 const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); 365 if (validateError(message)) { 366 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 367 updateErrorFileCache(diagnostic); 368 } 369 370 if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) { 371 return; 372 } 373 374 const logPrefix: string = diagnostic.category === ts.DiagnosticCategory.Error ? 'ERROR' : 'WARN'; 375 const etsCheckerLogger = fastBuildLogger ? fastBuildLogger : logger; 376 let logMessage: string; 377 if (logPrefix === 'ERROR') { 378 checkerResult.count += 1; 379 } else { 380 warnCheckerResult.count += 1; 381 } 382 if (diagnostic.file) { 383 const { line, character }: ts.LineAndCharacter = 384 diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); 385 logMessage = `ArkTS:${logPrefix} File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`; 386 } else { 387 logMessage = `ArkTS:${logPrefix}: ${message}`; 388 } 389 390 if (diagnostic.category === ts.DiagnosticCategory.Error) { 391 etsCheckerLogger.error('\u001b[31m' + logMessage); 392 } else { 393 etsCheckerLogger.warn('\u001b[33m' + logMessage); 394 } 395 } 396} 397 398function validateError(message: string): boolean { 399 const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/; 400 const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/; 401 if (matchMessage(message, props, propInfoReg) || 402 matchMessage(message, props, stateInfoReg)) { 403 return false; 404 } 405 return true; 406} 407function matchMessage(message: string, nameArr: any, reg: RegExp): boolean { 408 if (reg.test(message)) { 409 const match: string[] = message.match(reg); 410 if (match[1] && nameArr.includes(match[1])) { 411 return true; 412 } 413 } 414 return false; 415} 416 417function updateErrorFileCache(diagnostic: ts.Diagnostic): void { 418 if (diagnostic.file && cache[path.resolve(diagnostic.file.fileName)]) { 419 cache[path.resolve(diagnostic.file.fileName)].error = true; 420 } 421} 422 423function filterInput(rootFileNames: string[]): string[] { 424 return rootFileNames.filter((file: string) => { 425 const needUpdate: NeedUpdateFlag = { flag: false }; 426 const alreadyCheckedFiles: Set<string> = new Set(); 427 checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles); 428 if (!needUpdate.flag) { 429 storedFileInfo.changeFiles.push(path.resolve(file)); 430 } 431 return needUpdate.flag; 432 }); 433} 434 435function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set<string>): void { 436 if (alreadyCheckedFiles.has(file)) { 437 return; 438 } else { 439 alreadyCheckedFiles.add(file); 440 } 441 442 if (needUpdate.flag) { 443 return; 444 } 445 446 const value: CacheFileName = cache[file]; 447 const mtimeMs: number = fs.statSync(file).mtimeMs; 448 if (value) { 449 if (value.error || value.mtimeMs !== mtimeMs) { 450 needUpdate.flag = true; 451 return; 452 } 453 for (let i = 0; i < value.children.length; ++i) { 454 if (fs.existsSync(value.children[i])) { 455 checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles); 456 } else { 457 needUpdate.flag = true; 458 } 459 } 460 } else { 461 cache[file] = { mtimeMs, children: [], parent: [], error: false }; 462 needUpdate.flag = true; 463 } 464} 465 466const moduleResolutionHost: ts.ModuleResolutionHost = { 467 fileExists(fileName: string): boolean { 468 return ts.sys.fileExists(fileName); 469 }, 470 readFile(fileName: string): string | undefined { 471 return ts.sys.readFile(fileName); 472 }, 473 realpath(path: string): string { 474 return ts.sys.realpath(path); 475 }, 476 trace(s: string): void { 477 console.info(s); 478 } 479} 480 481export function resolveTypeReferenceDirectives(typeDirectiveNames: string[]): ts.ResolvedTypeReferenceDirective[] { 482 if (typeDirectiveNames.length === 0) { 483 return []; 484 } 485 486 const resolvedTypeReferenceCache: ts.ResolvedTypeReferenceDirective[] = []; 487 const cache: Map<string, ts.ResolvedTypeReferenceDirective> = new Map<string, ts.ResolvedTypeReferenceDirective>(); 488 const containingFile: string = path.join(projectConfig.modulePath, "build-profile.json5"); 489 490 for (const typeName of typeDirectiveNames) { 491 if (!cache.has(typeName)) { 492 const resolvedFile = ts.resolveTypeReferenceDirective(typeName, containingFile, compilerOptions, moduleResolutionHost); 493 if (!resolvedFile || !resolvedFile.resolvedTypeReferenceDirective) { 494 logger.error('\u001b[31m', `ArkTS:Cannot find type definition file for: ${typeName}\n`); 495 } 496 const result: ts.ResolvedTypeReferenceDirective = resolvedFile.resolvedTypeReferenceDirective; 497 cache.set(typeName, result); 498 resolvedTypeReferenceCache.push(result); 499 } 500 } 501 return resolvedTypeReferenceCache; 502} 503 504const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map(); 505 506export function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] { 507 const resolvedModules: ts.ResolvedModuleFull[] = []; 508 if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile)) 509 || !(resolvedModulesCache[path.resolve(containingFile)] && 510 resolvedModulesCache[path.resolve(containingFile)].length === moduleNames.length)) { 511 for (const moduleName of moduleNames) { 512 const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost); 513 if (result.resolvedModule) { 514 if (result.resolvedModule.resolvedFileName && 515 path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) { 516 const resultDETSPath: string = 517 result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS); 518 if (ts.sys.fileExists(resultDETSPath)) { 519 resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS)); 520 } else { 521 resolvedModules.push(result.resolvedModule); 522 } 523 } else { 524 resolvedModules.push(result.resolvedModule); 525 } 526 } else if (new RegExp(`^@(${sdkConfigPrefix})\\.`, 'i').test(moduleName.trim())) { 527 let apiFileExist: boolean = false; 528 for (let i = 0; i < sdkConfigs.length; i++) { 529 const sdkConfig = sdkConfigs[i]; 530 let modulePath: string = path.resolve(sdkConfig.apiPath, moduleName + '.d.ts'); 531 let isDETS: boolean = false; 532 if (!fs.existsSync(modulePath)) { 533 modulePath = path.resolve(sdkConfig.apiPath, moduleName + '.d.ets'); 534 isDETS = true; 535 } 536 if (systemModules.includes(moduleName + (isDETS ? '.d.ets' : '.d.ts')) && ts.sys.fileExists(modulePath)) { 537 resolvedModules.push(getResolveModule(modulePath, isDETS ? '.d.ets' : '.d.ts')); 538 apiFileExist = true; 539 break; 540 } 541 } 542 if (!apiFileExist) { 543 resolvedModules.push(null); 544 } 545 } else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) { 546 const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); 547 if (ts.sys.fileExists(modulePath)) { 548 resolvedModules.push(getResolveModule(modulePath, '.ets')); 549 } else { 550 resolvedModules.push(null); 551 } 552 } else if (/\.ts$/.test(moduleName)) { 553 const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); 554 if (ts.sys.fileExists(modulePath)) { 555 resolvedModules.push(getResolveModule(modulePath, '.ts')); 556 } else { 557 resolvedModules.push(null); 558 } 559 } else { 560 const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts'); 561 const systemDETSModulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ets'); 562 const suffix: string = /\.js$/.test(moduleName) ? '' : '.js'; 563 const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix); 564 const fileModulePath: string = 565 path.resolve(__dirname, '../node_modules', moduleName + '/index.js'); 566 const DETSModulePath: string = path.resolve(path.dirname(containingFile), 567 /\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS); 568 if (ts.sys.fileExists(modulePath)) { 569 resolvedModules.push(getResolveModule(modulePath, '.d.ts')); 570 } else if (ts.sys.fileExists(systemDETSModulePath)) { 571 resolvedModules.push(getResolveModule(systemDETSModulePath, '.d.ets')); 572 } else if (ts.sys.fileExists(jsModulePath)) { 573 resolvedModules.push(getResolveModule(jsModulePath, '.js')); 574 } else if (ts.sys.fileExists(fileModulePath)) { 575 resolvedModules.push(getResolveModule(fileModulePath, '.js')); 576 } else if (ts.sys.fileExists(DETSModulePath)) { 577 resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets')); 578 } else { 579 const srcIndex: number = projectConfig.projectPath.indexOf('src' + path.sep + 'main'); 580 let DETSModulePathFromModule: string; 581 if (srcIndex > 0) { 582 DETSModulePathFromModule = path.resolve( 583 projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS); 584 if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) { 585 resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets')); 586 } else { 587 resolvedModules.push(null); 588 } 589 } else { 590 resolvedModules.push(null); 591 } 592 } 593 } 594 if (projectConfig.hotReload && resolvedModules.length && 595 resolvedModules[resolvedModules.length - 1]) { 596 hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName)); 597 } 598 if (collectShouldPackedFiles(resolvedModules)) { 599 allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName); 600 } 601 } 602 if (!projectConfig.xtsMode) { 603 createOrUpdateCache(resolvedModules, path.resolve(containingFile)); 604 } 605 resolvedModulesCache[path.resolve(containingFile)] = resolvedModules; 606 return resolvedModules; 607 } 608 return resolvedModulesCache[path.resolve(containingFile)]; 609} 610 611function collectShouldPackedFiles(resolvedModules: ts.ResolvedModuleFull[]): boolean | RegExpMatchArray { 612 return (projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] && 613 resolvedModules[resolvedModules.length - 1].resolvedFileName && 614 (path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/) || 615 path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/\.d\.e?ts$/) && 616 path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match( 617 new RegExp('\\' + path.sep + 'src' + '\\' + path.sep + 'main' + '\\' + path.sep))); 618} 619 620function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void { 621 const children: string[] = []; 622 const error: boolean = false; 623 resolvedModules.forEach(moduleObj => { 624 if (moduleObj && moduleObj.resolvedFileName && /(?<!\.d)\.(ets|ts)$/.test(moduleObj.resolvedFileName)) { 625 const file: string = path.resolve(moduleObj.resolvedFileName); 626 const mtimeMs: number = fs.statSync(file).mtimeMs; 627 children.push(file); 628 const value: CacheFileName = cache[file]; 629 if (value) { 630 value.mtimeMs = mtimeMs; 631 value.error = error; 632 value.parent = value.parent || []; 633 value.parent.push(path.resolve(containingFile)); 634 value.parent = [...new Set(value.parent)]; 635 } else { 636 cache[file] = { mtimeMs, children: [], parent: [containingFile], error }; 637 } 638 } 639 }); 640 cache[path.resolve(containingFile)] = { mtimeMs: fs.statSync(containingFile).mtimeMs, children, 641 parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ? 642 cache[path.resolve(containingFile)].parent : [], error }; 643} 644 645export function createWatchCompilerHost(rootFileNames: string[], 646 reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function, 647 isPipe: boolean = false, resolveModulePaths: string[] = null): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> { 648 if (projectConfig.hotReload) { 649 rootFileNames.forEach(fileName => { 650 hotReloadSupportFiles.add(fileName); 651 }); 652 } 653 setCompilerOptions(resolveModulePaths); 654 const createProgram = ts.createSemanticDiagnosticsBuilderProgram; 655 const host = ts.createWatchCompilerHost( 656 [...rootFileNames, ...readDeaclareFiles()], compilerOptions, 657 ts.sys, createProgram, reportDiagnostic, 658 (diagnostic: ts.Diagnostic) => { 659 if ([6031, 6032].includes(diagnostic.code)) { 660 if (!isPipe) { 661 process.env.watchTs = 'start'; 662 resetErrorCount(); 663 } 664 } 665 // End of compilation in watch mode flag. 666 if ([6193, 6194].includes(diagnostic.code)) { 667 if (!isPipe) { 668 process.env.watchTs = 'end'; 669 if (fastBuildLogger) { 670 fastBuildLogger.debug(TS_WATCH_END_MSG); 671 tsWatchEmitter.emit(TS_WATCH_END_MSG); 672 } 673 } 674 delayPrintLogCount(); 675 } 676 }); 677 host.readFile = (fileName: string) => { 678 if (!fs.existsSync(fileName)) { 679 return undefined; 680 } 681 if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) { 682 let content: string = processContent(fs.readFileSync(fileName).toString(), fileName); 683 const extendFunctionInfo: extendInfo[] = []; 684 content = instanceInsteadThis(content, fileName, extendFunctionInfo); 685 return content; 686 } 687 return fs.readFileSync(fileName).toString(); 688 }; 689 host.resolveModuleNames = resolveModuleNames; 690 host.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives; 691 return host; 692} 693 694export function watchChecker(rootFileNames: string[], newLogger: any = null, resolveModulePaths: string[] = null): void { 695 fastBuildLogger = newLogger; 696 globalProgram.watchProgram = ts.createWatchProgram( 697 createWatchCompilerHost(rootFileNames, printDiagnostic, () => {}, () => {}, false, resolveModulePaths)); 698} 699 700export function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[]): string { 701 checkUISyntax(content, fileName, extendFunctionInfo); 702 extendFunctionInfo.reverse().forEach((item) => { 703 const subStr: string = content.substring(item.start, item.end); 704 const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => { 705 return item1 + item.compName + 'Instance' + item2; 706 }); 707 content = content.slice(0, item.start) + insert + content.slice(item.end); 708 }); 709 return content; 710} 711 712function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull { 713 return { 714 resolvedFileName: modulePath, 715 isExternalLibraryImport: false, 716 extension: type 717 }; 718} 719 720export const dollarCollection: Set<string> = new Set(); 721export const decoratorParamsCollection: Set<string> = new Set(); 722export const extendCollection: Set<string> = new Set(); 723export const importModuleCollection: Set<string> = new Set(); 724 725function checkUISyntax(source: string, fileName: string, extendFunctionInfo: extendInfo[]): void { 726 if (/\.ets$/.test(fileName)) { 727 if (process.env.compileMode === 'moduleJson' || 728 path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) { 729 const sourceFile: ts.SourceFile = ts.createSourceFile(fileName, source, 730 ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 731 collectComponents(sourceFile); 732 parseAllNode(sourceFile, sourceFile, extendFunctionInfo); 733 props.push(...dollarCollection, ...decoratorParamsCollection, ...extendCollection); 734 } 735 } 736} 737 738function collectComponents(node: ts.SourceFile): void { 739 // @ts-ignore 740 if (process.env.watchMode !== 'true' && node.identifiers && node.identifiers.size) { 741 // @ts-ignore 742 for (const key of node.identifiers.keys()) { 743 if (JS_BIND_COMPONENTS.has(key)) { 744 appComponentCollection.get(path.join(node.fileName)).add(key); 745 } 746 } 747 } 748} 749 750function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void { 751 if (ts.isStructDeclaration(node)) { 752 if (node.members) { 753 node.members.forEach(item => { 754 if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { 755 const propertyName: string = item.name.getText(); 756 if (item.decorators && item.decorators.length) { 757 for (let i = 0; i < item.decorators.length; i++) { 758 const decoratorName: string = item.decorators[i].getText().replace(/\(.*\)$/, '').trim(); 759 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 760 dollarCollection.add('$' + propertyName); 761 } 762 if (isDecoratorCollection(item.decorators[i], decoratorName)) { 763 decoratorParamsCollection.add(item.decorators[i].expression.arguments[0].getText()); 764 } 765 } 766 } 767 } 768 }); 769 } 770 } 771 if (process.env.watchMode !== 'true' && ts.isIfStatement(node)) { 772 appComponentCollection.get(path.join(sourceFileNode.fileName)).add(COMPONENT_IF); 773 } 774 if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION || 775 (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) && 776 hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { 777 if (node.body && node.body.statements && node.body.statements.length) { 778 const checkProp: ts.NodeArray<ts.Statement> = node.body.statements; 779 checkProp.forEach((item, index) => { 780 traverseBuild(item, index); 781 }); 782 } 783 } 784 if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) { 785 if (node.body && node.body.statements && node.body.statements.length && 786 !isOriginalExtend(node.body)) { 787 extendFunctionInfo.push({ 788 start: node.pos, 789 end: node.end, 790 compName: isExtendFunction(node, { decoratorName: '', componentName: '' })}); 791 } 792 } 793 node.getChildren().forEach((item: ts.Node) => parseAllNode(item, sourceFileNode, extendFunctionInfo)); 794} 795 796function isForeachAndLzayForEach(node: ts.Node): boolean { 797 return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && 798 FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] && 799 ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body); 800} 801 802function traverseBuild(node: ts.Node, index: number): void { 803 if (ts.isExpressionStatement(node)) { 804 let parentComponentName: string = getName(node); 805 if (!INNER_COMPONENT_NAMES.has(parentComponentName) && node.parent && node.parent.statements && index >= 1 && 806 node.parent.statements[index - 1].expression && node.parent.statements[index - 1].expression.expression) { 807 parentComponentName = node.parent.statements[index - 1].expression.expression.escapedText; 808 } 809 node = node.expression; 810 if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 811 ts.isIdentifier(node.expression) && !$$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { 812 node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { 813 traverseBuild(item, indexBlock); 814 }); 815 } else if (isForeachAndLzayForEach(node)) { 816 node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => { 817 traverseBuild(item, indexBlock); 818 }); 819 } else { 820 loopNodeFindDoubleDollar(node, parentComponentName); 821 if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 822 ts.isIdentifier(node.expression)) { 823 node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { 824 traverseBuild(item, indexBlock); 825 }); 826 } 827 } 828 } else if (ts.isIfStatement(node)) { 829 if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) { 830 node.thenStatement.statements.forEach((item, indexIfBlock) => { 831 traverseBuild(item, indexIfBlock); 832 }); 833 } 834 if (node.elseStatement && ts.isBlock(node.elseStatement) && node.elseStatement.statements) { 835 node.elseStatement.statements.forEach((item, indexElseBlock) => { 836 traverseBuild(item, indexElseBlock); 837 }); 838 } 839 } 840} 841 842function isPropertiesAddDoubleDollar(node: ts.Node): boolean { 843 if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) { 844 return true; 845 } else if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 846 ts.isIdentifier(node.expression) && $$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { 847 return true; 848 } else { 849 return false; 850 } 851} 852function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void { 853 while (node) { 854 if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { 855 const argument: ts.NodeArray<ts.Node> = node.arguments; 856 const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name; 857 if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) { 858 argument.forEach((item: ts.Node) => { 859 doubleDollarCollection(item); 860 }); 861 } 862 } else if (isPropertiesAddDoubleDollar(node)) { 863 node.arguments.forEach((item: ts.Node) => { 864 if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { 865 item.properties.forEach((param: ts.Node) => { 866 if (isObjectPram(param, parentComponentName)) { 867 doubleDollarCollection(param.initializer); 868 } 869 }); 870 } 871 if (STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()) && ts.isPropertyAccessExpression(item)) { 872 doubleDollarCollection(item); 873 } 874 }); 875 } 876 node = node.expression; 877 } 878} 879 880function doubleDollarCollection(item: ts.Node): void { 881 if (item.getText().startsWith($$)) { 882 while (item.expression) { 883 item = item.expression; 884 } 885 dollarCollection.add(item.getText()); 886 } 887} 888 889function isObjectPram(param: ts.Node, parentComponentName:string): boolean { 890 return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && 891 param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 892 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText()); 893} 894 895function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean { 896 return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 897 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) || 898 STYLE_ADD_DOUBLE_DOLLAR.has(propertyName); 899} 900 901function isDecoratorCollection(item: ts.Decorator, decoratorName: string): boolean { 902 return COMPONENT_DECORATORS_PARAMS.has(decoratorName) && 903 // @ts-ignore 904 item.expression.arguments && item.expression.arguments.length && 905 // @ts-ignore 906 ts.isIdentifier(item.expression.arguments[0]); 907} 908 909function processContent(source: string, id: string): string { 910 if (fastBuildLogger) { 911 source = visualTransform(source, id, fastBuildLogger); 912 } 913 source = preprocessExtend(source, extendCollection); 914 source = preprocessNewExtend(source, extendCollection); 915 return source; 916} 917 918function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set<string>): void { 919 if (shouldResolvedFiles.has(file)) { 920 return; 921 } 922 shouldResolvedFiles.add(file); 923 if (cache && cache[file] && cache[file].parent) { 924 cache[file].parent.forEach((item) => { 925 judgeFileShouldResolved(item, shouldResolvedFiles); 926 }); 927 cache[file].parent = []; 928 } 929 if (cache && cache[file] && cache[file].children) { 930 cache[file].children.forEach((item) => { 931 judgeFileShouldResolved(item, shouldResolvedFiles); 932 }); 933 cache[file].children = []; 934 } 935} 936 937export function incrementWatchFile(watchModifiedFiles: string[], 938 watchRemovedFiles: string[]): void { 939 const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles]; 940 if (changedFiles.length) { 941 shouldResolvedFiles.clear(); 942 } 943 changedFiles.forEach((file) => { 944 judgeFileShouldResolved(file, shouldResolvedFiles); 945 }); 946} 947 948function runArkTSLinter(): void { 949 const arkTSLinterDiagnostics = 950 doArkTSLinter(globalProgram.program, getArkTSLinterMode(), printArkTSLinterDiagnostic, !projectConfig.xtsMode); 951 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 952 arkTSLinterDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 953 updateErrorFileCache(diagnostic); 954 }); 955 } 956} 957 958function printArkTSLinterDiagnostic(diagnostic: ts.Diagnostic): void { 959 if (diagnostic.category === ts.DiagnosticCategory.Error && (isInOhModuleFile(diagnostic) || isInSDK(diagnostic))) { 960 const originalCategory = diagnostic.category; 961 diagnostic.category = ts.DiagnosticCategory.Warning; 962 printDiagnostic(diagnostic); 963 diagnostic.category = originalCategory; 964 return; 965 } 966 printDiagnostic(diagnostic); 967} 968 969function isInOhModuleFile(diagnostics: ts.Diagnostic): boolean { 970 return (diagnostics.file !== undefined) && 971 ((diagnostics.file.fileName.indexOf('/oh_modules/') !== -1) || diagnostics.file.fileName.indexOf('\\oh_modules\\') !== -1); 972} 973 974function isInSDK(diagnostics: ts.Diagnostic): boolean { 975 const fileName = diagnostics.file?.fileName; 976 if (projectConfig.etsLoaderPath === undefined || fileName === undefined) { 977 return false; 978 } 979 const sdkPath = path.resolve(projectConfig.etsLoaderPath, '../../../'); 980 return path.resolve(fileName).startsWith(sdkPath); 981} 982 983export function getArkTSLinterMode(): ArkTSLinterMode { 984 if (!partialUpdateConfig.executeArkTSLinter) { 985 return ArkTSLinterMode.NOT_USE; 986 } 987 988 if (!partialUpdateConfig.standardArkTSLinter) { 989 return ArkTSLinterMode.COMPATIBLE_MODE; 990 } 991 992 if (isStandardMode()) { 993 return ArkTSLinterMode.STANDARD_MODE; 994 } 995 return ArkTSLinterMode.COMPATIBLE_MODE; 996} 997 998export function isStandardMode(): boolean { 999 const STANDARD_MODE_COMPATIBLE_SDK_VERSION = 10; 1000 if (projectConfig && 1001 projectConfig.compatibleSdkVersion && 1002 projectConfig.compatibleSdkVersion >= STANDARD_MODE_COMPATIBLE_SDK_VERSION) { 1003 return true; 1004 } 1005 return false; 1006} 1007 1008function initEtsStandaloneCheckerConfig(logger, config): void { 1009 fastBuildLogger = logger; 1010 if (config.packageManagerType === 'ohpm') { 1011 config.packageDir = 'oh_modules'; 1012 config.packageJson = 'oh-package.json5'; 1013 } else { 1014 config.packageDir = 'node_modules'; 1015 config.packageJson = 'package.json'; 1016 } 1017 if (config.aceModuleJsonPath && fs.existsSync(config.aceModuleJsonPath)) { 1018 process.env.compileMode = 'moduleJson'; 1019 } 1020 Object.assign(projectConfig, config); 1021} 1022 1023export function etsStandaloneChecker(entryObj, logger, projectConfig): void { 1024 initEtsStandaloneCheckerConfig(logger, projectConfig); 1025 const rootFileNames: string[] = []; 1026 const resolveModulePaths: string[] = []; 1027 Object.values(entryObj).forEach((fileName: string) => { 1028 rootFileNames.push(path.resolve(fileName)); 1029 }); 1030 if (projectConfig.resolveModulePaths && Array.isArray(projectConfig.resolveModulePaths)) { 1031 resolveModulePaths.push(...projectConfig.resolveModulePaths); 1032 } 1033 const filterFiles: string[] = filterInput(rootFileNames); 1034 languageService = createLanguageService(filterFiles, resolveModulePaths); 1035 globalProgram.program = languageService.getProgram(); 1036 runArkTSLinter(); 1037 const allDiagnostics: ts.Diagnostic[] = globalProgram.program 1038 .getSyntacticDiagnostics() 1039 .concat(globalProgram.program.getSemanticDiagnostics()) 1040 .concat(globalProgram.program.getDeclarationDiagnostics()); 1041 allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 1042 printDiagnostic(diagnostic); 1043 }); 1044} 1045