1/* 2 * Copyright (c) 2023 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 ts from 'typescript'; 17import path from 'path'; 18import fs from 'fs'; 19import { createFilter } from '@rollup/pluginutils'; 20import MagicString from 'magic-string'; 21import nodeEvents from 'node:events'; 22 23import { 24 LogInfo, 25 componentInfo, 26 emitLogInfo, 27 getTransformLog, 28 genTemporaryPath, 29 writeFileSync, 30 getAllComponentsOrModules, 31 writeCollectionFile, 32 storedFileInfo, 33 fileInfo, 34 resourcesRawfile, 35 differenceResourcesRawfile, 36 CacheFile, 37 startTimeStatisticsLocation, 38 stopTimeStatisticsLocation, 39 CompilationTimeStatistics, 40 genLoaderOutPathOfHar, 41 harFilesRecord, 42 resetUtils, 43 getResolveModules, 44 toUnixPath 45} from '../../utils'; 46import { 47 preprocessExtend, 48 preprocessNewExtend, 49 validateUISyntax, 50 propertyCollection, 51 linkCollection, 52 resetComponentCollection, 53 componentCollection, 54 resetValidateUiSyntax 55} from '../../validate_ui_syntax'; 56import { 57 processUISyntax, 58 resetLog, 59 transformLog, 60 resetProcessUiSyntax 61} from '../../process_ui_syntax'; 62import { 63 projectConfig, 64 abilityPagesFullPath, 65 globalProgram, 66 resetMain, 67 globalModulePaths 68} from '../../../main'; 69import { 70 appComponentCollection, 71 compilerOptions as etsCheckerCompilerOptions, 72 resolveModuleNames, 73 resolveTypeReferenceDirectives, 74 resetEtsCheck, 75 collectAllFiles, 76 allModuleIds, 77 resetEtsCheckTypeScript 78} from '../../ets_checker'; 79import { 80 CUSTOM_BUILDER_METHOD, 81 GLOBAL_CUSTOM_BUILDER_METHOD, 82 INNER_CUSTOM_BUILDER_METHOD, 83 resetComponentMap, 84 INNER_CUSTOM_LOCALBUILDER_METHOD 85} from '../../component_map'; 86import { 87 kitTransformLog, 88 processKitImport, 89 checkHasKeepTs, 90 resetKitImportLog 91} from '../../process_kit_import'; 92import { resetProcessComponentMember } from '../../process_component_member'; 93import { mangleFilePath, resetObfuscation } from '../ark_compiler/common/ob_config_resolver'; 94import arkoalaProgramTransform, { ArkoalaPluginOptions } from './arkoala-plugin'; 95import processStructComponentV2 from '../../process_struct_componentV2'; 96import { resetlogMessageCollection } from '../../log_message_collection'; 97 98const filter:any = createFilter(/(?<!\.d)\.(ets|ts)$/); 99 100let shouldDisableCache: boolean = false; 101interface ShouldEnableDebugLineType { 102 enableDebugLine: boolean; 103} 104 105export const ShouldEnableDebugLine: ShouldEnableDebugLineType = { 106 enableDebugLine: false 107}; 108 109let disableCacheOptions = { 110 bundleName: 'default', 111 entryModuleName: 'default', 112 runtimeOS: 'default', 113 resourceTableHash: 'default', 114 etsLoaderVersion: 'default' 115}; 116 117export function etsTransform() { 118 const allFilesInHar: Map<string, string> = new Map(); 119 let cacheFile: { [fileName: string]: CacheFile }; 120 return { 121 name: 'etsTransform', 122 transform: transform, 123 buildStart() { 124 const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'buildStart'); 125 startTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformBuildStartTime : undefined); 126 judgeCacheShouldDisabled.call(this); 127 if (process.env.compileMode === 'moduleJson') { 128 cacheFile = this.cache.get('transformCacheFiles'); 129 storedFileInfo.addGlobalCacheInfo(this.cache.get('resourceListCacheInfo'), 130 this.cache.get('resourceToFileCacheInfo'), cacheFile); 131 if (this.cache.get('lastResourcesArr')) { 132 storedFileInfo.lastResourcesSet = new Set([...this.cache.get('lastResourcesArr')]); 133 } 134 if (process.env.rawFileResource) { 135 resourcesRawfile(process.env.rawFileResource, storedFileInfo.resourcesArr); 136 this.share.rawfilechanged = differenceResourcesRawfile(storedFileInfo.lastResourcesSet, storedFileInfo.resourcesArr); 137 } 138 } 139 if (!!this.cache.get('enableDebugLine') !== projectConfig.enableDebugLine) { 140 ShouldEnableDebugLine.enableDebugLine = true; 141 } 142 stopTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformBuildStartTime : undefined); 143 }, 144 load(id: string) { 145 let fileCacheInfo: fileInfo; 146 const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'load'); 147 startTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformLoadTime : undefined); 148 if (this.cache.get('fileCacheInfo')) { 149 fileCacheInfo = this.cache.get('fileCacheInfo')[path.resolve(id)]; 150 } 151 // Exclude Component Preview page 152 if (projectConfig.isPreview && !projectConfig.checkEntry && id.match(/(?<!\.d)\.(ets)$/)) { 153 abilityPagesFullPath.add(path.resolve(id).toLowerCase()); 154 storedFileInfo.judgeShouldHaveEntryFiles(abilityPagesFullPath); 155 } 156 storedFileInfo.addFileCacheInfo(path.resolve(id), fileCacheInfo); 157 storedFileInfo.setCurrentArkTsFile(); 158 stopTimeStatisticsLocation(compilationTime ? compilationTime.etsTransformLoadTime : undefined); 159 }, 160 shouldInvalidCache(options) { 161 const fileName: string = path.resolve(options.id); 162 let shouldDisable: boolean = shouldDisableCache || disableNonEntryFileCache(fileName) || ShouldEnableDebugLine.enableDebugLine; 163 if (process.env.compileMode === 'moduleJson') { 164 shouldDisable = shouldDisable || storedFileInfo.shouldInvalidFiles.has(fileName) || this.share.rawfilechanged; 165 if (cacheFile && cacheFile[fileName] && cacheFile[fileName].children.length) { 166 for (let child of cacheFile[fileName].children) { 167 const newTimeMs: number = fs.existsSync(child.fileName) ? fs.statSync(child.fileName).mtimeMs : -1; 168 if (newTimeMs !== child.mtimeMs) { 169 shouldDisable = true; 170 break; 171 } 172 } 173 } 174 } 175 // If a file import an const enum object from other changed file, this file also need to be transformed. 176 shouldDisable = shouldDisable || checkRelateToConstEnum(options.id); 177 if (!shouldDisable) { 178 storedFileInfo.collectCachedFiles(fileName); 179 } 180 return shouldDisable; 181 }, 182 buildEnd(): void { 183 if (process.env.watchMode !== 'true' && !projectConfig.hotReload && !projectConfig.isPreview) { 184 resetEtsCheckTypeScript(); 185 } 186 }, 187 afterBuildEnd() { 188 // Copy the cache files in the compileArkTS directory to the loader_out directory 189 if (projectConfig.compileHar && !projectConfig.byteCodeHar) { 190 for (let moduleInfoId of allModuleIds.keys()) { 191 const moduleInfo: Object = this.getModuleInfo(moduleInfoId); 192 if (!moduleInfo) { 193 continue; 194 } 195 if (moduleInfoId && !moduleInfoId.match(new RegExp(projectConfig.packageDir)) && 196 !moduleInfoId.startsWith('\x00') && 197 path.resolve(moduleInfoId).startsWith(projectConfig.moduleRootPath + path.sep)) { 198 let filePath: string = moduleInfoId; 199 const metaInfo: Object = moduleInfo.meta; 200 if (this.share.arkProjectConfig?.obfuscationMergedObConfig?.options?.enableFileNameObfuscation) { 201 filePath = mangleFilePath(filePath); 202 } 203 204 const cacheFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath, 205 process.env.cachePath, projectConfig, metaInfo); 206 const buildFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath, 207 projectConfig.buildPath, projectConfig, metaInfo, true); 208 if (filePath.match(/\.e?ts$/)) { 209 setIncrementalFileInHar(cacheFilePath, buildFilePath, allFilesInHar); 210 } else { 211 allFilesInHar.set(cacheFilePath, buildFilePath); 212 } 213 } 214 } 215 216 allFilesInHar.forEach((buildFilePath, cacheFilePath) => { 217 // if the ts or ets file code only contain interface, it doesn't have js file. 218 if (fs.existsSync(cacheFilePath)) { 219 const sourceCode: string = fs.readFileSync(cacheFilePath, 'utf-8'); 220 writeFileSync(buildFilePath, sourceCode); 221 } 222 }); 223 } 224 if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) { 225 let widgetPath: string; 226 if (projectConfig.widgetCompile) { 227 widgetPath = path.resolve(projectConfig.aceModuleBuild, 'widget'); 228 } 229 writeCollectionFile(projectConfig.cachePath, appComponentCollection, 230 this.share.allComponents, 'component_collection.json', this.share.allFiles, widgetPath); 231 } 232 shouldDisableCache = false; 233 this.cache.set('disableCacheOptions', disableCacheOptions); 234 this.cache.set('lastResourcesArr', [...storedFileInfo.resourcesArr]); 235 if (projectConfig.enableDebugLine) { 236 this.cache.set('enableDebugLine', true); 237 } else { 238 this.cache.set('enableDebugLine', false); 239 } 240 storedFileInfo.clearCollectedInfo(this.cache); 241 this.cache.set('transformCacheFiles', storedFileInfo.transformCacheFiles); 242 }, 243 cleanUp(): void { 244 resetMain(); 245 resetComponentMap(); 246 resetEtsCheck(); 247 resetEtsTransform(); 248 resetProcessComponentMember(); 249 resetProcessUiSyntax(); 250 resetUtils(); 251 resetValidateUiSyntax(); 252 resetObfuscation(); 253 resetlogMessageCollection(); 254 } 255 }; 256} 257 258// If a ArkTS file don't have @Entry decorator but it is entry file this time 259function disableNonEntryFileCache(filePath: string): boolean { 260 return storedFileInfo.buildStart && filePath.match(/(?<!\.d)\.(ets)$/) && 261 !storedFileInfo.wholeFileInfo[filePath].hasEntry && 262 storedFileInfo.shouldHaveEntry.includes(filePath); 263} 264 265function judgeCacheShouldDisabled(): void { 266 for (const key in disableCacheOptions) { 267 if (this.cache.get('disableCacheOptions') && this.share && 268 this.share.projectConfig && this.share.projectConfig[key] && 269 this.cache.get('disableCacheOptions')[key] !== this.share.projectConfig[key]) { 270 if (key === 'resourceTableHash' && process.env.compileMode === 'moduleJson') { 271 storedFileInfo.resourceTableChanged = true; 272 } else if (!shouldDisableCache) { 273 shouldDisableCache = true; 274 } 275 } 276 if (this.share && this.share.projectConfig && this.share.projectConfig[key]) { 277 disableCacheOptions[key] = this.share.projectConfig[key]; 278 } 279 storedFileInfo.judgeShouldHaveEntryFiles(abilityPagesFullPath); 280 } 281} 282 283interface EmitResult { 284 outputText: string, 285 sourceMapText: string, 286} 287 288function getCompilerHost(): ts.CompilerHost { 289 const compilerHost: ts.CompilerHost = ts.createCompilerHost(etsCheckerCompilerOptions); 290 compilerHost.writeFile = () => {}; 291 compilerHost.resolveModuleNames = resolveModuleNames; 292 compilerHost.getCurrentDirectory = () => process.cwd(); 293 compilerHost.getDefaultLibFileName = options => ts.getDefaultLibFilePath(options); 294 compilerHost.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives; 295 return compilerHost; 296} 297 298let compilerHost: ts.CompilerHost = null; 299 300if (!compilerHost) { 301 compilerHost = getCompilerHost(); 302} 303 304const arkoalaTsProgramCache: WeakMap<ts.Program, ts.Program> = new WeakMap(); 305 306function getArkoalaTsProgram(program: ts.Program): ts.Program { 307 let extendedProgram = arkoalaTsProgramCache.get(program); 308 if (!extendedProgram) { 309 const pluginOptions: ArkoalaPluginOptions = {}; 310 // This is a stub for the interface generated by ts-patch. 311 // Probably we can use the reported diagnostics in the output 312 const pluginTransformExtras: Object = { 313 diagnostics: [], 314 addDiagnostic(): number {return 0}, 315 removeDiagnostic(): void {}, 316 ts: ts, 317 library: 'typescript', 318 }; 319 extendedProgram = arkoalaProgramTransform(program, compilerHost, pluginOptions, pluginTransformExtras); 320 arkoalaTsProgramCache.set(program, extendedProgram); 321 } 322 return extendedProgram; 323} 324 325async function transform(code: string, id: string) { 326 const compilationTime: CompilationTimeStatistics = new CompilationTimeStatistics(this.share, 'etsTransform', 'transform'); 327 if (!filter(id)) { 328 return null; 329 } 330 331 storedFileInfo.collectTransformedFiles(path.resolve(id)); 332 333 const logger = this.share.getLogger('etsTransform'); 334 335 if (projectConfig.compileMode !== 'esmodule') { 336 const compilerOptions = ts.readConfigFile( 337 path.resolve(__dirname, '../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 338 compilerOptions.moduleResolution = 'nodenext'; 339 compilerOptions.module = 'es2020'; 340 const newContent: string = jsBundlePreProcess(code, id, this.getModuleInfo(id).isEntry, logger); 341 const result: ts.TranspileOutput = ts.transpileModule(newContent, { 342 compilerOptions: compilerOptions, 343 fileName: id, 344 transformers: { before: [processUISyntax(null)] } 345 }); 346 347 resetCollection(); 348 if (transformLog && transformLog.errors.length && !projectConfig.ignoreWarning) { 349 emitLogInfo(logger, getTransformLog(transformLog), true, id); 350 resetLog(); 351 } 352 353 return { 354 code: result.outputText, 355 map: result.sourceMapText ? JSON.parse(result.sourceMapText) : new MagicString(code).generateMap() 356 }; 357 } 358 359 let tsProgram: ts.Program = globalProgram.program; 360 let targetSourceFile: ts.SourceFile | undefined = tsProgram.getSourceFile(id); 361 362 // createProgram from the file which does not have corresponding ast from ets-checker's program 363 // by those following cases: 364 // 1. .ets/.ts imported by .js file with tsc's `allowJS` option is false. 365 // 2. .ets/.ts imported by .js file with same name '.d.ts' file which is prior to .js by tsc default resolving 366 if (!targetSourceFile) { 367 startTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); 368 if (storedFileInfo.isFirstBuild && storedFileInfo.changeFiles) { 369 storedFileInfo.newTsProgram = ts.createProgram(storedFileInfo.changeFiles, etsCheckerCompilerOptions, compilerHost); 370 storedFileInfo.isFirstBuild = false; 371 } 372 stopTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); 373 if (storedFileInfo.newTsProgram && storedFileInfo.newTsProgram.getSourceFile(id)) { 374 tsProgram = storedFileInfo.newTsProgram; 375 } else { 376 await CreateProgramMoment.block(id); 377 CreateProgramMoment.release(id); 378 if (storedFileInfo.newTsProgram && storedFileInfo.newTsProgram.getSourceFile(id)) { 379 tsProgram = storedFileInfo.newTsProgram; 380 } else { 381 startTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); 382 tsProgram = ts.createProgram(CreateProgramMoment.getRoots(id), etsCheckerCompilerOptions, compilerHost); 383 storedFileInfo.newTsProgram = tsProgram; 384 stopTimeStatisticsLocation(compilationTime ? compilationTime.noSourceFileRebuildProgramTime : undefined); 385 } 386 } 387 388 // init TypeChecker to run binding 389 globalProgram.checker = tsProgram.getTypeChecker(); 390 globalProgram.strictChecker = tsProgram.getLinterTypeChecker(); 391 targetSourceFile = tsProgram.getSourceFile(id)!; 392 storedFileInfo.reUseProgram = false; 393 collectAllFiles(tsProgram); 394 } else { 395 if (!storedFileInfo.reUseProgram) { 396 globalProgram.checker = globalProgram.program.getTypeChecker(); 397 globalProgram.strictChecker = globalProgram.program.getLinterTypeChecker(); 398 } 399 storedFileInfo.reUseProgram = true; 400 } 401 setPkgNameForFile(this.getModuleInfo(id)); 402 startTimeStatisticsLocation(compilationTime ? compilationTime.validateEtsTime : undefined); 403 validateEts(code, id, this.getModuleInfo(id).isEntry, logger, targetSourceFile); 404 stopTimeStatisticsLocation(compilationTime ? compilationTime.validateEtsTime : undefined); 405 const emitResult: EmitResult = { outputText: '', sourceMapText: '' }; 406 const writeFile: ts.WriteFileCallback = (fileName: string, data: string) => { 407 if (/.map$/.test(fileName)) { 408 emitResult.sourceMapText = data; 409 } else { 410 emitResult.outputText = data; 411 } 412 }; 413 414 // close `noEmit` to make invoking emit() effective. 415 tsProgram.getCompilerOptions().noEmit = false; 416 const metaInfo: Object = this.getModuleInfo(id).meta; 417 // use `try finally` to restore `noEmit` when error thrown by `processUISyntax` in preview mode 418 startTimeStatisticsLocation(compilationTime ? compilationTime.shouldEmitJsTime : undefined); 419 const shouldEmitJsFlag: boolean = getShouldEmitJs(projectConfig.shouldEmitJs, targetSourceFile); 420 stopTimeStatisticsLocation(compilationTime ? compilationTime.shouldEmitJsTime : undefined); 421 let transformResult: ts.TransformationResult<ts.SourceFile> = null; 422 try { 423 startTimeStatisticsLocation(compilationTime ? compilationTime.tsProgramEmitTime : undefined); 424 if (projectConfig.useArkoala) { 425 tsProgram = getArkoalaTsProgram(tsProgram); 426 targetSourceFile = tsProgram.getSourceFile(id); 427 } 428 if (shouldEmitJsFlag) { 429 startTimeStatisticsLocation(compilationTime ? compilationTime.emitTime : undefined); 430 tsProgram.emit(targetSourceFile, writeFile, undefined, undefined, 431 { 432 before: [ 433 processUISyntax(null, false, compilationTime, id), 434 processKitImport(id, metaInfo, compilationTime) 435 ] 436 } 437 ); 438 stopTimeStatisticsLocation(compilationTime ? compilationTime.emitTime : undefined); 439 } else { 440 startTimeStatisticsLocation(compilationTime ? compilationTime.transformNodesTime : undefined); 441 const emitResolver: ts.EmitResolver = globalProgram.checker.getEmitResolver(outFile(tsProgram.getCompilerOptions()) ? 442 undefined : targetSourceFile, undefined); 443 transformResult = ts.transformNodes(emitResolver, tsProgram.getEmitHost?.(), ts.factory, 444 tsProgram.getCompilerOptions(), [targetSourceFile], 445 [processUISyntax(null, false, compilationTime, id), 446 processKitImport(id, metaInfo, compilationTime, false)], false); 447 stopTimeStatisticsLocation(compilationTime ? compilationTime.transformNodesTime : undefined); 448 } 449 stopTimeStatisticsLocation(compilationTime ? compilationTime.tsProgramEmitTime : undefined); 450 } finally { 451 // restore `noEmit` to prevent tsc's watchService emitting automatically. 452 tsProgram.getCompilerOptions().noEmit = true; 453 } 454 455 resetCollection(); 456 processStructComponentV2.resetStructMapInEts(); 457 if (((transformLog && transformLog.errors.length) || (kitTransformLog && kitTransformLog.errors.length)) && 458 !projectConfig.ignoreWarning) { 459 emitLogInfo(logger, [...getTransformLog(kitTransformLog), ...getTransformLog(transformLog)], true, id); 460 resetLog(); 461 resetKitImportLog(); 462 } 463 464 return shouldEmitJsFlag ? { 465 code: emitResult.outputText, 466 // Use magicString to generate sourceMap because of Typescript do not emit sourceMap in some cases 467 map: emitResult.sourceMapText ? JSON.parse(emitResult.sourceMapText) : new MagicString(code).generateMap(), 468 meta: { 469 shouldEmitJs: true 470 } 471 } : printSourceFile(transformResult.transformed[0], compilationTime); 472} 473 474function printSourceFile(sourceFile: ts.SourceFile, compilationTime: CompilationTimeStatistics): string | null { 475 if (sourceFile) { 476 startTimeStatisticsLocation(compilationTime ? compilationTime.printNodeTime : undefined); 477 const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 478 const sourceCode: string = printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile); 479 stopTimeStatisticsLocation(compilationTime ? compilationTime.printNodeTime : undefined); 480 return sourceCode; 481 } 482 return null; 483} 484 485function outFile(options: ts.CompilerOptions): string { 486 return options.outFile || options.out; 487} 488 489function getShouldEmitJs(shouldEmitJs: boolean, targetSourceFile: ts.SourceFile): boolean { 490 let shouldEmitJsFlag: boolean = true; 491 let hasKeepTs: boolean = false; 492 if (!projectConfig.processTs) { 493 return shouldEmitJsFlag; 494 } 495 if (projectConfig.complieHar) { 496 if (!projectConfig.UseTsHar && !projectConfig.byteCodeHar) { 497 return shouldEmitJsFlag; 498 } 499 } else { 500 hasKeepTs = checkHasKeepTs(targetSourceFile); 501 } 502 // FA model/code coverage instrumentation/default situation 503 // These three situations require calling the emit interface, while in other cases 'shouldEmitJs' be false. 504 // The 'shouldEmitJS' variable is obtained through 'this.share.sprojectConfig'. 505 if (shouldEmitJs !== undefined) { 506 // ark es2abc 507 shouldEmitJsFlag = shouldEmitJs || ts.hasTsNoCheckOrTsIgnoreFlag(targetSourceFile) && !hasKeepTs; 508 } 509 return shouldEmitJsFlag; 510} 511 512function setPkgNameForFile(moduleInfo: Object): void { 513 if (moduleInfo && moduleInfo.meta && moduleInfo.meta.pkgName) { 514 storedFileInfo.getCurrentArkTsFile().pkgName = moduleInfo.meta.pkgName; 515 } 516} 517 518function validateEts(code: string, id: string, isEntry: boolean, logger: any, sourceFile: ts.SourceFile) { 519 if (/\.ets$/.test(id)) { 520 clearCollection(); 521 const fileQuery: string = isEntry && !abilityPagesFullPath.has(path.resolve(id).toLowerCase()) ? '?entry' : ''; 522 const log: LogInfo[] = validateUISyntax(code, code, id, fileQuery, sourceFile); 523 if (log.length && !projectConfig.ignoreWarning) { 524 emitLogInfo(logger, log, true, id); 525 } 526 } 527} 528 529function jsBundlePreProcess(code: string, id: string, isEntry: boolean, logger: any): string { 530 if (/\.ets$/.test(id)) { 531 clearCollection(); 532 let content = preprocessExtend(code); 533 content = preprocessNewExtend(content); 534 const fileQuery: string = isEntry && !abilityPagesFullPath.has(path.resolve(id).toLowerCase()) ? '?entry' : ''; 535 const log: LogInfo[] = validateUISyntax(code, content, id, fileQuery); 536 if (log.length && !projectConfig.ignoreWarning) { 537 emitLogInfo(logger, log, true, id); 538 } 539 return content; 540 } 541 return code; 542} 543 544function clearCollection(): void { 545 componentCollection.customComponents.clear(); 546 CUSTOM_BUILDER_METHOD.clear(); 547 INNER_CUSTOM_LOCALBUILDER_METHOD.clear(); 548 GLOBAL_CUSTOM_BUILDER_METHOD.clear(); 549 INNER_CUSTOM_BUILDER_METHOD.clear(); 550 storedFileInfo.getCurrentArkTsFile().compFromDETS.clear(); 551} 552 553function resetCollection() { 554 componentInfo.id = 0; 555 propertyCollection.clear(); 556 linkCollection.clear(); 557 resetComponentCollection(); 558 storedFileInfo.hasLocalBuilderInFile = false; 559} 560 561function resetEtsTransform(): void { 562 ShouldEnableDebugLine.enableDebugLine = false; 563 projectConfig.ignoreWarning = false; 564 projectConfig.widgetCompile = false; 565 compilerHost = null; 566 disableCacheOptions = { 567 bundleName: 'default', 568 entryModuleName: 'default', 569 runtimeOS: 'default', 570 resourceTableHash: 'default', 571 etsLoaderVersion: 'default' 572 }; 573} 574 575function findArkoalaRoot(): string { 576 let arkoalaSdkRoot: string; 577 if (process.env.ARKOALA_SDK_ROOT) { 578 arkoalaSdkRoot = process.env.ARKOALA_SDK_ROOT; 579 if (!isDir(arkoalaSdkRoot)) { 580 throw new Error('Arkoala SDK not found in ' + arkoalaSdkRoot); 581 } 582 } else { 583 const arkoalaPossiblePaths: string[] = globalModulePaths.map(dir => path.join(dir, '../../arkoala')); 584 arkoalaSdkRoot = arkoalaPossiblePaths.find(possibleRootDir => isDir(possibleRootDir)) ?? ''; 585 if (!arkoalaSdkRoot) { 586 throw new Error('Arkoala SDK not found in ' + arkoalaPossiblePaths.join(';')); 587 } 588 } 589 590 return arkoalaSdkRoot; 591} 592 593function isDir(filePath: string): boolean { 594 try { 595 let stat: fs.Stats = fs.statSync(filePath); 596 return stat.isDirectory(); 597 } catch (e) { 598 return false; 599 } 600} 601 602function setIncrementalFileInHar(cacheFilePath: string, buildFilePath: string, allFilesInHar: Map<string, string>): void { 603 if (cacheFilePath.match(/\.d.e?ts$/)) { 604 allFilesInHar.set(cacheFilePath, buildFilePath); 605 return; 606 } 607 let extName = projectConfig.useTsHar ? '.ts' : '.js'; 608 allFilesInHar.set(cacheFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts'), 609 buildFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts')); 610 allFilesInHar.set(cacheFilePath.replace(/\.e?ts$/, extName), buildFilePath.replace(/\.e?ts$/, extName)); 611} 612 613function checkRelateToConstEnum(id: string): boolean { 614 let tsProgram: ts.Program = globalProgram.builderProgram; 615 let targetSourceFile: ts.SourceFile | undefined = tsProgram ? tsProgram.getSourceFile(id) : undefined; 616 if (!targetSourceFile) { 617 return false; 618 } 619 if (!tsProgram.isFileUpdateInConstEnumCache) { 620 return false; 621 } 622 return tsProgram.isFileUpdateInConstEnumCache(targetSourceFile); 623} 624 625interface moduleInfoType { 626 id: string; 627}; 628interface optionsType { 629 id: string; 630}; 631class CreateProgramMoment { 632 static transFileCollect: Set<string> = new Set(); 633 static awaitFileCollect: Set<string> = new Set(); 634 static moduleParsedFileCollect: Set<string> = new Set(); 635 static promise: Promise<void> = undefined; 636 static emitter = undefined; 637 static roots: Set<string> = new Set(); 638 639 static init(): void { 640 if (CreateProgramMoment.promise) { 641 return; 642 } 643 CreateProgramMoment.roots.add(path.resolve(__dirname, '../../../declarations/common.d.ts')); 644 CreateProgramMoment.emitter = new nodeEvents.EventEmitter(); 645 CreateProgramMoment.promise = new Promise<void>(resolve => { 646 CreateProgramMoment.emitter.on('checkPrefCreateProgramId', () => { 647 if (CreateProgramMoment.awaitFileCollect.size + CreateProgramMoment.moduleParsedFileCollect.size === 648 CreateProgramMoment.transFileCollect.size) { 649 resolve(); 650 } 651 }); 652 }); 653 } 654 655 static getPlugin() { 656 return { 657 name: 'createProgramPlugin', 658 load: { 659 order: 'pre', 660 handler(id: string) { 661 CreateProgramMoment.transFileCollect.add(id); 662 } 663 }, 664 665 moduleParsed(moduleInfo: moduleInfoType): void { 666 CreateProgramMoment.moduleParsedFileCollect.add(moduleInfo.id); 667 CreateProgramMoment.emitter?.emit('checkPrefCreateProgramId'); 668 }, 669 cleanUp(): void { 670 CreateProgramMoment.reset(); 671 } 672 }; 673 } 674 675 static async block(id: string): Promise<void> { 676 CreateProgramMoment.init(); 677 CreateProgramMoment.awaitFileCollect.add(id); 678 CreateProgramMoment.roots.add(id); 679 CreateProgramMoment.emitter.emit('checkPrefCreateProgramId'); 680 return CreateProgramMoment.promise; 681 } 682 683 static release(id: string): void { 684 CreateProgramMoment.awaitFileCollect.delete(id); 685 } 686 687 static reset(): void { 688 CreateProgramMoment.transFileCollect.clear(); 689 CreateProgramMoment.awaitFileCollect.clear(); 690 CreateProgramMoment.moduleParsedFileCollect.clear(); 691 CreateProgramMoment.promise = undefined; 692 CreateProgramMoment.emitter = undefined; 693 CreateProgramMoment.roots.clear(); 694 } 695 696 static getRoots(id: string): string[] { 697 const res: string[] = []; 698 CreateProgramMoment.roots.forEach(id => res.push(id)); 699 CreateProgramMoment.promise = undefined; 700 CreateProgramMoment.emitter = undefined; 701 CreateProgramMoment.roots.clear(); 702 if (res.length === 0) { 703 return [id]; 704 } 705 return res; 706 } 707} 708 709exports.createProgramPlugin = CreateProgramMoment.getPlugin; 710