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} from './pre_define'; 42 43export enum LogType { 44 ERROR = 'ERROR', 45 WARN = 'WARN', 46 NOTE = 'NOTE' 47} 48export const TEMPORARYS: string = 'temporarys'; 49export const BUILD: string = 'build'; 50export const SRC_MAIN: string = 'src/main'; 51 52const red: string = '\u001b[31m'; 53const reset: string = '\u001b[39m'; 54 55const WINDOWS: string = 'Windows_NT'; 56const LINUX: string = 'Linux'; 57const MAC: string = 'Darwin'; 58 59export interface LogInfo { 60 type: LogType, 61 message: string, 62 pos?: number, 63 line?: number, 64 column?: number, 65 fileName?: string 66} 67 68export const repeatLog: Map<string, LogInfo> = new Map(); 69 70export class FileLog { 71 private _sourceFile: ts.SourceFile | undefined; 72 private _errors: LogInfo[] = []; 73 74 public get sourceFile() { 75 return this._sourceFile; 76 } 77 78 public set sourceFile(newValue: ts.SourceFile) { 79 this._sourceFile = newValue; 80 } 81 82 public get errors() { 83 return this._errors; 84 } 85 86 public set errors(newValue: LogInfo[]) { 87 this._errors = newValue; 88 } 89 90 public cleanUp(): void { 91 this._sourceFile = undefined; 92 this._errors = []; 93 } 94} 95 96export function emitLogInfo(loader: any, infos: LogInfo[], fastBuild: boolean = false, 97 resourcePath: string = null): void { 98 if (infos && infos.length) { 99 infos.forEach((item) => { 100 switch (item.type) { 101 case LogType.ERROR: 102 fastBuild ? loader.error('\u001b[31m' + getMessage(item.fileName || resourcePath, item, true)) : 103 loader.emitError(getMessage(item.fileName || loader.resourcePath, item)); 104 break; 105 case LogType.WARN: 106 fastBuild ? loader.warn('\u001b[33m' + getMessage(item.fileName || resourcePath, item, true)) : 107 loader.emitWarning(getMessage(item.fileName || loader.resourcePath, item)); 108 break; 109 case LogType.NOTE: 110 fastBuild ? loader.info('\u001b[34m' + getMessage(item.fileName || resourcePath, item, true)) : 111 loader.emitWarning(getMessage(loader.resourcePath, item)); 112 break; 113 } 114 }); 115 } 116} 117 118export function addLog(type: LogType, message: string, pos: number, log: LogInfo[], 119 sourceFile: ts.SourceFile) { 120 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(pos); 121 log.push({ 122 type: type, 123 message: message, 124 line: posOfNode.line + 1, 125 column: posOfNode.character + 1, 126 fileName: sourceFile.fileName 127 }); 128} 129 130export function getMessage(fileName: string, info: LogInfo, fastBuild: boolean = false): string { 131 let message: string; 132 if (info.line && info.column) { 133 message = `BUILD${info.type} File: ${fileName}:${info.line}:${info.column}\n ${info.message}`; 134 } else { 135 message = `BUILD${info.type} File: ${fileName}\n ${info.message}`; 136 } 137 if (fastBuild) { 138 message = message.replace(/^BUILD/, 'ArkTS:'); 139 } 140 return message; 141} 142 143export function getTransformLog(transformLog: FileLog): LogInfo[] { 144 const sourceFile: ts.SourceFile = transformLog.sourceFile; 145 const logInfos: LogInfo[] = transformLog.errors.map((item) => { 146 if (item.pos) { 147 if (!item.column || !item.line) { 148 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(item.pos); 149 item.line = posOfNode.line + 1; 150 item.column = posOfNode.character + 1; 151 } 152 } else { 153 item.line = item.line || undefined; 154 item.column = item.column || undefined; 155 } 156 if (!item.fileName) { 157 item.fileName = sourceFile.fileName; 158 } 159 return item; 160 }); 161 return logInfos; 162} 163 164class ComponentInfo { 165 private _id: number = 0; 166 private _componentNames: Set<string> = new Set(['ForEach']); 167 public set id(id: number) { 168 this._id = id; 169 } 170 public get id() { 171 return this._id; 172 } 173 public set componentNames(componentNames: Set<string>) { 174 this._componentNames = componentNames; 175 } 176 public get componentNames() { 177 return this._componentNames; 178 } 179} 180 181export let componentInfo: ComponentInfo = new ComponentInfo(); 182 183export function hasDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | 184 ts.StructDeclaration | ts.ClassDeclaration, decortorName: string, 185 customBuilder: ts.Decorator[] = null, log: LogInfo[] = null): boolean { 186 const decorators: readonly ts.Decorator[] = ts.getAllDecorators(node); 187 if (decorators && decorators.length) { 188 const extendResult = { 189 Extend: false, 190 AnimatableExtend: false 191 } 192 for (let i = 0; i < decorators.length; i++) { 193 const originalDecortor: string = decorators[i].getText().replace(/\(.*\)$/, '').trim(); 194 if (log && EXTEND_DECORATORS.includes(decortorName)) { 195 if (originalDecortor === COMPONENT_EXTEND_DECORATOR) { 196 extendResult.Extend = true; 197 } 198 if (originalDecortor === COMPONENT_ANIMATABLE_EXTEND_DECORATOR) { 199 extendResult.AnimatableExtend = true; 200 } 201 } else { 202 if (originalDecortor === decortorName) { 203 if (customBuilder) { 204 customBuilder.push(...decorators.slice(i + 1), ...decorators.slice(0, i)); 205 } 206 return true; 207 } 208 } 209 } 210 if (log && extendResult.Extend && extendResult.AnimatableExtend) { 211 log.push({ 212 type: LogType.ERROR, 213 message: `The function can not be decorated by '@Extend' and '@AnimatableExtend' at the same time.`, 214 pos: node.getStart() 215 }); 216 } 217 return (decortorName === COMPONENT_EXTEND_DECORATOR && extendResult.Extend) 218 || (decortorName === COMPONENT_ANIMATABLE_EXTEND_DECORATOR && extendResult.AnimatableExtend); 219 } 220 return false; 221} 222 223const STATEMENT_EXPECT: number = 1128; 224const SEMICOLON_EXPECT: number = 1005; 225const STATESTYLES_EXPECT: number = 1003; 226export const IGNORE_ERROR_CODE: number[] = [STATEMENT_EXPECT, SEMICOLON_EXPECT, STATESTYLES_EXPECT]; 227 228export function readFile(dir: string, utFiles: string[]): void { 229 try { 230 const files: string[] = fs.readdirSync(dir); 231 files.forEach((element) => { 232 const filePath: string = path.join(dir, element); 233 const status: fs.Stats = fs.statSync(filePath); 234 if (status.isDirectory()) { 235 readFile(filePath, utFiles); 236 } else { 237 utFiles.push(filePath); 238 } 239 }); 240 } catch (e) { 241 console.error(red, 'ArkTS ERROR: ' + e, reset); 242 } 243} 244 245export function circularFile(inputPath: string, outputPath: string): void { 246 if (!inputPath || !outputPath) { 247 return; 248 } 249 fs.readdir(inputPath, function (err, files) { 250 if (!files) { 251 return; 252 } 253 files.forEach(file => { 254 const inputFile: string = path.resolve(inputPath, file); 255 const outputFile: string = path.resolve(outputPath, file); 256 const fileStat: fs.Stats = fs.statSync(inputFile); 257 if (fileStat.isFile()) { 258 copyFile(inputFile, outputFile); 259 } else { 260 circularFile(inputFile, outputFile); 261 } 262 }); 263 }); 264} 265 266function copyFile(inputFile: string, outputFile: string): void { 267 try { 268 const parent: string = path.join(outputFile, '..'); 269 if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { 270 mkDir(parent); 271 } 272 if (fs.existsSync(outputFile)) { 273 return; 274 } 275 const readStream: fs.ReadStream = fs.createReadStream(inputFile); 276 const writeStream: fs.WriteStream = fs.createWriteStream(outputFile); 277 readStream.pipe(writeStream); 278 readStream.on('close', function () { 279 writeStream.end(); 280 }); 281 } catch (err) { 282 throw err.message; 283 } 284} 285 286export function mkDir(path_: string): void { 287 const parent: string = path.join(path_, '..'); 288 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 289 mkDir(parent); 290 } 291 fs.mkdirSync(path_); 292} 293 294export function toUnixPath(data: string): string { 295 if (/^win/.test(require('os').platform())) { 296 const fileTmps: string[] = data.split(path.sep); 297 const newData: string = path.posix.join(...fileTmps); 298 return newData; 299 } 300 return data; 301} 302 303export function tryToLowerCasePath(filePath: string): string { 304 return toUnixPath(filePath).toLowerCase(); 305} 306 307export function toHashData(path: string): string { 308 const content: string = fs.readFileSync(path).toString(); 309 const hash: Hash = createHash('sha256'); 310 hash.update(content); 311 return hash.digest('hex'); 312} 313 314export function writeFileSync(filePath: string, content: string): void { 315 if (!fs.existsSync(filePath)) { 316 const parent: string = path.join(filePath, '..'); 317 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 318 mkDir(parent); 319 } 320 } 321 fs.writeFileSync(filePath, content); 322} 323export function genLoaderOutPathOfHar(filePath: string, cachePath: string, buildPath: string, moduleRootPath: string, projectRootPath): string { 324 filePath = toUnixPath(filePath); 325 buildPath = toUnixPath(buildPath); 326 const cacheRootPath: string = toUnixPath(cachePath); 327 const moduleName = toUnixPath(moduleRootPath).replace(toUnixPath(projectRootPath), ''); 328 const relativeFilePath: string = filePath.replace(cacheRootPath, '').replace(moduleName, ''); 329 const output: string = path.join(buildPath, relativeFilePath); 330 return output; 331} 332 333export function genTemporaryPath(filePath: string, projectPath: string, buildPath: string, 334 projectConfig: Object, buildInHar: boolean = false): string { 335 filePath = toUnixPath(filePath).replace(/\.[cm]js$/, EXTNAME_JS); 336 projectPath = toUnixPath(projectPath); 337 338 if (process.env.compileTool === 'rollup') { 339 const projectRootPath: string = toUnixPath(buildInHar ? projectPath : projectConfig.projectRootPath); 340 const relativeFilePath: string = filePath.replace(projectRootPath, ''); 341 const output: string = path.join(buildPath, relativeFilePath); 342 return output; 343 } 344 345 if (isPackageModulesFile(filePath, projectConfig)) { 346 const packageDir: string = projectConfig.packageDir; 347 const fakePkgModulesPath: string = toUnixPath(path.join(projectConfig.projectRootPath, packageDir)); 348 let output: string = ''; 349 if (filePath.indexOf(fakePkgModulesPath) === -1) { 350 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 351 const tempFilePath: string = filePath.replace(hapPath, ''); 352 const relativeFilePath: string = tempFilePath.substring(tempFilePath.indexOf(packageDir) + packageDir.length + 1); 353 output = path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, MAIN, relativeFilePath); 354 } else { 355 output = filePath.replace(fakePkgModulesPath, 356 path.join(buildPath, buildInHar ? '' : TEMPORARY, packageDir, AUXILIARY)); 357 } 358 return output; 359 } 360 361 if (filePath.indexOf(projectPath) !== -1) { 362 const relativeFilePath: string = filePath.replace(projectPath, ''); 363 const output: string = path.join(buildPath, buildInHar ? '' : TEMPORARY, relativeFilePath); 364 return output; 365 } 366 367 return ''; 368} 369 370export function isPackageModulesFile(filePath: string, projectConfig: Object): boolean { 371 filePath = toUnixPath(filePath); 372 const hapPath: string = toUnixPath(projectConfig.projectRootPath); 373 const tempFilePath: string = filePath.replace(hapPath, ''); 374 const packageDir: string = projectConfig.packageDir; 375 if (tempFilePath.indexOf(packageDir) !== -1) { 376 const fakePkgModulesPath: string = toUnixPath(path.resolve(projectConfig.projectRootPath, packageDir)); 377 if (filePath.indexOf(fakePkgModulesPath) !== -1) { 378 return true; 379 } 380 if (projectConfig.modulePathMap) { 381 for (const key in projectConfig.modulePathMap) { 382 const value: string = projectConfig.modulePathMap[key]; 383 const fakeModulePkgModulesPath: string = toUnixPath(path.resolve(value, packageDir)); 384 if (filePath.indexOf(fakeModulePkgModulesPath) !== -1) { 385 return true; 386 } 387 } 388 } 389 } 390 391 return false; 392} 393 394export interface GeneratedFileInHar { 395 sourcePath: string; 396 sourceCachePath?: string; 397 obfuscatedSourceCachePath?: string; 398 originalDeclarationCachePath?: string; 399 originalDeclarationContent?: string; 400 obfuscatedDeclarationCachePath?: string; 401} 402 403export const harFilesRecord: Map<string, GeneratedFileInHar> = new Map(); 404 405export function generateSourceFilesInHar(sourcePath: string, sourceContent: string, suffix: string, projectConfig: any) { 406 // compileShared: compile shared har of project 407 let jsFilePath: string = genTemporaryPath(sourcePath, 408 projectConfig.compileShared ? projectConfig.projectRootPath : projectConfig.moduleRootPath, 409 projectConfig.compileShared ? path.resolve(projectConfig.aceModuleBuild, '../etsFortgz') : projectConfig.cachePath, 410 projectConfig, projectConfig.compileShared); 411 if (!jsFilePath.match(new RegExp(projectConfig.packageDir))) { 412 jsFilePath = jsFilePath.replace(/\.ets$/, suffix).replace(/\.ts$/, suffix); 413 if (projectConfig.obfuscateHarType === 'uglify' && suffix === '.js') { 414 sourceContent = uglifyJS.minify(sourceContent).code; 415 } 416 // collect the declaration files for obfuscation 417 if (projectConfig.compileMode === ESMODULE && (/\.d\.e?ts$/).test(jsFilePath)) { 418 sourcePath = toUnixPath(sourcePath); 419 const genFilesInHar: GeneratedFileInHar = { sourcePath: sourcePath, originalDeclarationCachePath: jsFilePath, originalDeclarationContent: sourceContent }; 420 harFilesRecord.set(sourcePath, genFilesInHar); 421 return; 422 } else { 423 mkdirsSync(path.dirname(jsFilePath)); 424 } 425 fs.writeFileSync(jsFilePath, sourceContent); 426 } 427} 428 429export function mkdirsSync(dirname: string): boolean { 430 if (fs.existsSync(dirname)) { 431 return true; 432 } else if (mkdirsSync(path.dirname(dirname))) { 433 fs.mkdirSync(dirname); 434 return true; 435 } 436 437 return false; 438} 439 440export function nodeLargeOrEqualTargetVersion(targetVersion: number): boolean { 441 const currentNodeVersion: number = parseInt(process.versions.node.split('.')[0]); 442 if (currentNodeVersion >= targetVersion) { 443 return true; 444 } 445 446 return false; 447} 448 449export function removeDir(dirName: string): void { 450 if (fs.existsSync(dirName)) { 451 if (nodeLargeOrEqualTargetVersion(16)) { 452 fs.rmSync(dirName, { recursive: true }); 453 } else { 454 fs.rmdirSync(dirName, { recursive: true }); 455 } 456 } 457} 458 459export function parseErrorMessage(message: string): string { 460 const messageArrary: string[] = message.split('\n'); 461 let logContent: string = ''; 462 messageArrary.forEach(element => { 463 if (!(/^at/.test(element.trim()))) { 464 logContent = logContent + element + '\n'; 465 } 466 }); 467 return logContent; 468} 469 470export function isWindows(): boolean { 471 return os.type() === WINDOWS; 472} 473 474export function isLinux(): boolean { 475 return os.type() === LINUX; 476} 477 478export function isMac(): boolean { 479 return os.type() === MAC; 480} 481 482export function maxFilePathLength(): number { 483 if (isWindows()) { 484 return 32766; 485 } else if (isLinux()) { 486 return 4095; 487 } else if (isMac()) { 488 return 1016; 489 } else { 490 return -1; 491 } 492} 493 494export function validateFilePathLength(filePath: string, logger: Object): boolean { 495 if (maxFilePathLength() < 0) { 496 logger.error(red, "Unknown OS platform", reset); 497 process.exitCode = FAIL; 498 return false; 499 } else if (filePath.length > 0 && filePath.length <= maxFilePathLength()) { 500 return true; 501 } else if (filePath.length > maxFilePathLength()) { 502 logger.error(red, `The length of ${filePath} exceeds the limitation of current platform, which is ` + 503 `${maxFilePathLength()}. Please try moving the project folder to avoid deeply nested file path and try again`, 504 reset); 505 process.exitCode = FAIL; 506 return false; 507 } else { 508 logger.error(red, "Validate file path failed", reset); 509 process.exitCode = FAIL; 510 return false; 511 } 512} 513 514export function validateFilePathLengths(filePaths: Array<string>, logger: any): boolean { 515 filePaths.forEach((filePath) => { 516 if (!validateFilePathLength(filePath, logger)) { 517 return false; 518 } 519 }) 520 return true; 521} 522 523export function unlinkSync(filePath: string): void { 524 if (fs.existsSync(filePath)) { 525 fs.unlinkSync(filePath); 526 } 527} 528 529export function getExtensionIfUnfullySpecifiedFilepath(filePath: string): string { 530 if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { 531 return ""; 532 } 533 534 let extension: string = EXTNAME_ETS; 535 if (fs.existsSync(filePath + '.ts') && fs.statSync(filePath + '.ts').isFile()) { 536 extension = '.ts'; 537 } else if (fs.existsSync(filePath + '.d.ts') && fs.statSync(filePath + '.d.ts').isFile()) { 538 extension = '.d.ts'; 539 } else if (fs.existsSync(filePath + '.d.ets') && fs.statSync(filePath + '.d.ets').isFile()) { 540 extension = '.d.ets'; 541 } else if (fs.existsSync(filePath + '.js') && fs.statSync(filePath + '.js').isFile()) { 542 extension = '.js'; 543 } else if (fs.existsSync(filePath + '.json') && fs.statSync(filePath + '.json').isFile()) { 544 extension = '.json'; 545 } 546 547 return extension; 548} 549 550export function shouldWriteChangedList(watchModifiedFiles: string[], 551 watchRemovedFiles: string[]): boolean { 552 if (projectConfig.compileMode === ESMODULE && process.env.watchMode === 'true' && !projectConfig.isPreview && 553 projectConfig.changedFileList && (watchRemovedFiles.length + watchModifiedFiles.length)) { 554 if (process.env.compileTool !== 'rollup') { 555 if (!(watchModifiedFiles.length === 1 && 556 watchModifiedFiles[0] === projectConfig.projectPath && !watchRemovedFiles.length)) { 557 return true; 558 } else { 559 return false; 560 } 561 } 562 return true; 563 } 564 return false; 565} 566 567interface HotReloadIncrementalTime { 568 hotReloadIncrementalStartTime: string; 569 hotReloadIncrementalEndTime: string; 570} 571 572export const hotReloadIncrementalTime: HotReloadIncrementalTime = { 573 hotReloadIncrementalStartTime: '', 574 hotReloadIncrementalEndTime: '' 575}; 576 577interface FilesObj { 578 modifiedFiles: string[], 579 removedFiles: string[] 580} 581 582let allModifiedFiles: Set<string> = new Set(); 583 584export function getHotReloadFiles(watchModifiedFiles: string[], 585 watchRemovedFiles: string[], hotReloadSupportFiles: Set<string>): FilesObj { 586 hotReloadIncrementalTime.hotReloadIncrementalStartTime = new Date().getTime().toString(); 587 watchRemovedFiles = watchRemovedFiles.map(file => path.relative(projectConfig.projectPath, file)); 588 allModifiedFiles = new Set([...allModifiedFiles, ...watchModifiedFiles 589 .filter(file => fs.statSync(file).isFile() && 590 (hotReloadSupportFiles.has(file) || !['.ets', '.ts', '.js'].includes(path.extname(file)))) 591 .map(file => path.relative(projectConfig.projectPath, file))] 592 .filter(file => !watchRemovedFiles.includes(file))); 593 return { 594 modifiedFiles: [...allModifiedFiles], 595 removedFiles: [...watchRemovedFiles] 596 }; 597} 598 599export function getResolveModules(projectPath: string, faMode: boolean): string[] { 600 if (faMode) { 601 return [ 602 path.resolve(projectPath, '../../../../../'), 603 path.resolve(projectPath, '../../../../' + projectConfig.packageDir), 604 path.resolve(projectPath, '../../../../../' + projectConfig.packageDir), 605 path.resolve(projectPath, '../../') 606 ]; 607 } else { 608 return [ 609 path.resolve(projectPath, '../../../../'), 610 path.resolve(projectPath, '../../../' + projectConfig.packageDir), 611 path.resolve(projectPath, '../../../../' + projectConfig.packageDir), 612 path.resolve(projectPath, '../') 613 ]; 614 } 615} 616 617export function writeUseOSFiles(useOSFiles: Set<string>): void { 618 let info: string = ''; 619 if (!fs.existsSync(projectConfig.aceSoPath)) { 620 const parent: string = path.resolve(projectConfig.aceSoPath, '..'); 621 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 622 mkDir(parent); 623 } 624 } else { 625 info = fs.readFileSync(projectConfig.aceSoPath, 'utf-8') + '\n'; 626 } 627 fs.writeFileSync(projectConfig.aceSoPath, info + Array.from(useOSFiles).join('\n')); 628} 629 630 631export function writeCollectionFile(cachePath: string, appCollection: Map<string, Set<string>>, 632 allComponentsOrModules: Map<string, Array<string>>, fileName: string, allFiles: Set<string> = null, 633 widgetPath: string = undefined) { 634 for (let key of appCollection.keys()) { 635 if (appCollection.get(key).size === 0) { 636 allComponentsOrModules.delete(key); 637 continue; 638 } 639 if (allFiles && !allFiles.has(key)) { 640 continue; 641 } 642 const newKey: string = projectConfig.projectRootPath ? path.relative(projectConfig.projectRootPath, key) : key; 643 allComponentsOrModules.set(newKey, Array.from(appCollection.get(key))); 644 } 645 const content: string = JSON.stringify(Object.fromEntries(allComponentsOrModules), null, 2); 646 writeFileSync(path.resolve(cachePath, fileName), content); 647 if (widgetPath) { 648 writeFileSync(path.resolve(widgetPath, fileName), content); 649 } 650} 651 652export function getAllComponentsOrModules(allFiles: Set<string>, 653 cacheCollectionFileName: string): Map<string, Array<string>> { 654 const cacheCollectionFilePath: string = path.resolve(projectConfig.cachePath, cacheCollectionFileName); 655 const allComponentsOrModules: Map<string, Array<string>> = new Map(); 656 if (!fs.existsSync(cacheCollectionFilePath)) { 657 return allComponentsOrModules; 658 } 659 const lastComponentsOrModules = require(cacheCollectionFilePath); 660 for (let key in lastComponentsOrModules) { 661 if (allFiles.has(key)) { 662 allComponentsOrModules.set(key, lastComponentsOrModules[key]); 663 } 664 } 665 return allComponentsOrModules; 666} 667 668export function getPossibleBuilderTypeParameter(parameters: ts.ParameterDeclaration[]): string[] { 669 const parameterNames: string[] = []; 670 if (!partialUpdateConfig.builderCheck) { 671 if (is$$Parameter(parameters)) { 672 parameters[0].type.members.forEach((member) => { 673 if (member.name && ts.isIdentifier(member.name)) { 674 parameterNames.push(member.name.escapedText.toString()); 675 } 676 }); 677 } else { 678 parameters.forEach((parameter) => { 679 if (parameter.name && ts.isIdentifier(parameter.name)) { 680 parameterNames.push(parameter.name.escapedText.toString()); 681 } 682 }); 683 } 684 } 685 return parameterNames; 686} 687 688function is$$Parameter(parameters: ts.ParameterDeclaration[]): boolean { 689 return parameters.length === 1 && parameters[0].name && ts.isIdentifier(parameters[0].name) && 690 parameters[0].name.escapedText.toString() === $$ && parameters[0].type && ts.isTypeLiteralNode(parameters[0].type) && 691 parameters[0].type.members && parameters[0].type.members.length > 0; 692} 693 694interface ChildrenCacheFile { 695 fileName: string, 696 mtimeMs: number, 697} 698 699export interface CacheFile { 700 mtimeMs: number, 701 children: Array<ChildrenCacheFile>, 702} 703 704// Global Information & Method 705class ProcessFileInfo { 706 buildStart: boolean = true; 707 wholeFileInfo: { [id: string]: SpecialArkTSFileInfo | TSFileInfo } = {}; // Save ArkTS & TS file's infomation 708 transformedFiles: Set<string> = new Set(); // ArkTS & TS Files which should be transformed in this compilation 709 cachedFiles: string[] = []; // ArkTS & TS Files which should not be transformed in this compilation 710 shouldHaveEntry: string[] = []; // Which file should have @Entry decorator 711 resourceToFile: { [resource: string]: Set<string> } = {}; // Resource is used by which file 712 lastResourceList: Set<string> = new Set(); 713 resourceList: Set<string> = new Set(); // Whole project resource 714 shouldInvalidFiles: Set<string> = new Set(); 715 resourceTableChanged: boolean = false; 716 currentArkTsFile: SpecialArkTSFileInfo; 717 reUseProgram: boolean = false; 718 resourcesArr: Set<string> = new Set(); 719 lastResourcesSet: Set<string> = new Set(); 720 transformCacheFiles: { [fileName: string]: CacheFile } = {}; 721 processBuilder: boolean = false; 722 processGlobalBuilder: boolean = false; 723 builderLikeCollection: Set<string> = new Set(); 724 newTsProgram: ts.Program; 725 changeFiles: string[] = []; 726 isFirstBuild: boolean = true; 727 processForEach: number = 0; 728 processLazyForEach: number = 0; 729 lazyForEachInfo: { 730 forEachParameters: ts.ParameterDeclaration, 731 isDependItem: boolean 732 } = { 733 forEachParameters: null, 734 isDependItem: false 735 }; 736 737 addGlobalCacheInfo(resourceListCacheInfo: string[], 738 resourceToFileCacheInfo: { [resource: string]: Set<string> }) { 739 if (this.buildStart) { 740 for (const element in resourceToFileCacheInfo) { 741 this.resourceToFile[element] = new Set(resourceToFileCacheInfo[element]); 742 } 743 this.lastResourceList = new Set(resourceListCacheInfo); 744 } 745 if (this.resourceTableChanged) { 746 this.compareResourceDiff(); 747 } 748 } 749 750 addFileCacheInfo(id: string, fileCacheInfo: fileInfo) { 751 if (fileCacheInfo && process.env.compileMode === 'moduleJson') { 752 if (Array.isArray(fileCacheInfo.fileToResourceList)) { 753 fileCacheInfo.fileToResourceList = new Set(fileCacheInfo.fileToResourceList); 754 } else { 755 fileCacheInfo.fileToResourceList = new Set(); 756 } 757 } 758 if (id.match(/(?<!\.d)\.(ets)$/)) { 759 this.wholeFileInfo[id] = new SpecialArkTSFileInfo(fileCacheInfo); 760 } else if (id.match(/(?<!\.d)\.(ts)$/) && process.env.compileMode === 'moduleJson') { 761 this.wholeFileInfo[id] = new TSFileInfo(fileCacheInfo); 762 } 763 } 764 765 collectTransformedFiles(id: string) { 766 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 767 this.transformedFiles.add(id); 768 } 769 } 770 771 collectCachedFiles(id: string) { 772 if (id.match(process.env.compileMode === 'moduleJson' ? /(?<!\.d)\.(ets|ts)$/ : /(?<!\.d)\.(ets)$/)) { 773 this.cachedFiles.push(id); 774 } 775 } 776 777 judgeShouldHaveEntryFiles(entryFileWithoutEntryDecorator: string[]): void { 778 this.shouldHaveEntry = Object.values(projectConfig.entryObj as string[]).filter((item) => { 779 return !entryFileWithoutEntryDecorator.includes(item.toLowerCase()) && item.match(/(?<!\.d)\.(ets)$/); 780 }); 781 } 782 783 saveCacheFileInfo(cache) { 784 if (process.env.compileMode === 'moduleJson') { 785 const fileCacheInfo: { [id: string]: fileInfo | tsFileInfo } = cache.get('fileCacheInfo') || {}; 786 const resourceToFileCacheInfo = cache.get('resourceToFileCacheInfo') || {}; 787 for (const i in resourceToFileCacheInfo) { 788 resourceToFileCacheInfo[i] = new Set(resourceToFileCacheInfo[i]); 789 } 790 const resourceToFile: { [resource: string]: Set<string> | string[] } = Object.assign(resourceToFileCacheInfo, this.resourceToFile); 791 for (const id of this.transformedFiles) { 792 fileCacheInfo[id] = this.wholeFileInfo[id].fileInfo; 793 for (const resource of this.wholeFileInfo[id].newFileToResourceList) { 794 if (!(fileCacheInfo[id].fileToResourceList as Set<string>).has(resource)) { 795 if (!resourceToFile[resource]) { 796 resourceToFile[resource] = new Set(); 797 } 798 (resourceToFile[resource] as Set<string>).add(id); 799 } 800 } 801 for (const resource of fileCacheInfo[id].fileToResourceList) { 802 if (!this.wholeFileInfo[id].newFileToResourceList.has(resource)) { 803 (resourceToFile[resource] as Set<string>).delete(id); 804 } 805 } 806 fileCacheInfo[id].fileToResourceList = [...this.wholeFileInfo[id].newFileToResourceList]; 807 } 808 for (const id of this.cachedFiles) { 809 fileCacheInfo[id].fileToResourceList = [...fileCacheInfo[id].fileToResourceList]; 810 } 811 this.resourceToFile = resourceToFile as { [resource: string]: Set<string> }; 812 for (const resource in resourceToFile) { 813 resourceToFile[resource] = [...resourceToFile[resource]]; 814 } 815 cache.set('fileCacheInfo', fileCacheInfo); 816 cache.set('resourceListCacheInfo', [...this.resourceList]); 817 cache.set('resourceToFileCacheInfo', resourceToFile); 818 } else { 819 const cacheInfo: { [id: string]: fileInfo } = cache.get('fileCacheInfo') || {}; 820 for (const id of this.transformedFiles) { 821 cacheInfo[id] = this.wholeFileInfo[id].fileInfo; 822 } 823 cache.set('fileCacheInfo', cacheInfo); 824 } 825 } 826 827 updateResourceList(resource: string) { 828 this.resourceList.add(resource); 829 } 830 831 compareResourceDiff() { 832 // delete resource 833 for (const resource of this.lastResourceList) { 834 if (!this.resourceList.has(resource) && this.resourceToFile[resource]) { 835 this.resourceToFile[resource].forEach(file => { 836 this.shouldInvalidFiles.add(file); 837 }); 838 } 839 } 840 // create resource 841 for (const resource of this.resourceList) { 842 if (!this.resourceToFile[resource]) { 843 this.resourceToFile[resource] = new Set(); 844 } 845 if (!this.lastResourceList.has(resource)) { 846 this.resourceToFile[resource].forEach(file => { 847 this.shouldInvalidFiles.add(file); 848 }); 849 } 850 } 851 } 852 853 collectResourceInFile(resource: string, file: string) { 854 this.wholeFileInfo[file].newFileToResourceList.add(resource); 855 } 856 857 clearCollectedInfo(cache) { 858 this.buildStart = false; 859 this.resourceTableChanged = false; 860 this.saveCacheFileInfo(cache); 861 this.transformedFiles = new Set(); 862 this.cachedFiles = []; 863 this.lastResourceList = new Set([...this.resourceList]); 864 this.shouldInvalidFiles.clear(); 865 this.resourcesArr.clear(); 866 } 867 setCurrentArkTsFile(): void { 868 this.currentArkTsFile = new SpecialArkTSFileInfo(); 869 } 870 getCurrentArkTsFile(): SpecialArkTSFileInfo { 871 return this.currentArkTsFile; 872 } 873} 874 875export let storedFileInfo: ProcessFileInfo = new ProcessFileInfo(); 876 877export interface fileInfo extends tsFileInfo { 878 hasEntry: boolean; // Has @Entry decorator or not 879} 880 881export interface tsFileInfo { 882 fileToResourceList: Set<string> | string[]; // How much Resource is used 883} 884 885// Save single TS file information 886class TSFileInfo { 887 fileInfo: tsFileInfo = { 888 fileToResourceList: new Set() 889 } 890 newFileToResourceList: Set<string> = new Set() 891 constructor(cacheInfo: fileInfo, etsFile?: boolean) { 892 if (!etsFile) { 893 this.fileInfo = cacheInfo || this.fileInfo; 894 } 895 } 896} 897 898// Save single ArkTS file information 899class SpecialArkTSFileInfo extends TSFileInfo { 900 fileInfo: fileInfo = { 901 hasEntry: false, 902 fileToResourceList: new Set() 903 } 904 recycleComponents: Set<string> = new Set([]); 905 compFromDETS: Set<string> = new Set(); 906 animatableExtendAttribute: Map<string, Set<string>> = new Map(); 907 908 constructor(cacheInfo?: fileInfo) { 909 super(cacheInfo, true); 910 this.fileInfo = cacheInfo || this.fileInfo; 911 } 912 913 get hasEntry() { 914 return this.fileInfo.hasEntry; 915 } 916 set hasEntry(value: boolean) { 917 this.fileInfo.hasEntry = value; 918 } 919} 920 921export function setChecker(): void { 922 if (globalProgram.program) { 923 globalProgram.checker = globalProgram.program.getTypeChecker(); 924 } else if (globalProgram.watchProgram) { 925 globalProgram.checker = globalProgram.watchProgram.getCurrentProgram().getProgram().getTypeChecker(); 926 } 927} 928export interface ExtendResult { 929 decoratorName: string; 930 componentName: string; 931} 932 933export function resourcesRawfile(rawfilePath: string, resourcesArr: Set<string>, resourceName: string = ''): void { 934 if (fs.existsSync(process.env.rawFileResource) && fs.statSync(rawfilePath).isDirectory()) { 935 const files: string[] = fs.readdirSync(rawfilePath); 936 files.forEach((file: string) => { 937 if (fs.statSync(path.join(rawfilePath, file)).isDirectory()) { 938 resourcesRawfile(path.join(rawfilePath, file), resourcesArr, resourceName ? resourceName + '/' + file : file); 939 } else { 940 if (resourceName) { 941 resourcesArr.add(resourceName + '/' + file); 942 } else { 943 resourcesArr.add(file); 944 } 945 } 946 }); 947 } 948} 949 950export function differenceResourcesRawfile(oldRawfile: Set<string>, newRawfile: Set<string>): boolean { 951 if (oldRawfile.size !== 0 && oldRawfile.size === newRawfile.size) { 952 for (const singleRawfiles of oldRawfile.values()) { 953 if (!newRawfile.has(singleRawfiles)) { 954 return true; 955 } 956 } 957 return false; 958 } else if (oldRawfile.size === 0 && oldRawfile.size === newRawfile.size) { 959 return false; 960 } else { 961 return true; 962 } 963} 964 965export function isString(text: unknown): text is string { 966 return typeof text === 'string'; 967} 968 969export function getRollupCacheStoreKey(projectConfig: object): string { 970 let keyInfo: string[] = [projectConfig.compileSdkVersion, projectConfig.compatibleSdkVersion, projectConfig.runtimeOS, 971 projectConfig.etsLoaderPath]; 972 return keyInfo.join('#'); 973} 974 975export function getRollupCacheKey(projectConfig: object): string { 976 let isWidget: string = projectConfig.widgetCompile ? 'widget' : 'non-widget'; 977 let ohosTestInfo: string = 'non-ohosTest'; 978 if (projectConfig.testFrameworkPar) { 979 ohosTestInfo = JSON.stringify(projectConfig.testFrameworkPar); 980 } 981 982 let keyInfo: string[] = [projectConfig.entryModuleName, projectConfig.targetName, isWidget, ohosTestInfo, 983 projectConfig.needCoverageInsert, projectConfig.isOhosTest]; 984 return keyInfo.join('#'); 985} 986 987export function clearRollupCacheStore(cacheStoreManager: object, currentKey: string): void { 988 if (!cacheStoreManager) { 989 return; 990 } 991 992 for (let key of cacheStoreManager.keys()) { 993 if (key !== currentKey) { 994 cacheStoreManager.unmount(key); 995 } 996 } 997} 998 999export function startTimeStatisticsLocation(startTimeEvent: CompileEvent): void { 1000 if (startTimeEvent) { 1001 startTimeEvent.start(); 1002 } 1003} 1004 1005export function stopTimeStatisticsLocation(stopTimeEvent: CompileEvent): void { 1006 if (stopTimeEvent) { 1007 stopTimeEvent.stop(); 1008 } 1009} 1010export let resolveModuleNamesTime: CompileEvent; 1011export class CompilationTimeStatistics { 1012 hookEventFactory: HookEventFactoryType; 1013 createProgramTime: CompileEvent; 1014 runArkTSLinterTime: CompileEvent; 1015 diagnosticTime: CompileEvent; 1016 scriptSnapshotTime: CompileEvent; 1017 processImportTime: CompileEvent; 1018 processComponentClassTime: CompileEvent; 1019 validateEtsTime: CompileEvent; 1020 tsProgramEmitTime: CompileEvent; 1021 noSourceFileRebuildProgramTime: CompileEvent; 1022 etsTransformBuildStartTime: CompileEvent; 1023 etsTransformLoadTime: CompileEvent; 1024 constructor(share: Record<string, any>, pluginName: string, hookName: string) { 1025 if (share && share.getHookEventFactory) { 1026 if (pluginName === 'etsChecker' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) { 1027 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1028 this.createProgramTime = this.hookEventFactory.createEvent('createProgram'); 1029 this.runArkTSLinterTime = this.hookEventFactory.createEvent('arkTSLinter'); 1030 this.diagnosticTime = this.hookEventFactory.createEvent('diagnostic'); 1031 this.scriptSnapshotTime = this.hookEventFactory.createEvent('scriptSnapshot'); 1032 resolveModuleNamesTime = this.hookEventFactory.createEvent('resolveModuleNames'); 1033 } else if (pluginName === 'etsTransform' && hookName === 'transform' && share.getHookEventFactory(pluginName, hookName)) { 1034 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1035 this.processImportTime = this.hookEventFactory.createEvent('processImport'); 1036 this.processComponentClassTime = this.hookEventFactory.createEvent('processComponentClass'); 1037 this.validateEtsTime = this.hookEventFactory.createEvent('validateEts'); 1038 this.tsProgramEmitTime = this.hookEventFactory.createEvent('tsProgramEmit'); 1039 this.noSourceFileRebuildProgramTime = this.hookEventFactory.createEvent('noSourceFileRebuildProgram'); 1040 } else if (pluginName === 'etsTransform' && hookName === 'buildStart' && share.getHookEventFactory(pluginName, hookName)) { 1041 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1042 this.etsTransformBuildStartTime = this.hookEventFactory.createEvent('etsTransformBuildStart'); 1043 } else if (pluginName === 'etsTransform' && hookName === 'load' && share.getHookEventFactory(pluginName, hookName)) { 1044 this.hookEventFactory = share.getHookEventFactory(pluginName, hookName); 1045 this.etsTransformLoadTime = this.hookEventFactory.createEvent('etsTransformLoad'); 1046 } 1047 } 1048 } 1049} 1050 1051interface HookEventFactoryType { 1052 createEvent(name: string): CompileEvent | undefined; 1053} 1054 1055type CompileEventState = 'created' | 'beginning' | 'running' | 'failed' | 'success' | 'warn'; 1056interface CompileEvent { 1057 start(time?: number): CompileEvent; 1058 stop(state?: CompileEventState, time?: number): void; 1059 startAsyncEvent(time: number): CompileEvent; 1060 stopAsyncEvent(state?: CompileEventState, TIME?: number): void; 1061 createSubEvent(name: string): CompileEvent; 1062} 1063 1064export function resetUtils(): void { 1065 componentInfo = new ComponentInfo(); 1066 harFilesRecord.clear(); 1067 storedFileInfo = new ProcessFileInfo(); 1068} 1069 1070export function getStoredFileInfo(): ProcessFileInfo { 1071 return storedFileInfo; 1072} 1073