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