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