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