1/* 2 * Copyright (c) 2021 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 path from 'path'; 17import ts from 'typescript'; 18import fs from 'fs'; 19import os from 'os'; 20import uglifyJS from 'uglify-js'; 21 22import { 23 partialUpdateConfig, 24 projectConfig, 25 globalProgram 26} from '../main'; 27import { createHash } from 'crypto'; 28import type { Hash } from 'crypto'; 29import { 30 AUXILIARY, 31 EXTNAME_ETS, 32 EXTNAME_JS, 33 MAIN, 34 FAIL, 35 TEMPORARY, 36 ESMODULE, 37 $$, 38 EXTEND_DECORATORS, 39 COMPONENT_EXTEND_DECORATOR, 40 COMPONENT_ANIMATABLE_EXTEND_DECORATOR, 41 COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU, 42 GET_SHARED, 43 COMPONENT_CONSTRUCTOR_UNDEFINED, 44 USE_SHARED_STORAGE, 45 STORAGE 46} from './pre_define'; 47import { 48 ERROR_DESCRIPTION, 49 HvigorLogInfo, 50 HvigorErrorInfo 51} from './hvigor_error_code/hvigor_error_info'; 52 53export enum LogType { 54 ERROR = 'ERROR', 55 WARN = 'WARN', 56 NOTE = 'NOTE' 57} 58export const TEMPORARYS: string = 'temporarys'; 59export const BUILD: string = 'build'; 60export const SRC_MAIN: string = 'src/main'; 61 62const red: string = '\u001b[31m'; 63const reset: string = '\u001b[39m'; 64 65const WINDOWS: string = 'Windows_NT'; 66const LINUX: string = 'Linux'; 67const MAC: string = 'Darwin'; 68const HARMONYOS: string = 'HarmonyOS'; 69 70export interface LogInfo extends HvigorLogInfo { 71 type: LogType, 72 message: string, 73 pos?: number, 74 line?: number, 75 column?: number, 76 fileName?: string 77} 78 79export const repeatLog: Map<string, LogInfo> = new Map(); 80 81export interface IFileLog { 82 sourceFile: ts.SourceFile | undefined; 83 errors: LogInfo[]; 84 cleanUp(): void 85} 86 87export function buildErrorInfoFromLogInfo(info: LogInfo, fileName: string): HvigorErrorInfo { 88 const positionMessage: string = info.line && info.column ? `${fileName}:${info.line}:${info.column}` : fileName; 89 return new HvigorErrorInfo() 90 .setCode(info.code) 91 .setDescription(info.description ?? ERROR_DESCRIPTION) 92 .setCause(info.cause ?? info.message) 93 .setPosition(`File: ${positionMessage}`) 94 .setSolutions(info.solutions ?? []); 95} 96 97export function isHvigorLogInfo(logInfo: LogInfo): boolean { 98 // Hvigor logger requires code as mendatory attribute. 99 // ArkUI warnings or info are not using Hvigor logger to emit. 100 return logInfo.code !== undefined; 101} 102 103export function emitLogInfo(loader: Object, infos: LogInfo[], fastBuild: boolean = false, 104 resourcePath: string | null = null, hvigorLogger: Object | undefined = undefined): void { 105 if (infos && infos.length) { 106 infos.forEach((item) => { 107 hvigorLogger ? emitLogInfoFromHvigorLogger(hvigorLogger, item, loader, fastBuild, resourcePath) 108 : emitLogInfoFromLoader(loader, item, fastBuild, resourcePath); 109 }); 110 } 111} 112 113function emitLogInfoFromHvigorLogger(hvigorLogger: Object, info: LogInfo, loader: Object, 114 fastBuild: boolean, resourcePath: string | null): void { 115 if (!isHvigorLogInfo(info)) { 116 emitLogInfoFromLoader(loader, info, fastBuild, resourcePath); 117 return; 118 } 119 const errorInfo: HvigorErrorInfo = buildErrorInfoFromLogInfo(info, info.fileName || resourcePath); 120 switch (info.type) { 121 case LogType.ERROR: 122 hvigorLogger.printError(errorInfo); 123 break; 124 case LogType.WARN: 125 hvigorLogger.printWarn(errorInfo.cause); 126 break; 127 case LogType.NOTE: 128 hvigorLogger.printInfo(errorInfo.cause); 129 break; 130 } 131} 132 133function emitLogInfoFromLoader(loader: Object, info: LogInfo, fastBuild: boolean = false, 134 resourcePath: string | null = null): void { 135 const message: string = fastBuild ? getMessage(info.fileName || resourcePath, info, true) 136 : getMessage(info.fileName || loader.resourcePath, info); 137 switch (info.type) { 138 case LogType.ERROR: 139 fastBuild ? loader.error(message) : loader.emitError(message); 140 break; 141 case LogType.WARN: 142 fastBuild ? loader.warn(message) : loader.emitWarning(message); 143 break; 144 case LogType.NOTE: 145 fastBuild ? loader.info(message) : loader.emitWarning(message); 146 break; 147 } 148} 149 150export function addLog( 151 type: LogType, 152 message: string, 153 pos: number, 154 log: LogInfo[], 155 sourceFile: ts.SourceFile, 156 hvigorLogInfo?: HvigorLogInfo 157): void { 158 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(pos); 159 log.push({ 160 type: type, 161 message: message, 162 line: posOfNode.line + 1, 163 column: posOfNode.character + 1, 164 fileName: sourceFile.fileName, 165 ...hvigorLogInfo 166 }); 167} 168 169export function getMessage(fileName: string, info: LogInfo, fastBuild: boolean = false): string { 170 let message: string; 171 if (info.line && info.column) { 172 message = `BUILD${info.type} File: ${fileName}:${info.line}:${info.column}\n ${info.message}`; 173 } else { 174 message = `BUILD${info.type} File: ${fileName}\n ${info.message}`; 175 } 176 if (fastBuild) { 177 message = message.replace(/^BUILD/, 'ArkTS:'); 178 } 179 return message; 180} 181 182export function getTransformLog(transformLog: IFileLog): LogInfo[] { 183 const sourceFile: ts.SourceFile = transformLog.sourceFile; 184 const logInfos: LogInfo[] = transformLog.errors.map((item) => { 185 if (item.pos || item.pos === 0) { 186 if (!item.column || !item.line) { 187 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(item.pos); 188 item.line = posOfNode.line + 1; 189 item.column = posOfNode.character + 1; 190 } 191 } else { 192 item.line = item.line || undefined; 193 item.column = item.column || undefined; 194 } 195 if (!item.fileName) { 196 item.fileName = sourceFile.fileName; 197 } 198 return item; 199 }); 200 return logInfos; 201} 202 203class ComponentInfo { 204 private _id: number = 0; 205 private _componentNames: Set<string> = new Set(['ForEach']); 206 public set id(id: number) { 207 this._id = id; 208 } 209 public get id(): number { 210 return this._id; 211 } 212 public set componentNames(componentNames: Set<string>) { 213 this._componentNames = componentNames; 214 } 215 public get componentNames(): Set<string> { 216 return this._componentNames; 217 } 218} 219 220export let componentInfo: ComponentInfo = new ComponentInfo(); 221 222export function hasDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 223 ts.StructDeclaration | ts.ClassDeclaration, decortorName: string, 224 customBuilder: ts.Decorator[] = null, log: LogInfo[] = null): boolean { 225 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 226 if (decorators && decorators.length) { 227 const extendResult = { 228 Extend: false, 229 AnimatableExtend: false 230 }; 231 for (let i = 0; i < decorators.length; i++) { 232 const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').replace(/\s*/g, '').trim(); 233 if (log && EXTEND_DECORATORS.includes(decortorName)) { 234 if (originalDecortor === COMPONENT_EXTEND_DECORATOR) { 235 extendResult.Extend = true; 236 } 237 if (originalDecortor === COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { 238 extendResult.AnimatableExtend = true; 239 } 240 } else { 241 if (originalDecortor === decortorName) { 242 if (customBuilder) { 243 customBuilder.push(...decorators.slice(i + 1), ...decorators.slice(0, i)); 244 } 245 return true; 246 } 247 } 248 } 249 if (log && extendResult.Extend && extendResult.AnimatableExtend) { 250 log.push({ 251 type: LogType.ERROR, 252 message: `The function can not be decorated by '@Extend' and '@AnimatableExtend' at the same time.`, 253 pos: node.getStart(), 254 code: '10905111' 255 }); 256 } 257 return (decortorName === COMPONENT_EXTEND_DECORATOR && extendResult.Extend) || 258 (decortorName === COMPONENT_ANIMATABLE_EXTEND_DECORATOR && extendResult.AnimatableExtend); 259 } 260 return false; 261} 262 263const STATEMENT_EXPECT: number = 1128; 264const SEMICOLON_EXPECT: number = 1005; 265const STATESTYLES_EXPECT: number = 1003; 266export const IGNORE_ERROR_CODE: number[] = [STATEMENT_EXPECT, SEMICOLON_EXPECT, STATESTYLES_EXPECT]; 267 268export function readFile(dir: string, utFiles: string[]): void { 269 try { 270 const files: string[] = fs.readdirSync(dir); 271 files.forEach((element) => { 272 const filePath: string = path.join(dir, element); 273 const status: fs.Stats = fs.statSync(filePath); 274 if (status.isDirectory()) { 275 readFile(filePath, utFiles); 276 } else { 277 utFiles.push(filePath); 278 } 279 }); 280 } catch (e) { 281 console.error(red, 'ArkTS ERROR: ' + e, reset); 282 } 283} 284 285export function circularFile(inputPath: string, outputPath: string): void { 286 if (!inputPath || !outputPath) { 287 return; 288 } 289 fs.readdir(inputPath, function (err, files) { 290 if (!files) { 291 return; 292 } 293 files.forEach(file => { 294 const inputFile: string = path.resolve(inputPath, file); 295 const outputFile: string = path.resolve(outputPath, file); 296 const fileStat: fs.Stats = fs.statSync(inputFile); 297 if (fileStat.isFile()) { 298 copyFile(inputFile, outputFile); 299 } else { 300 circularFile(inputFile, outputFile); 301 } 302 }); 303 }); 304} 305 306function copyFile(inputFile: string, outputFile: string): void { 307 try { 308 const parent: string = path.join(outputFile, '..'); 309 if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { 310 mkDir(parent); 311 } 312 if (fs.existsSync(outputFile)) { 313 return; 314 } 315 const readStream: fs.ReadStream = fs.createReadStream(inputFile); 316 const writeStream: fs.WriteStream = fs.createWriteStream(outputFile); 317 readStream.pipe(writeStream); 318 readStream.on('close', function () { 319 writeStream.end(); 320 }); 321 } catch (err) { 322 throw err.message; 323 } 324} 325 326export function mkDir(path_: string): void { 327 const parent: string = path.join(path_, '..'); 328 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 329 mkDir(parent); 330 } 331 fs.mkdirSync(path_); 332} 333 334export function toUnixPath(data: string): string { 335 if (/^win/.test(require('os').platform())) { 336 const fileTmps: string[] = data.split(path.sep); 337 const newData: string = path.posix.join(...fileTmps); 338 return newData; 339 } 340 return data; 341} 342 343export function tryToLowerCasePath(filePath: string): string { 344 return toUnixPath(filePath).toLowerCase(); 345} 346 347export function toHashData(path: string): string { 348 const content: string = fs.readFileSync(path).toString(); 349 const hash: Hash = createHash('sha256'); 350 hash.update(content); 351 return hash.digest('hex'); 352} 353 354export function writeFileSync(filePath: string, content: string): void { 355 if (!fs.existsSync(filePath)) { 356 const parent: string = path.join(filePath, '..'); 357 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 358 mkDir(parent); 359 } 360 } 361 fs.writeFileSync(filePath, content); 362} 363export function genLoaderOutPathOfHar(filePath: string, cachePath: string, buildPath: string, moduleRootPath: string, projectRootPath): string { 364 filePath = toUnixPath(filePath); 365 buildPath = toUnixPath(buildPath); 366 const cacheRootPath: string = toUnixPath(cachePath); 367 const moduleName = toUnixPath(moduleRootPath).replace(toUnixPath(projectRootPath), ''); 368 const relativeFilePath: string = filePath.replace(cacheRootPath, '').replace(moduleName, ''); 369 const output: string = path.join(buildPath, relativeFilePath); 370 return output; 371} 372 373export function genTemporaryPath(filePath: string, projectPath: string, buildPath: string, projectConfig: Object, 374 metaInfo: Object, buildInHar: boolean = false): string { 375 filePath = toUnixPath(filePath).replace(/\.[cm]js$/, EXTNAME_JS); 376 projectPath = toUnixPath(projectPath); 377 378 if (process.env.compileTool === 'rollup') { 379 let relativeFilePath: string = ''; 380 if (metaInfo) { 381 if (metaInfo.isLocalDependency) { 382 // When buildInHar and compileHar are both True, 383 // this is the path under the PackageHar directory being spliced together. 384 // Here, only the relative path based on moduleRootPath needs to be retained. 385 // eg. moduleA/index.js --> index.js --> PackageHar/index.js 386 // eg. moduleA/src/main/ets/test.js --> src/main/ets/test.js --> PackageHar/src/main/ets/test.js 387 const moduleName: string = buildInHar && projectConfig.compileHar ? '' : metaInfo.moduleName; 388 relativeFilePath = filePath.replace(toUnixPath(metaInfo.belongModulePath), moduleName); 389 } else { 390 relativeFilePath = filePath.replace(toUnixPath(metaInfo.belongProjectPath), ''); 391 } 392 } else { 393 relativeFilePath = filePath.replace(toUnixPath(projectConfig.projectRootPath), ''); 394 } 395 const output: string = path.join(buildPath, relativeFilePath); 396 return output; 397 } 398 399 if (isPackageModulesFile(filePath, projectConfig)) { 400 const packageDir: string = projectConfig.packageDir; 401 const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); 402 let output: string = ''; 403 if (filePath.indexOf(fakePkgModulesPath) === -1) { 404 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 405 const tempFilePath: string = filePath.replace(hapPath, ''); 406 const relativeFilePath: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); 407 output = path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, MAIN, relativeFilePath); 408 } else { 409 output = filePath.replace(fakePkgModulesPath, 410 path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, AUXILIARY)); 411 } 412 return output; 413 } 414 415 if (filePath.indexOf(projectPath) !== -1) { 416 const relativeFilePath: string = filePath.replace(projectPath, ''); 417 const output: string = path.join(buildPath, buildInHar ? '' : TEMPORARY, relativeFilePath); 418 return output; 419 } 420 421 return ''; 422} 423 424export function isPackageModulesFile(filePath: string, projectConfig: Object): boolean { 425 filePath = toUnixPath(filePath); 426 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 427 const tempFilePath: string = filePath.replace(hapPath, ''); 428 const packageDir: string = projectConfig.packageDir; 429 if (tempFilePath.indexOf(packageDir) !== -1) { 430 const fakePkgModulesPath: string = toUnixPath(path.resolve(projectConfig.projectRootPath, packageDir)); 431 if (filePath.indexOf(fakePkgModulesPath) !== -1) { 432 return true; 433 } 434 if (projectConfig.modulePathMap) { 435 for (const key in projectConfig.modulePathMap) { 436 const value: string = projectConfig.modulePathMap[key]; 437 const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir)); 438 if (filePath.indexOf(fakeModulePkgModulesPath) !== -1) { 439 return true; 440 } 441 } 442 } 443 } 444 445 return false; 446} 447 448export interface GeneratedFileInHar { 449 sourcePath: string; 450 sourceCachePath?: string; 451 obfuscatedSourceCachePath?: string; 452 originalDeclarationCachePath?: string; 453 originalDeclarationContent?: string; 454 obfuscatedDeclarationCachePath?: string; 455} 456 457export const harFilesRecord: Map<string, GeneratedFileInHar> = new Map(); 458 459export function generateSourceFilesInHar(sourcePath: string, sourceContent: string, suffix: string, 460 projectConfig: Object, modulePathMap?: Object): void { 461 const belongModuleInfo: Object = getBelongModuleInfo(sourcePath, modulePathMap, projectConfig.projectRootPath); 462 // compileShared: compile shared har of project 463 let jsFilePath: string = genTemporaryPath(sourcePath, 464 projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, 465 projectConfig.compileShared || projectConfig.byteCodeHar ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, 466 projectConfig, belongModuleInfo, projectConfig.compileShared); 467 if (!jsFilePath.match(new RegExp(projectConfig.packageDir))) { 468 jsFilePath = jsFilePath.replace(/\.ets$/, suffix).replace(/\.ts$/, suffix); 469 if (projectConfig.obfuscateHarType === 'uglify' && suffix === '.js') { 470 sourceContent = uglifyJS.minify(sourceContent).code; 471 } 472 // collect the declaration files for obfuscation 473 if (projectConfig.compileMode === ESMODULE && (/\.d\.e?ts$/).test(jsFilePath)) { 474 sourcePath = toUnixPath(sourcePath); 475 const genFilesInHar: GeneratedFileInHar = { 476 sourcePath: sourcePath, 477 originalDeclarationCachePath: jsFilePath, 478 originalDeclarationContent: sourceContent 479 }; 480 harFilesRecord.set(sourcePath, genFilesInHar); 481 return; 482 } else { 483 mkdirsSync(path.dirname(jsFilePath)); 484 } 485 fs.writeFileSync(jsFilePath, sourceContent); 486 } 487} 488 489export function mkdirsSync(dirname: string): boolean { 490 if (fs.existsSync(dirname)) { 491 return true; 492 } else if (mkdirsSync(path.dirname(dirname))) { 493 fs.mkdirSync(dirname); 494 return true; 495 } 496 497 return false; 498} 499 500export function nodeLargeOrEqualTargetVersion(targetVersion: number): boolean { 501 const currentNodeVersion: number = parseInt(process.versions.node.split('.')[0]); 502 if (currentNodeVersion >= targetVersion) { 503 return true; 504 } 505 506 return false; 507} 508 509export function removeDir(dirName: string): void { 510 if (fs.existsSync(dirName)) { 511 if (nodeLargeOrEqualTargetVersion(16)) { 512 fs.rmSync(dirName, { recursive: true }); 513 } else { 514 fs.rmdirSync(dirName, { recursive: true }); 515 } 516 } 517} 518 519export function parseErrorMessage(message: string): string { 520 const messageArrary: string[] = message.split('\n'); 521 let logContent: string = ''; 522 messageArrary.forEach(element => { 523 if (!(/^at/.test(element.trim()))) { 524 logContent = logContent + element + '\n'; 525 } 526 }); 527 return logContent; 528} 529 530export function isWindows(): boolean { 531 return os.type() === WINDOWS; 532} 533 534export function isLinux(): boolean { 535 return os.type() === LINUX; 536} 537 538export function isMac(): boolean { 539 return os.type() === MAC; 540} 541 542export function isHarmonyOs(): boolean { 543 return os.type() === HARMONYOS; 544} 545 546export function maxFilePathLength(): number { 547 if (isWindows()) { 548 return 32766; 549 } else if (isLinux() || isHarmonyOs()) { 550 return 4095; 551 } else if (isMac()) { 552 return 1016; 553 } else { 554 return -1; 555 } 556} 557 558export function validateFilePathLength(filePath: string, logger: Object): boolean { 559 if (maxFilePathLength() < 0) { 560 logger.error(red, 'Unknown OS platform', reset); 561 process.exitCode = FAIL; 562 return false; 563 } else if (filePath.length > 0 && filePath.length <= maxFilePathLength()) { 564 return true; 565 } else if (filePath.length > maxFilePathLength()) { 566 logger.error(red, `The length of ${filePath} exceeds the limitation of current platform, which is ` + 567 `${maxFilePathLength()}. Please try moving the project folder to avoid deeply nested file path and try again`, 568 reset); 569 process.exitCode = FAIL; 570 return false; 571 } else { 572 logger.error(red, 'Validate file path failed', reset); 573 process.exitCode = FAIL; 574 return false; 575 } 576} 577 578export function validateFilePathLengths(filePaths: Array<string>, logger: any): boolean { 579 filePaths.forEach((filePath) => { 580 if (!validateFilePathLength(filePath, logger)) { 581 return false; 582 } 583 return true; 584 }); 585 return true; 586} 587 588export function unlinkSync(filePath: string): void { 589 if (fs.existsSync(filePath)) { 590 fs.unlinkSync(filePath); 591 } 592} 593 594export function getExtensionIfUnfullySpecifiedFilepath(filePath: string): string { 595 if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { 596 return ''; 597 } 598 599 let extension: string = EXTNAME_ETS; 600 if (fs.existsSync(filePath + '.ts') && fs.statSync(filePath + '.ts').isFile()) { 601 extension = '.ts'; 602 } else if (fs.existsSync(filePath + '.d.ts') && fs.statSync(filePath + '.d.ts').isFile()) { 603 extension = '.d.ts'; 604 } else if (fs.existsSync(filePath + '.d.ets') && fs.statSync(filePath + '.d.ets').isFile()) { 605 extension = '.d.ets'; 606 } else if (fs.existsSync(filePath + '.js') && fs.statSync(filePath + '.js').isFile()) { 607 extension = '.js'; 608 } else if (fs.existsSync(filePath + '.json') && fs.statSync(filePath + '.json').isFile()) { 609 extension = '.json'; 610 } 611 612 return extension; 613} 614 615export function shouldWriteChangedList(watchModifiedFiles: string[], 616 watchRemovedFiles: string[]): boolean { 617 if (projectConfig.compileMode === ESMODULE && process.env.watchMode === 'true' && !projectConfig.isPreview && 618 projectConfig.changedFileList && (watchRemovedFiles.length + watchModifiedFiles.length)) { 619 if (process.env.compileTool !== 'rollup') { 620 if (!(watchModifiedFiles.length === 1 && 621 watchModifiedFiles[0] === projectConfig.projectPath && !watchRemovedFiles.length)) { 622 return true; 623 } else { 624 return false; 625 } 626 } 627 return true; 628 } 629 return false; 630} 631 632interface HotReloadIncrementalTime { 633 hotReloadIncrementalStartTime: string; 634 hotReloadIncrementalEndTime: string; 635} 636 637export const hotReloadIncrementalTime: HotReloadIncrementalTime = { 638 hotReloadIncrementalStartTime: '', 639 hotReloadIncrementalEndTime: '' 640}; 641 642interface FilesObj { 643 modifiedFiles: string[], 644 removedFiles: string[] 645} 646 647let allModifiedFiles: Set<string> = new Set(); 648 649export function getHotReloadFiles(watchModifiedFiles: string[], 650 watchRemovedFiles: string[], hotReloadSupportFiles: Set<string>): FilesObj { 651 hotReloadIncrementalTime.hotReloadIncrementalStartTime = new Date().getTime().toString(); 652 watchRemovedFiles = watchRemovedFiles.map(file => path.relative(projectConfig.projectPath, file)); 653 allModifiedFiles = new Set([...allModifiedFiles, ...watchModifiedFiles 654 .filter(file => fs.statSync(file).isFile() && 655 (hotReloadSupportFiles.has(file) || !['.ets', '.ts', '.js'].includes(path.extname(file)))) 656 .map(file => path.relative(projectConfig.projectPath, file))] 657 .filter(file => !watchRemovedFiles.includes(file))); 658 return { 659 modifiedFiles: [...allModifiedFiles], 660 removedFiles: [...watchRemovedFiles] 661 }; 662} 663 664export function getResolveModules(projectPath: string, faMode: boolean): string[] { 665 if (faMode) { 666 return [ 667 path.resolve(projectPath, '../../../../../'), 668 path.resolve(projectPath, '../../../../' + projectConfig.packageDir), 669 path.resolve(projectPath, '../../../../../' + projectConfig.packageDir), 670 path.resolve(projectPath, '../../') 671 ]; 672 } else { 673 return [ 674 path.resolve(projectPath, '../../../../'), 675 path.resolve(projectPath, '../../../' + projectConfig.packageDir), 676 path.resolve(projectPath, '../../../../' + projectConfig.packageDir), 677 path.resolve(projectPath, '../') 678 ]; 679 } 680} 681 682export function writeUseOSFiles(useOSFiles: Set<string>): void { 683 let info: string = ''; 684 if (!fs.existsSync(projectConfig.aceSoPath)) { 685 const parent: string = path.resolve(projectConfig.aceSoPath, '..'); 686 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 687 mkDir(parent); 688 } 689 } else { 690 info = fs.readFileSync(projectConfig.aceSoPath, 'utf-8') + '\n'; 691 } 692 fs.writeFileSync(projectConfig.aceSoPath, info + Array.from(useOSFiles).join('\n')); 693} 694 695 696export function writeCollectionFile(cachePath: string, appCollection: Map<string, Set<string>>, 697 allComponentsOrModules: Map<string, Array<string>>, fileName: string, allFiles: Set<string> = null, 698 widgetPath: string = undefined): void { 699 for (let key of appCollection.keys()) { 700 if (appCollection.get(key).size === 0) { 701 allComponentsOrModules.delete(key); 702 continue; 703 } 704 if (allFiles && !allFiles.has(key)) { 705 continue; 706 } 707 const newKey: string = projectConfig.projectRootPath ? path.relative(projectConfig.projectRootPath, key) : key; 708 allComponentsOrModules.set(newKey, Array.from(appCollection.get(key))); 709 } 710 const content: string = JSON.stringify(Object.fromEntries(allComponentsOrModules), null, 2); 711 writeFileSync(path.resolve(cachePath, fileName), content); 712 if (widgetPath) { 713 writeFileSync(path.resolve(widgetPath, fileName), content); 714 } 715} 716 717export function getAllComponentsOrModules(allFiles: Set<string>, 718 cacheCollectionFileName: string): Map<string, Array<string>> { 719 const cacheCollectionFilePath: string = path.resolve(projectConfig.cachePath, cacheCollectionFileName); 720 const allComponentsOrModules: Map<string, Array<string>> = new Map(); 721 if (!fs.existsSync(cacheCollectionFilePath)) { 722 return allComponentsOrModules; 723 } 724 const lastComponentsOrModules = require(cacheCollectionFilePath); 725 for (let key in lastComponentsOrModules) { 726 if (allFiles.has(key)) { 727 allComponentsOrModules.set(key, lastComponentsOrModules[key]); 728 } 729 } 730 return allComponentsOrModules; 731} 732 733export function getPossibleBuilderTypeParameter(parameters: ts.ParameterDeclaration[]): string[] { 734 const parameterNames: string[] = []; 735 if (!partialUpdateConfig.builderCheck) { 736 if (isDollarParameter(parameters)) { 737 parameters[0].type.members.forEach((member) => { 738 if (member.name && ts.isIdentifier(member.name)) { 739 parameterNames.push(member.name.escapedText.toString()); 740 } 741 }); 742 } else { 743 parameters.forEach((parameter) => { 744 if (parameter.name && ts.isIdentifier(parameter.name)) { 745 parameterNames.push(parameter.name.escapedText.toString()); 746 } 747 }); 748 } 749 } 750 return parameterNames; 751} 752 753function isDollarParameter(parameters: ts.ParameterDeclaration[]): boolean { 754 return parameters.length === 1 && parameters[0].name && ts.isIdentifier(parameters[0].name) && 755 parameters[0].name.escapedText.toString() === $$ && parameters[0].type && ts.isTypeLiteralNode(parameters[0].type) && 756 parameters[0].type.members && parameters[0].type.members.length > 0; 757} 758 759interface ChildrenCacheFile { 760 fileName: string, 761 mtimeMs: number, 762 hash: string 763} 764 765export interface CacheFile { 766 mtimeMs: number, 767 hash: string, 768 children: Array<ChildrenCacheFile>, 769} 770 771export interface RouterInfo { 772 name: string, 773 buildFunction: string, 774} 775 776// Global Information & Method 777export class ProcessFileInfo { 778 buildStart: boolean = true; 779 wholeFileInfo: { [id: string]: SpecialArkTSFileInfo | TSFileInfo } = {}; // Save ArkTS & TS file's infomation 780 transformedFiles: Set<string> = new Set(); // ArkTS & TS Files which should be transformed in this compilation 781 cachedFiles: string[] = []; // ArkTS & TS Files which should not be transformed in this compilation 782 shouldHaveEntry: string[] = []; // Which file should have @Entry decorator 783 resourceToFile: { [resource: string]: Set<string> } = {}; // Resource is used by which file 784 lastResourceList: Set<string> = new Set(); 785 resourceList: Set<string> = new Set(); // Whole project resource 786 shouldInvalidFiles: Set<string> = new Set(); 787 resourceTableChanged: boolean = false; 788 currentArkTsFile: SpecialArkTSFileInfo; 789 reUseProgram: boolean = false; 790 resourcesArr: Set<string> = new Set(); 791 lastResourcesSet: Set<string> = new Set(); 792 transformCacheFiles: { [fileName: string]: CacheFile } = {}; 793 processBuilder: boolean = false; 794 processGlobalBuilder: boolean = false; 795 processLocalBuilder: boolean = false; 796 builderLikeCollection: Set<string> = new Set(); 797 newTsProgram: ts.Program; 798 changeFiles: string[] = []; 799 isFirstBuild: boolean = true; 800 processForEach: number = 0; 801 processLazyForEach: number = 0; 802 processRepeat: boolean = false; 803 isAsPageImport: boolean = false; 804 overallObjectLinkCollection: Map<string, Set<string>> = new Map(); 805 overallLinkCollection: Map<string, Set<string>> = new Map(); 806 overallBuilderParamCollection: Map<string, Set<string>> = new Map(); 807 lazyForEachInfo: { 808 forEachParameters: ts.ParameterDeclaration, 809 isDependItem: boolean 810 } = { 811 forEachParameters: null, 812 isDependItem: false 813 }; 814 routerInfo: Map<string, Array<RouterInfo>> = new Map(); 815 hasLocalBuilderInFile: boolean = false; 816 817 addGlobalCacheInfo(resourceListCacheInfo: string[], 818 resourceToFileCacheInfo: { [resource: string]: Set<string> }, 819 cacheFile: { [fileName: string]: CacheFile }): void { 820 if (this.buildStart) { 821 for (const element in resourceToFileCacheInfo) { 822 this.resourceToFile[element] = new Set(resourceToFileCacheInfo[element]); 823 } 824 this.lastResourceList = new Set(resourceListCacheInfo); 825 } 826 if (this.resourceTableChanged) { 827 this.compareResourceDiff(); 828 } 829 if (cacheFile) { 830 this.transformCacheFiles = cacheFile; 831 } 832 } 833 834 addFileCacheInfo(id: string, fileCacheInfo: fileInfo): void { 835 if (fileCacheInfo && process.env.compileMode === 'moduleJson') { 836 if (Array.isArray(fileCacheInfo.fileToResourceList)) { 837 fileCacheInfo.fileToResourceList = new Set(fileCacheInfo.fileToResourceList); 838 } else { 839 fileCacheInfo.fileToResourceList = new Set(); 840 } 841 } 842 if (id.match(/(?<!\.d)\.(ets)$/)) { 843 this.wholeFileInfo[id] = new SpecialArkTSFileInfo(fileCacheInfo); 844 } else if (id.match(/(?<!\.d)\.(ts)$/) && process.env.compileMode === 'moduleJson') { 845 this.wholeFileInfo[id] = new TSFileInfo(fileCacheInfo); 846 } 847 } 848 849 collectTransformedFiles(id: string): void { 850 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 851 this.transformedFiles.add(id); 852 } 853 } 854 855 collectCachedFiles(id: string): void { 856 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 857 this.cachedFiles.push(id); 858 } 859 } 860 861 judgeShouldHaveEntryFiles(entryFileWithoutEntryDecorator: Set<string>): void { 862 this.shouldHaveEntry = Object.values(projectConfig.entryObj as string[]).filter((item) => { 863 return !entryFileWithoutEntryDecorator.has(item.toLowerCase()) && item.match(/(?<!\.d)\.(ets)$/); 864 }); 865 } 866 867 saveCacheFileInfo(cache): void { 868 if (process.env.compileMode === 'moduleJson') { 869 const fileCacheInfo: { [id: string]: fileInfo | tsFileInfo } = cache.get('fileCacheInfo') || {}; 870 const resourceToFileCacheInfo = cache.get('resourceToFileCacheInfo') || {}; 871 for (const i in resourceToFileCacheInfo) { 872 resourceToFileCacheInfo[i] = new Set(resourceToFileCacheInfo[i]); 873 } 874 const resourceToFile: { [resource: string]: Set<string> | string[] } = Object.assign(resourceToFileCacheInfo, this.resourceToFile); 875 for (const id of this.transformedFiles) { 876 fileCacheInfo[id] = this.wholeFileInfo[id].fileInfo; 877 for (const resource of this.wholeFileInfo[id].newFileToResourceList) { 878 if (!(fileCacheInfo[id].fileToResourceList as Set<string>).has(resource)) { 879 this.resourceToFileBecomeSet(resourceToFile, resource, id); 880 } 881 } 882 for (const resource of fileCacheInfo[id].fileToResourceList) { 883 if (!this.wholeFileInfo[id].newFileToResourceList.has(resource)) { 884 (resourceToFile[resource] as Set<string>).delete(id); 885 } 886 } 887 fileCacheInfo[id].fileToResourceList = [...this.wholeFileInfo[id].newFileToResourceList]; 888 } 889 for (const id of this.cachedFiles) { 890 fileCacheInfo[id].fileToResourceList = [...fileCacheInfo[id].fileToResourceList]; 891 } 892 this.resourceToFile = resourceToFile as { [resource: string]: Set<string> }; 893 for (const resource in resourceToFile) { 894 resourceToFile[resource] = [...resourceToFile[resource]]; 895 } 896 cache.set('fileCacheInfo', fileCacheInfo); 897 cache.set('resourceListCacheInfo', [...this.resourceList]); 898 cache.set('resourceToFileCacheInfo', resourceToFile); 899 } else { 900 const cacheInfo: { [id: string]: fileInfo } = cache.get('fileCacheInfo') || {}; 901 for (const id of this.transformedFiles) { 902 cacheInfo[id] = this.wholeFileInfo[id].fileInfo; 903 } 904 cache.set('fileCacheInfo', cacheInfo); 905 } 906 } 907 908 resourceToFileBecomeSet(resourceToFile: { [resource: string]: Set<string> | string[] }, resource: string, id: string): void { 909 if (!resourceToFile[resource]) { 910 resourceToFile[resource] = new Set(); 911 } 912 if (resourceToFile[resource] instanceof Set) { 913 resourceToFile[resource].add(id); 914 } else if (Array.isArray(resourceToFile[resource])) { 915 resourceToFile[resource] = new Set(resourceToFile[resource]); 916 resourceToFile[resource].add(id); 917 } else { 918 return; 919 } 920 } 921 922 updateResourceList(resource: string): void { 923 this.resourceList.add(resource); 924 } 925 926 compareResourceDiff(): void { 927 // delete resource 928 for (const resource of this.lastResourceList) { 929 if (!this.resourceList.has(resource) && this.resourceToFile[resource]) { 930 this.resourceToFile[resource].forEach(file => { 931 this.shouldInvalidFiles.add(file); 932 }); 933 } 934 } 935 // create resource 936 for (const resource of this.resourceList) { 937 if (!this.resourceToFile[resource]) { 938 this.resourceToFile[resource] = new Set(); 939 } 940 if (!this.lastResourceList.has(resource)) { 941 this.resourceToFile[resource].forEach(file => { 942 this.shouldInvalidFiles.add(file); 943 }); 944 } 945 } 946 } 947 948 collectResourceInFile(resource: string, file: string): void { 949 if (this.wholeFileInfo[file]) { 950 this.wholeFileInfo[file].newFileToResourceList.add(resource); 951 } 952 } 953 954 clearCollectedInfo(cache): void { 955 this.buildStart = false; 956 this.resourceTableChanged = false; 957 this.isAsPageImport = false; 958 this.saveCacheFileInfo(cache); 959 this.transformedFiles = new Set(); 960 this.cachedFiles = []; 961 this.lastResourceList = new Set([...this.resourceList]); 962 this.shouldInvalidFiles.clear(); 963 this.resourcesArr.clear(); 964 } 965 setCurrentArkTsFile(): void { 966 this.currentArkTsFile = new SpecialArkTSFileInfo(); 967 } 968 getCurrentArkTsFile(): SpecialArkTSFileInfo { 969 return this.currentArkTsFile; 970 } 971} 972 973export let storedFileInfo: ProcessFileInfo = new ProcessFileInfo(); 974 975export interface fileInfo extends tsFileInfo { 976 hasEntry: boolean; // Has @Entry decorator or not 977} 978 979export interface tsFileInfo { 980 fileToResourceList: Set<string> | string[]; // How much Resource is used 981} 982 983// Save single TS file information 984class TSFileInfo { 985 fileInfo: tsFileInfo = { 986 fileToResourceList: new Set() 987 }; 988 newFileToResourceList: Set<string> = new Set(); 989 constructor(cacheInfo: fileInfo, etsFile?: boolean) { 990 if (!etsFile) { 991 this.fileInfo = cacheInfo || this.fileInfo; 992 } 993 } 994} 995 996// Save single ArkTS file information 997class SpecialArkTSFileInfo extends TSFileInfo { 998 fileInfo: fileInfo = { 999 hasEntry: false, 1000 fileToResourceList: new Set() 1001 }; 1002 recycleComponents: Set<string> = new Set([]); 1003 reuseComponentsV2: Set<string> = new Set([]); 1004 compFromDETS: Set<string> = new Set(); 1005 animatableExtendAttribute: Map<string, Set<string>> = new Map(); 1006 pkgName: string; 1007 1008 constructor(cacheInfo?: fileInfo) { 1009 super(cacheInfo, true); 1010 this.fileInfo = cacheInfo || this.fileInfo; 1011 } 1012 1013 get hasEntry(): boolean { 1014 return this.fileInfo.hasEntry; 1015 } 1016 set hasEntry(value: boolean) { 1017 this.fileInfo.hasEntry = value; 1018 } 1019} 1020 1021export function setChecker(): void { 1022 if (globalProgram.program) { 1023 globalProgram.checker = globalProgram.program.getTypeChecker(); 1024 globalProgram.strictChecker = globalProgram.program.getLinterTypeChecker(); 1025 } else if (globalProgram.watchProgram) { 1026 globalProgram.checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker(); 1027 } 1028} 1029export interface ExtendResult { 1030 decoratorName: string; 1031 componentName: string; 1032} 1033 1034export function resourcesRawfile(rawfilePath: string, resourcesArr: Set<string>, resourceName: string = ''): void { 1035 if (fs.existsSync(process.env.rawFileResource) && fs.statSync(rawfilePath).isDirectory()) { 1036 const files: string[] = fs.readdirSync(rawfilePath); 1037 files.forEach((file: string) => { 1038 if (fs.statSync(path.join(rawfilePath, file)).isDirectory()) { 1039 resourcesRawfile(path.join(rawfilePath, file), resourcesArr, resourceName ? resourceName + '/' + file : file); 1040 } else { 1041 if (resourceName) { 1042 resourcesArr.add(resourceName + '/' + file); 1043 } else { 1044 resourcesArr.add(file); 1045 } 1046 } 1047 }); 1048 } 1049} 1050 1051export function differenceResourcesRawfile(oldRawfile: Set<string>, newRawfile: Set<string>): boolean { 1052 if (oldRawfile.size !== 0 && oldRawfile.size === newRawfile.size) { 1053 for (const singleRawfiles of oldRawfile.values()) { 1054 if (!newRawfile.has(singleRawfiles)) { 1055 return true; 1056 } 1057 } 1058 return false; 1059 } else if (oldRawfile.size === 0 && oldRawfile.size === newRawfile.size) { 1060 return false; 1061 } else { 1062 return true; 1063 } 1064} 1065 1066export function isString(text: unknown): text is string { 1067 return typeof text === 'string'; 1068} 1069 1070function getRollupCacheStoreKey(projectConfig: object): string { 1071 let keyInfo: string[] = [projectConfig.compileSdkVersion, projectConfig.compatibleSdkVersion, projectConfig.runtimeOS, 1072 projectConfig.etsLoaderPath]; 1073 return keyInfo.join('#'); 1074} 1075 1076function getRollupCacheKey(projectConfig: object): string { 1077 let isWidget: string = projectConfig.widgetCompile ? 'widget' : 'non-widget'; 1078 let ohosTestInfo: string = 'non-ohosTest'; 1079 if (projectConfig.testFrameworkPar) { 1080 ohosTestInfo = JSON.stringify(projectConfig.testFrameworkPar); 1081 } 1082 1083 let keyInfo: string[] = [projectConfig.entryModuleName, projectConfig.targetName, isWidget, ohosTestInfo, 1084 projectConfig.needCoverageInsert, projectConfig.isOhosTest]; 1085 return keyInfo.join('#'); 1086} 1087 1088function clearRollupCacheStore(cacheStoreManager: object, currentKey: string): void { 1089 if (!cacheStoreManager) { 1090 return; 1091 } 1092 1093 for (let key of cacheStoreManager.keys()) { 1094 if (key !== currentKey) { 1095 cacheStoreManager.unmount(key); 1096 } 1097 } 1098} 1099 1100export function startTimeStatisticsLocation(startTimeEvent: CompileEvent): void { 1101 if (startTimeEvent) { 1102 startTimeEvent.start(); 1103 } 1104} 1105 1106export function stopTimeStatisticsLocation(stopTimeEvent: CompileEvent): void { 1107 if (stopTimeEvent) { 1108 stopTimeEvent.stop(); 1109 } 1110} 1111export let resolveModuleNamesTime: CompileEvent; 1112export class CompilationTimeStatistics { 1113 hookEventFactory: HookEventFactoryType; 1114 createProgramTime: CompileEvent; 1115 runArkTSLinterTime: CompileEvent; 1116 diagnosticTime: CompileEvent; 1117 scriptSnapshotTime: CompileEvent; 1118 processImportTime: CompileEvent; 1119 processComponentClassTime: CompileEvent; 1120 validateEtsTime: CompileEvent; 1121 tsProgramEmitTime: CompileEvent; 1122 shouldEmitJsTime: CompileEvent; 1123 transformNodesTime: CompileEvent; 1124 emitTime: CompileEvent; 1125 printNodeTime: CompileEvent; 1126 noSourceFileRebuildProgramTime: CompileEvent; 1127 etsTransformBuildStartTime: CompileEvent; 1128 etsTransformLoadTime: CompileEvent; 1129 processKitImportTime: CompileEvent; 1130 processUISyntaxTime: CompileEvent; 1131 constructor(share, pluginName: string, hookName: string) { 1132 if (share && share.getHookEventFactory) { 1133 if (pluginName === 'etsChecker' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) { 1134 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1135 this.createProgramTime = this.hookEventFactory.createEvent('createProgram'); 1136 this.runArkTSLinterTime = this.hookEventFactory.createEvent('arkTSLinter'); 1137 this.diagnosticTime = this.hookEventFactory.createEvent('diagnostic'); 1138 this.scriptSnapshotTime = this.createProgramTime.createSubEvent('scriptSnapshot'); 1139 resolveModuleNamesTime = this.hookEventFactory.createEvent('resolveModuleNames'); 1140 } else if (pluginName === 'etsTransform' && hookName === 'transform' && share.getHookEventFactory(pluginName, hookName)) { 1141 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1142 this.validateEtsTime = this.hookEventFactory.createEvent('validateEts'); 1143 this.tsProgramEmitTime = this.hookEventFactory.createEvent('tsProgramEmit'); 1144 this.shouldEmitJsTime = this.hookEventFactory.createEvent('shouldEmitJs'); 1145 this.transformNodesTime = this.tsProgramEmitTime.createSubEvent('transformNodes'); 1146 this.emitTime = this.tsProgramEmitTime.createSubEvent('emit'); 1147 this.printNodeTime = this.hookEventFactory.createEvent('printNode'); 1148 this.noSourceFileRebuildProgramTime = this.hookEventFactory.createEvent('noSourceFileRebuildProgram'); 1149 this.processKitImportTime = this.tsProgramEmitTime.createSubEvent('processKitImport'); 1150 this.processUISyntaxTime = this.tsProgramEmitTime.createSubEvent('processUISyntax'); 1151 this.processImportTime = this.processUISyntaxTime.createSubEvent('processImport'); 1152 this.processComponentClassTime = this.processUISyntaxTime.createSubEvent('processComponentClass'); 1153 } else if (pluginName === 'etsTransform' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) { 1154 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1155 this.etsTransformBuildStartTime = this.hookEventFactory.createEvent('etsTransformBuildStart'); 1156 } else if (pluginName === 'etsTransform' && hookName === 'load' && share.getHookEventFactory(pluginName, hookName)) { 1157 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1158 this.etsTransformLoadTime = this.hookEventFactory.createEvent('etsTransformLoad'); 1159 } 1160 } 1161 } 1162} 1163 1164interface HookEventFactoryType { 1165 createEvent(name: string): CompileEvent | undefined; 1166} 1167 1168type CompileEventState = 'created' | 'beginning' | 'running' | 'failed' | 'success' | 'warn'; 1169interface CompileEvent { 1170 start(time?: number): CompileEvent; 1171 stop(state?: CompileEventState, time?: number): void; 1172 startAsyncEvent(time: number): CompileEvent; 1173 stopAsyncEvent(state?: CompileEventState, TIME?: number): void; 1174 createSubEvent(name: string): CompileEvent; 1175} 1176 1177export function resetUtils(): void { 1178 componentInfo = new ComponentInfo(); 1179 harFilesRecord.clear(); 1180 storedFileInfo = new ProcessFileInfo(); 1181} 1182 1183export function getStoredFileInfo(): ProcessFileInfo { 1184 return storedFileInfo; 1185} 1186 1187export class EntryOptionValue { 1188 routeName: ts.Expression; 1189 storage: ts.Expression; 1190 useSharedStorage: ts.Expression; 1191} 1192 1193function sharedNode(): ts.CallExpression { 1194 return ts.factory.createCallExpression( 1195 ts.factory.createPropertyAccessExpression( 1196 ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU), 1197 ts.factory.createIdentifier(GET_SHARED) 1198 ), 1199 undefined, 1200 [] 1201 ); 1202} 1203 1204export function createGetShared(entryOptionValue: EntryOptionValue): ts.ConditionalExpression { 1205 return ts.factory.createConditionalExpression( 1206 entryOptionValue.useSharedStorage, 1207 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 1208 sharedNode(), 1209 ts.factory.createToken(ts.SyntaxKind.ColonToken), 1210 entryOptionValue.storage || ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) 1211 ); 1212} 1213 1214export function createGetSharedForVariable(entryOptionNode: ts.Expression, isShare: boolean = true): ts.ConditionalExpression { 1215 return ts.factory.createConditionalExpression( 1216 ts.factory.createPropertyAccessExpression( 1217 entryOptionNode, 1218 ts.factory.createIdentifier(USE_SHARED_STORAGE) 1219 ), 1220 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 1221 sharedNode(), 1222 ts.factory.createToken(ts.SyntaxKind.ColonToken), 1223 isShare ? ts.factory.createPropertyAccessExpression( 1224 entryOptionNode, ts.factory.createIdentifier(STORAGE)) : 1225 ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) 1226 ); 1227} 1228 1229export function judgeUseSharedStorageForExpresion(entryOptionNode: ts.Expression): ts.BinaryExpression { 1230 return ts.factory.createBinaryExpression( 1231 entryOptionNode, 1232 ts.factory.createToken(ts.SyntaxKind.AmpersandAmpersandToken), 1233 ts.factory.createBinaryExpression( 1234 ts.factory.createPropertyAccessExpression( 1235 entryOptionNode, 1236 ts.factory.createIdentifier(USE_SHARED_STORAGE) 1237 ), 1238 ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsToken), 1239 ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED) 1240 ) 1241 ); 1242} 1243 1244export function getRollupCache(rollupShareObject: object, projectConfig: object, key: string): object | undefined { 1245 if (!rollupShareObject) { 1246 return undefined; 1247 } 1248 1249 // Preferentially get cache object from the rollup’s cache interface. 1250 if (rollupShareObject.cache) { 1251 // Only the cache object’s name as the cache key is required. 1252 return rollupShareObject.cache.get(key); 1253 } 1254 1255 // Try to get cache object from the rollup's cacheStoreManager interface. 1256 if (rollupShareObject.cacheStoreManager) { 1257 // The cache under cacheStoreManager is divided into two layers. The key for the first layer of cache is determined 1258 // by the SDK and runtimeOS, accessed through the `mount` interface. The key for the second layer of cache is 1259 // determined by the compilation task and the name of the cache object, 1260 // accessed through `getCache` or `setCache` interface. 1261 const cacheStoreKey: string = getRollupCacheStoreKey(projectConfig); 1262 const cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + key; 1263 1264 // Clear the cache if the SDK path or runtimeOS changed 1265 clearRollupCacheStore(rollupShareObject.cacheStoreManager, cacheStoreKey); 1266 return rollupShareObject.cacheStoreManager.mount(cacheStoreKey).getCache(cacheServiceKey); 1267 } 1268 1269 return undefined; 1270} 1271 1272export function setRollupCache(rollupShareObject: object, projectConfig: object, key: string, value: object): void { 1273 if (!rollupShareObject) { 1274 return; 1275 } 1276 1277 // Preferentially set cache object to the rollup’s cache interface. 1278 if (rollupShareObject.cache) { 1279 // Only the cache object’s name as the cache key is required. 1280 rollupShareObject.cache.set(key, value); 1281 return; 1282 } 1283 1284 // Try to set cache object to the rollup's cacheStoreManager interface. 1285 if (rollupShareObject.cacheStoreManager) { 1286 const cacheStoreKey: string = getRollupCacheStoreKey(projectConfig); 1287 const cacheServiceKey: string = getRollupCacheKey(projectConfig) + '#' + key; 1288 1289 rollupShareObject.cacheStoreManager.mount(cacheStoreKey).setCache(cacheServiceKey, value); 1290 } 1291} 1292 1293export function removeDecorator(decorators: readonly ts.Decorator[], decoratorName: string): readonly ts.Decorator[] { 1294 return decorators.filter((item: ts.Node) => { 1295 if (ts.isDecorator(item) && ts.isIdentifier(item.expression) && 1296 item.expression.escapedText.toString() === decoratorName) { 1297 return false; 1298 } 1299 return true; 1300 }); 1301} 1302 1303export function isFileInProject(filePath: string, projectRootPath: string): boolean { 1304 const relativeFilePath: string = toUnixPath(path.relative(toUnixPath(projectRootPath), toUnixPath(filePath))); 1305 // When processing ohmurl, hsp's filePath is consistent with moduleRequest 1306 return fs.existsSync(filePath) && fs.statSync(filePath).isFile() && !relativeFilePath.startsWith('../'); 1307} 1308 1309export function getProjectRootPath(filePath: string, projectConfig: Object, rootPathSet: Object): string { 1310 if (rootPathSet) { 1311 for (const rootPath of rootPathSet) { 1312 if (isFileInProject(filePath, rootPath)) { 1313 return rootPath; 1314 } 1315 } 1316 } 1317 return projectConfig.projectRootPath; 1318} 1319 1320export function getBelongModuleInfo(filePath: string, modulePathMap: Object, projectRootPath: string): Object { 1321 for (const moduleName of Object.keys(modulePathMap)) { 1322 if (toUnixPath(filePath).startsWith(toUnixPath(modulePathMap[moduleName]) + '/')) { 1323 return { 1324 isLocalDependency: true, 1325 moduleName: moduleName, 1326 belongModulePath: modulePathMap[moduleName] 1327 }; 1328 } 1329 } 1330 return { 1331 isLocalDependency: false, 1332 moduleName: '', 1333 belongModulePath: projectRootPath 1334 }; 1335} 1336 1337/** 1338 * Eliminate null in type 1339 * 1340 * @param { ts.Type } type 1341 * @returns { ts.Type | ts.Type[] } 1342 */ 1343export function findNonNullType(type: ts.Type): ts.Type | ts.Type[] { 1344 if (!type.isUnion() || !(type.types && type.types.length)) { 1345 return type; 1346 } 1347 const filteredTypes: ts.Type[] = type.types.filter((t) => { 1348 return t.flags && !(t.flags & ts.TypeFlags.Nullable); 1349 }); 1350 if (filteredTypes.length === 1) { 1351 return filteredTypes[0]; 1352 } 1353 return filteredTypes; 1354} 1355 1356/** 1357 * Manage File type and checker 1358 */ 1359export class CurrentProcessFile { 1360 private static isProcessingFileETS: boolean = false; 1361 1362 /** 1363 * Get checker according to file type 1364 * 1365 * @param { ts.Program? } program 1366 * @returns { ts.TypeChecker | undefined } 1367 */ 1368 static getChecker(program?: ts.Program): ts.TypeChecker | undefined { 1369 if (CurrentProcessFile.isProcessingFileETS) { 1370 return (program ?? globalProgram.program)?.getLinterTypeChecker(); 1371 } 1372 return (program ?? globalProgram.program)?.getTypeChecker(); 1373 } 1374 1375 /** 1376 * Record current file type 1377 * 1378 * @param { string } id 1379 */ 1380 static setIsProcessingFileETS(id: string): void { 1381 CurrentProcessFile.isProcessingFileETS = CurrentProcessFile.isETSFile(id); 1382 } 1383 1384 private static isETSFile(id: string): boolean { 1385 return !!(path.extname(id)?.endsWith('ets')); 1386 } 1387}