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'; 19 20import { 21 projectConfig, 22 systemModules, 23 globalProgram 24} from '../main'; 25import { 26 preprocessExtend, 27 preprocessNewExtend 28} from './validate_ui_syntax'; 29import { 30 INNER_COMPONENT_MEMBER_DECORATORS, 31 COMPONENT_DECORATORS_PARAMS, 32 COMPONENT_BUILD_FUNCTION, 33 STYLE_ADD_DOUBLE_DOLLAR, 34 $$, 35 PROPERTIES_ADD_DOUBLE_DOLLAR, 36 $$_BLOCK_INTERFACE, 37 COMPONENT_EXTEND_DECORATOR, 38 COMPONENT_BUILDER_DECORATOR, 39 ESMODULE, 40 EXTNAME_D_ETS, 41 EXTNAME_JS, 42 FOREACH_LAZYFOREACH, 43 TS_WATCH_END_MSG 44} from './pre_define'; 45import { getName } from './process_component_build'; 46import { INNER_COMPONENT_NAMES } from './component_map'; 47import { 48 props, 49 logger 50} from './compile_info'; 51import { hasDecorator } from './utils'; 52import { generateSourceFilesInHar } from './utils'; 53import { isExtendFunction, isOriginalExtend } from './process_ui_syntax'; 54import { visualTransform } from './process_visual'; 55import { tsWatchEmitter } from './fast_build/ets_ui/rollup-plugin-ets-checker'; 56 57export function readDeaclareFiles(): string[] { 58 const declarationsFileNames: string[] = []; 59 fs.readdirSync(path.resolve(__dirname, '../declarations')) 60 .forEach((fileName: string) => { 61 if (/\.d\.ts$/.test(fileName)) { 62 declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName)); 63 } 64 }); 65 return declarationsFileNames; 66} 67 68export const compilerOptions: ts.CompilerOptions = ts.readConfigFile( 69 path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 70function setCompilerOptions() { 71 const allPath: Array<string> = [ 72 '*' 73 ]; 74 if (!projectConfig.aceModuleJsonPath) { 75 allPath.push('../../../../../*'); 76 allPath.push('../../*'); 77 } else { 78 allPath.push('../../../../*'); 79 allPath.push('../*'); 80 } 81 Object.assign(compilerOptions, { 82 'allowJs': false, 83 'emitNodeModulesFiles': true, 84 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve, 85 'module': ts.ModuleKind.CommonJS, 86 'moduleResolution': ts.ModuleResolutionKind.NodeJs, 87 'noEmit': true, 88 'target': ts.ScriptTarget.ES2017, 89 'baseUrl': path.resolve(projectConfig.projectPath), 90 'paths': { 91 '*': allPath 92 }, 93 'lib': [ 94 'lib.es2020.d.ts' 95 ] 96 }); 97 if (projectConfig.compileMode === ESMODULE) { 98 Object.assign(compilerOptions, { 99 'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Remove, 100 'module': ts.ModuleKind.ES2020 101 }); 102 } 103 if (projectConfig.packageDir === 'oh_modules') { 104 Object.assign(compilerOptions, { 105 'packageManagerType': 'ohpm' 106 }); 107 } 108} 109 110interface extendInfo { 111 start: number, 112 end: number, 113 compName: string 114} 115 116export function createLanguageService(rootFileNames: string[]): ts.LanguageService { 117 setCompilerOptions(); 118 const files: ts.MapLike<{ version: number }> = {}; 119 const servicesHost: ts.LanguageServiceHost = { 120 getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()], 121 getScriptVersion: fileName => 122 files[fileName] && files[fileName].version.toString(), 123 getScriptSnapshot: fileName => { 124 if (!fs.existsSync(fileName)) { 125 return undefined; 126 } 127 if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) { 128 let content: string = processContent(fs.readFileSync(fileName).toString(), fileName); 129 const extendFunctionInfo: extendInfo[] = []; 130 content = instanceInsteadThis(content, fileName, extendFunctionInfo); 131 return ts.ScriptSnapshot.fromString(content); 132 } 133 return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); 134 }, 135 getCurrentDirectory: () => process.cwd(), 136 getCompilationSettings: () => compilerOptions, 137 getDefaultLibFileName: options => ts.getDefaultLibFilePath(options), 138 fileExists: ts.sys.fileExists, 139 readFile: ts.sys.readFile, 140 readDirectory: ts.sys.readDirectory, 141 resolveModuleNames: resolveModuleNames, 142 directoryExists: ts.sys.directoryExists, 143 getDirectories: ts.sys.getDirectories, 144 getTagNameNeededCheckByFile: (fileName, sourceFileName) => { 145 let needCheckResult: boolean = false; 146 if ((/compiler\/declarations/.test(sourceFileName) || /ets-loader\/declarations/.test(sourceFileName)) && 147 isCardFile(fileName)) { 148 needCheckResult = true; 149 } 150 return { 151 needCheck: needCheckResult, 152 checkConfig: [{ 153 tagName: "form", 154 message: "'{0}' can't support form application.", 155 needConditionCheck: false, 156 type: ts.DiagnosticCategory.Error, 157 specifyCheckConditionFuncName: '', 158 tagNameShouldExisted: true 159 }] 160 } 161 } 162 }; 163 return ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); 164} 165 166interface CacheFileName { 167 mtimeMs: number, 168 children: string[], 169 parent: string[], 170 error: boolean 171} 172interface NeedUpdateFlag { 173 flag: boolean; 174} 175interface CheckerResult { 176 count: number 177} 178 179interface WarnCheckerResult { 180 count: number 181} 182 183interface WholeCache { 184 runtimeOS: string, 185 sdkInfo: string, 186 fileList: Cache 187} 188type Cache = Record<string, CacheFileName>; 189export let cache: Cache = {}; 190export const hotReloadSupportFiles: Set<string> = new Set(); 191export const shouldResolvedFiles: Set<string> = new Set(); 192const allResolvedModules: Set<string> = new Set(); 193 194let fastBuildLogger = null; 195 196export const checkerResult: CheckerResult = {count: 0}; 197export const warnCheckerResult: WarnCheckerResult = {count: 0}; 198export function serviceChecker(rootFileNames: string[], newLogger: any = null): void { 199 fastBuildLogger = newLogger; 200 let languageService: ts.LanguageService = null; 201 let cacheFile: string = null; 202 if (projectConfig.xtsMode) { 203 languageService = createLanguageService(rootFileNames); 204 } else { 205 cacheFile = path.resolve(projectConfig.cachePath, '../.ts_checker_cache'); 206 const wholeCache: WholeCache = fs.existsSync(cacheFile) ? 207 JSON.parse(fs.readFileSync(cacheFile).toString()) : 208 {'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {}}; 209 if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) { 210 cache = wholeCache.fileList; 211 } else { 212 cache = {}; 213 } 214 const filterFiles: string[] = filterInput(rootFileNames); 215 languageService = createLanguageService(filterFiles); 216 } 217 globalProgram.program = languageService.getProgram(); 218 const allDiagnostics: ts.Diagnostic[] = globalProgram.program 219 .getSyntacticDiagnostics() 220 .concat(globalProgram.program.getSemanticDiagnostics()) 221 .concat(globalProgram.program.getDeclarationDiagnostics()); 222 allDiagnostics.forEach((diagnostic: ts.Diagnostic) => { 223 printDiagnostic(diagnostic); 224 }); 225 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 226 fs.writeFileSync(cacheFile, JSON.stringify({ 227 'runtimeOS': projectConfig.runtimeOS, 228 'sdkInfo': projectConfig.sdkInfo, 229 'fileList': cache 230 }, null, 2)); 231 } 232 if (projectConfig.compileHar || projectConfig.compileShared) { 233 [...allResolvedModules, ...rootFileNames].forEach(moduleFile => { 234 if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar)) { 235 try { 236 const emit: any = languageService.getEmitOutput(moduleFile, true, true); 237 if (emit.outputFiles[0]) { 238 generateSourceFilesInHar(moduleFile, emit.outputFiles[0].text, '.d' + path.extname(moduleFile), 239 projectConfig); 240 } else { 241 console.warn(this.yellow, 242 "ArkTS:WARN doesn't generate .d" + path.extname(moduleFile) + ' for ' + moduleFile, this.reset); 243 } 244 } catch (err) {} 245 } 246 }); 247 } 248} 249 250function isCardFile(file: string): boolean { 251 for (const key in projectConfig.cardEntryObj) { 252 if (path.normalize(projectConfig.cardEntryObj[key]) === path.normalize(file)) { 253 return true; 254 } 255 } 256 return false; 257} 258 259function containFormError(message: string): boolean { 260 if (/can't support form application./.test(message)) { 261 return true; 262 } 263 return false; 264} 265 266export function printDiagnostic(diagnostic: ts.Diagnostic): void { 267 const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); 268 if (validateError(message)) { 269 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 270 updateErrorFileCache(diagnostic); 271 } 272 273 if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) { 274 return; 275 } 276 277 const logPrefix: string = diagnostic.category === ts.DiagnosticCategory.Error ? 'ERROR' : 'WARN'; 278 const etsCheckerLogger = fastBuildLogger ? fastBuildLogger : logger; 279 let logMessage: string; 280 if (logPrefix === 'ERROR') { 281 checkerResult.count += 1; 282 } else { 283 warnCheckerResult.count += 1; 284 } 285 if (diagnostic.file) { 286 const { line, character }: ts.LineAndCharacter = 287 diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); 288 logMessage = `ArkTS:${logPrefix} File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`; 289 } else { 290 logMessage = `ArkTS:${logPrefix}: ${message}`; 291 } 292 293 if (diagnostic.category === ts.DiagnosticCategory.Error) { 294 etsCheckerLogger.error('\u001b[31m' + logMessage); 295 } else { 296 etsCheckerLogger.warn('\u001b[33m' + logMessage); 297 } 298 } 299} 300 301function validateError(message: string): boolean { 302 const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/; 303 const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/; 304 if (matchMessage(message, props, propInfoReg) || 305 matchMessage(message, props, stateInfoReg)) { 306 return false; 307 } 308 return true; 309} 310function matchMessage(message: string, nameArr: any, reg: RegExp): boolean { 311 if (reg.test(message)) { 312 const match: string[] = message.match(reg); 313 if (match[1] && nameArr.includes(match[1])) { 314 return true; 315 } 316 } 317 return false; 318} 319 320function updateErrorFileCache(diagnostic: ts.Diagnostic): void { 321 if (diagnostic.file && cache[path.resolve(diagnostic.file.fileName)]) { 322 cache[path.resolve(diagnostic.file.fileName)].error = true; 323 } 324} 325 326function filterInput(rootFileNames: string[]): string[] { 327 return rootFileNames.filter((file: string) => { 328 const needUpdate: NeedUpdateFlag = { flag: false }; 329 const alreadyCheckedFiles: Set<string> = new Set(); 330 checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles); 331 return needUpdate.flag; 332 }); 333} 334 335function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set<string>): void { 336 if (alreadyCheckedFiles.has(file)) { 337 return; 338 } else { 339 alreadyCheckedFiles.add(file); 340 } 341 342 if (needUpdate.flag) { 343 return; 344 } 345 346 const value: CacheFileName = cache[file]; 347 const mtimeMs: number = fs.statSync(file).mtimeMs; 348 if (value) { 349 if (value.error || value.mtimeMs !== mtimeMs) { 350 needUpdate.flag = true; 351 return; 352 } 353 for (let i = 0; i < value.children.length; ++i) { 354 if (fs.existsSync(value.children[i])) { 355 checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles); 356 } else { 357 needUpdate.flag = true; 358 } 359 } 360 } else { 361 cache[file] = { mtimeMs, children: [], parent: [], error: false }; 362 needUpdate.flag = true; 363 } 364} 365 366const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map(); 367 368export function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] { 369 const resolvedModules: ts.ResolvedModuleFull[] = []; 370 if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile)) 371 || !(resolvedModulesCache[path.resolve(containingFile)] && 372 resolvedModulesCache[path.resolve(containingFile)].length === moduleNames.length)) { 373 for (const moduleName of moduleNames) { 374 const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, { 375 fileExists(fileName: string): boolean { 376 return ts.sys.fileExists(fileName); 377 }, 378 readFile(fileName: string): string | undefined { 379 return ts.sys.readFile(fileName); 380 }, 381 realpath(path: string): string { 382 return ts.sys.realpath(path); 383 } 384 }); 385 if (result.resolvedModule) { 386 if (result.resolvedModule.resolvedFileName && 387 path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) { 388 const resultDETSPath: string = 389 result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS); 390 if (ts.sys.fileExists(resultDETSPath)) { 391 resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS)); 392 } else { 393 resolvedModules.push(result.resolvedModule); 394 } 395 } else { 396 resolvedModules.push(result.resolvedModule); 397 } 398 } else if (/^@(system|ohos)\./i.test(moduleName.trim())) { 399 const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts'); 400 if (systemModules.includes(moduleName + '.d.ts') && ts.sys.fileExists(modulePath)) { 401 resolvedModules.push(getResolveModule(modulePath, '.d.ts')); 402 } else { 403 resolvedModules.push(null); 404 } 405 } else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) { 406 const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); 407 if (ts.sys.fileExists(modulePath)) { 408 resolvedModules.push(getResolveModule(modulePath, '.ets')); 409 } else { 410 resolvedModules.push(null); 411 } 412 } else if (/\.ts$/.test(moduleName)) { 413 const modulePath: string = path.resolve(path.dirname(containingFile), moduleName); 414 if (ts.sys.fileExists(modulePath)) { 415 resolvedModules.push(getResolveModule(modulePath, '.ts')); 416 } else { 417 resolvedModules.push(null); 418 } 419 } else { 420 const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts'); 421 const suffix: string = /\.js$/.test(moduleName) ? '' : '.js'; 422 const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix); 423 const fileModulePath: string = 424 path.resolve(__dirname, '../node_modules', moduleName + '/index.js'); 425 const DETSModulePath: string = path.resolve(path.dirname(containingFile), 426 /\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS); 427 if (ts.sys.fileExists(modulePath)) { 428 resolvedModules.push(getResolveModule(modulePath, '.d.ts')); 429 } else if (ts.sys.fileExists(jsModulePath)) { 430 resolvedModules.push(getResolveModule(jsModulePath, '.js')); 431 } else if (ts.sys.fileExists(fileModulePath)) { 432 resolvedModules.push(getResolveModule(fileModulePath, '.js')); 433 } else if (ts.sys.fileExists(DETSModulePath)) { 434 resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets')); 435 } else { 436 const srcIndex: number = projectConfig.projectPath.indexOf('src' + path.sep + 'main'); 437 let DETSModulePathFromModule: string; 438 if (srcIndex > 0) { 439 DETSModulePathFromModule = path.resolve( 440 projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS); 441 if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) { 442 resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets')); 443 } else { 444 resolvedModules.push(null); 445 } 446 } else { 447 resolvedModules.push(null); 448 } 449 } 450 } 451 if (projectConfig.hotReload && resolvedModules.length && 452 resolvedModules[resolvedModules.length - 1]) { 453 hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName)); 454 } 455 if ((projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] && 456 path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/)) { 457 allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName); 458 } 459 } 460 if (!projectConfig.xtsMode) { 461 createOrUpdateCache(resolvedModules, path.resolve(containingFile)); 462 } 463 resolvedModulesCache[path.resolve(containingFile)] = resolvedModules; 464 return resolvedModules; 465 } 466 return resolvedModulesCache[path.resolve(containingFile)]; 467} 468 469function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void { 470 const children: string[] = []; 471 const error: boolean = false; 472 resolvedModules.forEach(moduleObj => { 473 if (moduleObj && moduleObj.resolvedFileName && /(?<!\.d)\.(ets|ts)$/.test(moduleObj.resolvedFileName)) { 474 const file: string = path.resolve(moduleObj.resolvedFileName); 475 const mtimeMs: number = fs.statSync(file).mtimeMs; 476 children.push(file); 477 const value: CacheFileName = cache[file]; 478 if (value) { 479 value.mtimeMs = mtimeMs; 480 value.error = error; 481 value.parent = value.parent || []; 482 value.parent.push(path.resolve(containingFile)); 483 value.parent = [...new Set(value.parent)]; 484 } else { 485 cache[file] = { mtimeMs, children: [], parent: [containingFile], error }; 486 } 487 } 488 }); 489 cache[path.resolve(containingFile)] = { mtimeMs: fs.statSync(containingFile).mtimeMs, children, 490 parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ? 491 cache[path.resolve(containingFile)].parent : [], error }; 492} 493 494export function createWatchCompilerHost(rootFileNames: string[], 495 reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function, 496 isPipe: boolean = false): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> { 497 if (projectConfig.hotReload) { 498 rootFileNames.forEach(fileName => { 499 hotReloadSupportFiles.add(fileName); 500 }); 501 } 502 setCompilerOptions(); 503 const createProgram = ts.createSemanticDiagnosticsBuilderProgram; 504 const host = ts.createWatchCompilerHost( 505 [...rootFileNames, ...readDeaclareFiles()], compilerOptions, 506 ts.sys, createProgram, reportDiagnostic, 507 (diagnostic: ts.Diagnostic) => { 508 if ([6031, 6032].includes(diagnostic.code)) { 509 if (!isPipe) { 510 process.env.watchTs = 'start'; 511 resetErrorCount(); 512 } 513 } 514 // End of compilation in watch mode flag. 515 if ([6193, 6194].includes(diagnostic.code)) { 516 if (!isPipe) { 517 process.env.watchTs = 'end'; 518 if (fastBuildLogger) { 519 fastBuildLogger.debug(TS_WATCH_END_MSG); 520 tsWatchEmitter.emit(TS_WATCH_END_MSG); 521 } 522 } 523 delayPrintLogCount(); 524 } 525 }); 526 host.readFile = (fileName: string) => { 527 if (!fs.existsSync(fileName)) { 528 return undefined; 529 } 530 if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) { 531 let content: string = processContent(fs.readFileSync(fileName).toString(), fileName); 532 const extendFunctionInfo: extendInfo[] = []; 533 content = instanceInsteadThis(content, fileName, extendFunctionInfo); 534 return content; 535 } 536 return fs.readFileSync(fileName).toString(); 537 }; 538 host.resolveModuleNames = resolveModuleNames; 539 return host; 540} 541 542export function watchChecker(rootFileNames: string[], newLogger: any = null): void { 543 fastBuildLogger = newLogger; 544 globalProgram.watchProgram = ts.createWatchProgram( 545 createWatchCompilerHost(rootFileNames, printDiagnostic, () => {}, () => {})); 546} 547 548export function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[]): string { 549 checkUISyntax(content, fileName, extendFunctionInfo); 550 extendFunctionInfo.reverse().forEach((item) => { 551 const subStr: string = content.substring(item.start, item.end); 552 const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => { 553 return item1 + item.compName + 'Instance' + item2; 554 }); 555 content = content.slice(0, item.start) + insert + content.slice(item.end); 556 }); 557 return content; 558} 559 560function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull { 561 return { 562 resolvedFileName: modulePath, 563 isExternalLibraryImport: false, 564 extension: type 565 }; 566} 567 568export const dollarCollection: Set<string> = new Set(); 569export const decoratorParamsCollection: Set<string> = new Set(); 570export const extendCollection: Set<string> = new Set(); 571export const importModuleCollection: Set<string> = new Set(); 572 573function checkUISyntax(source: string, fileName: string, extendFunctionInfo: extendInfo[]): void { 574 if (/\.ets$/.test(fileName)) { 575 if (process.env.compileMode === 'moduleJson' || 576 path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) { 577 const sourceFile: ts.SourceFile = ts.createSourceFile(fileName, source, 578 ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS); 579 parseAllNode(sourceFile, sourceFile, extendFunctionInfo); 580 props.push(...dollarCollection, ...decoratorParamsCollection, ...extendCollection); 581 } 582 } 583} 584 585function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void { 586 if (ts.isStructDeclaration(node)) { 587 if (node.members) { 588 node.members.forEach(item => { 589 if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { 590 const propertyName: string = item.name.getText(); 591 if (item.decorators && item.decorators.length) { 592 for (let i = 0; i < item.decorators.length; i++) { 593 const decoratorName: string = item.decorators[i].getText().replace(/\(.*\)$/, '').trim(); 594 if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { 595 dollarCollection.add('$' + propertyName); 596 } 597 if (isDecoratorCollection(item.decorators[i], decoratorName)) { 598 decoratorParamsCollection.add(item.decorators[i].expression.arguments[0].getText()); 599 } 600 } 601 } 602 } 603 }); 604 } 605 } 606 if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION || 607 (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) && 608 hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { 609 if (node.body && node.body.statements && node.body.statements.length) { 610 const checkProp: ts.NodeArray<ts.Statement> = node.body.statements; 611 checkProp.forEach((item, index) => { 612 traverseBuild(item, index); 613 }); 614 } 615 } 616 if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) { 617 if (node.body && node.body.statements && node.body.statements.length && 618 !isOriginalExtend(node.body)) { 619 extendFunctionInfo.push({start: node.pos, end: node.end, compName: isExtendFunction(node)}); 620 } 621 } 622 node.getChildren().forEach((item: ts.Node) => parseAllNode(item, sourceFileNode, extendFunctionInfo)); 623} 624 625function isForeachAndLzayForEach(node: ts.Node): boolean { 626 return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) && 627 FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] && 628 ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body); 629} 630 631function traverseBuild(node: ts.Node, index: number): void { 632 if (ts.isExpressionStatement(node)) { 633 let parentComponentName: string = getName(node); 634 if (!INNER_COMPONENT_NAMES.has(parentComponentName) && node.parent && node.parent.statements && index >= 1 && 635 node.parent.statements[index - 1].expression && node.parent.statements[index - 1].expression.expression) { 636 parentComponentName = node.parent.statements[index - 1].expression.expression.escapedText; 637 } 638 node = node.expression; 639 if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 640 ts.isIdentifier(node.expression) && !$$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { 641 node.body.statements.forEach((item: ts.Statement, indexBlock: number) => { 642 traverseBuild(item, indexBlock); 643 }); 644 } else if (isForeachAndLzayForEach(node)) { 645 node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => { 646 traverseBuild(item, indexBlock); 647 }); 648 } else { 649 loopNodeFindDoubleDollar(node, parentComponentName); 650 } 651 } else if (ts.isIfStatement(node)) { 652 if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) { 653 node.thenStatement.statements.forEach((item, indexIfBlock) => { 654 traverseBuild(item, indexIfBlock); 655 }); 656 } 657 if (node.elseStatement && ts.isBlock(node.elseStatement) && node.elseStatement.statements) { 658 node.elseStatement.statements.forEach((item, indexElseBlock) => { 659 traverseBuild(item, indexElseBlock); 660 }); 661 } 662 } 663} 664 665function isPropertiesAddDoubleDollar(node: ts.Node): boolean { 666 if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) { 667 return true; 668 } else if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) && 669 ts.isIdentifier(node.expression) && $$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) { 670 return true; 671 } else { 672 return false; 673 } 674} 675function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void { 676 while (node) { 677 if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { 678 const argument: ts.NodeArray<ts.Node> = node.arguments; 679 const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name; 680 if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) { 681 argument.forEach((item: ts.Node) => { 682 doubleDollarCollection(item); 683 }); 684 } 685 } else if (isPropertiesAddDoubleDollar(node)) { 686 node.arguments.forEach((item: ts.Node) => { 687 if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) { 688 item.properties.forEach((param: ts.Node) => { 689 if (isObjectPram(param, parentComponentName)) { 690 doubleDollarCollection(param.initializer); 691 } 692 }); 693 } 694 if (STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()) && ts.isPropertyAccessExpression(item)) { 695 doubleDollarCollection(item); 696 } 697 }); 698 } 699 node = node.expression; 700 } 701} 702 703function doubleDollarCollection(item: ts.Node): void { 704 if (item.getText().startsWith($$)) { 705 while (item.expression) { 706 item = item.expression; 707 } 708 dollarCollection.add(item.getText()); 709 } 710} 711 712function isObjectPram(param: ts.Node, parentComponentName:string): boolean { 713 return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) && 714 param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 715 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText()); 716} 717 718function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean { 719 return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) && 720 PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) || 721 STYLE_ADD_DOUBLE_DOLLAR.has(propertyName); 722} 723 724function isDecoratorCollection(item: ts.Decorator, decoratorName: string): boolean { 725 return COMPONENT_DECORATORS_PARAMS.has(decoratorName) && 726 // @ts-ignore 727 item.expression.arguments && item.expression.arguments.length && 728 // @ts-ignore 729 ts.isIdentifier(item.expression.arguments[0]); 730} 731 732function processContent(source: string, id: string): string { 733 if (fastBuildLogger) { 734 source = visualTransform(source, id, fastBuildLogger); 735 } 736 source = preprocessExtend(source, extendCollection); 737 source = preprocessNewExtend(source, extendCollection); 738 return source; 739} 740 741function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set<string>): void { 742 if (shouldResolvedFiles.has(file)) { 743 return; 744 } 745 shouldResolvedFiles.add(file); 746 if (cache && cache[file] && cache[file].parent) { 747 cache[file].parent.forEach((item) => { 748 judgeFileShouldResolved(item, shouldResolvedFiles); 749 }); 750 cache[file].parent = []; 751 } 752 if (cache && cache[file] && cache[file].children) { 753 cache[file].children.forEach((item) => { 754 judgeFileShouldResolved(item, shouldResolvedFiles); 755 }); 756 cache[file].children = []; 757 } 758} 759 760export function incrementWatchFile(watchModifiedFiles: string[], 761 watchRemovedFiles: string[]): void { 762 const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles]; 763 if (changedFiles.length) { 764 shouldResolvedFiles.clear(); 765 } 766 changedFiles.forEach((file) => { 767 judgeFileShouldResolved(file, shouldResolvedFiles); 768 }); 769} 770