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