• 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} 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