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