• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-2024 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 {
17  createPrinter,
18  createTextWriter,
19  transform,
20  createObfTextSingleLineWriter,
21} from 'typescript';
22
23import type {
24  CompilerOptions,
25  EmitTextWriter,
26  Node,
27  Printer,
28  PrinterOptions,
29  RawSourceMap,
30  SourceFile,
31  SourceMapGenerator,
32  TransformationResult,
33  TransformerFactory,
34} from 'typescript';
35
36import path from 'path';
37
38import {
39  AtIntentCollections,
40  AtKeepCollections,
41  BytecodeObfuscationCollections,
42  LocalVariableCollections,
43  PropCollections
44} from './utils/CommonCollections';
45import type { IOptions } from './configs/IOptions';
46import { FileUtils } from './utils/FileUtils';
47import { TransformerManager } from './transformers/TransformerManager';
48import { getSourceMapGenerator } from './utils/SourceMapUtil';
49import {
50  decodeSourcemap,
51  ExistingDecodedSourceMap,
52  Source,
53  SourceMapLink,
54  SourceMapSegmentObj,
55  mergeSourceMap
56} from './utils/SourceMapMergingUtil';
57import {
58  deleteLineInfoForNameString,
59  getMapFromJson,
60  IDENTIFIER_CACHE,
61  MEM_METHOD_CACHE
62} from './utils/NameCacheUtil';
63import { ListUtil } from './utils/ListUtil';
64import { needReadApiInfo, readProjectPropertiesByCollectedPaths } from './common/ApiReader';
65import type { ReseverdSetForArkguard } from './common/ApiReader';
66import { ApiExtractor } from './common/ApiExtractor';
67import esInfo from './configs/preset/es_reserved_properties.json';
68import optimizeEsInfo from './configs/preset/es_reserved_properties_optimized.json';
69import {
70  TimeSumPrinter,
71  TimeTracker,
72  endFilesEvent,
73  endSingleFileEvent,
74  initPerformancePrinter,
75  startFilesEvent,
76  startSingleFileEvent,
77} from './utils/PrinterUtils';
78import { ObConfigResolver } from './initialization/ConfigResolver';
79import {
80  EventList,
81  TimeAndMemTimeTracker,
82  clearTimeAndMemPrinterData,
83  disablePrinterTimeAndMemConfig,
84  initPerformanceTimeAndMemPrinter,
85} from './utils/PrinterTimeAndMemUtils';
86
87export {
88  TimeSumPrinter,
89  blockPrinter,
90  endFilesEvent,
91  endSingleFileEvent,
92  printTimeSumData,
93  printTimeSumInfo,
94  startFilesEvent,
95  startSingleFileEvent,
96} from './utils/PrinterUtils';
97export {
98  EventList,
99  PerfMode,
100  TimeAndMemTimeTracker,
101  blockTimeAndMemPrinter,
102  configurePerformancePrinter,
103  enableTimeAndMemoryPrint,
104} from './utils/PrinterTimeAndMemUtils';
105import { Extension, type ProjectInfo, type FilePathObj } from './common/type';
106export { type HvigorErrorInfo } from './common/type';
107export { FileUtils } from './utils/FileUtils';
108export { MemoryUtils } from './utils/MemoryUtils';
109import { TypeUtils } from './utils/TypeUtils';
110import { handleReservedConfig } from './utils/TransformUtil';
111import { UnobfuscationCollections } from './utils/CommonCollections';
112import { FilePathManager, FileContentManager, initProjectWhiteListManager } from './utils/ProjectCollections';
113import { clearHistoryUnobfuscatedMap, historyAllUnobfuscatedNamesMap } from './initialization/Initializer';
114import { MemoryDottingDefine } from './utils/MemoryDottingDefine';
115import { nodeSymbolMap } from './utils/ScopeAnalyzer';
116import { clearUnobfuscationNamesObj } from './initialization/CommonObject';
117export { UnobfuscationCollections } from './utils/CommonCollections';
118export * as ProjectCollections from './utils/ProjectCollections';
119export { separateUniversalReservedItem, containWildcards, wildcardTransformer } from './utils/TransformUtil';
120export type { ReservedNameInfo } from './utils/TransformUtil';
121export type { ReseverdSetForArkguard } from './common/ApiReader';
122
123export { initObfuscationConfig, printerTimeAndMemDataConfig } from './initialization/Initializer';
124export { nameCacheMap, unobfuscationNamesObj } from './initialization/CommonObject';
125export {
126  collectResevedFileNameInIDEConfig, // For running unit test.
127  enableObfuscatedFilePathConfig,
128  enableObfuscateFileName,
129  generateConsumerObConfigFile,
130  getRelativeSourcePath,
131  handleObfuscatedFilePath,
132  handleUniversalPathInObf,
133  mangleFilePath,
134  MergedConfig,
135  ObConfigResolver,
136  readNameCache,
137  clearNameCache,
138  writeObfuscationNameCache,
139  writeUnobfuscationContent
140} from './initialization/ConfigResolver';
141export {
142  collectReservedNameForObf
143} from './utils/NodeUtils';
144
145export const renameIdentifierModule = require('./transformers/rename/RenameIdentifierTransformer');
146export const renameFileNameModule = require('./transformers/rename/RenameFileNameTransformer');
147
148export { getMapFromJson, readProjectPropertiesByCollectedPaths, deleteLineInfoForNameString, ApiExtractor, PropCollections };
149export let orignalFilePathForSearching: string | undefined;
150export let cleanFileMangledNames: boolean = false;
151export interface PerformancePrinter {
152  filesPrinter?: TimeTracker;
153  singleFilePrinter?: TimeTracker;
154  timeSumPrinter?: TimeSumPrinter;
155}
156// TimeAndMem performance printer interface
157export interface PerformanceTimeAndMemPrinter {
158  filesPrinter?: TimeAndMemTimeTracker;
159  singleFilePrinter?: TimeAndMemTimeTracker;
160}
161export let performancePrinter: PerformancePrinter = {
162  filesPrinter: new TimeTracker(),
163  singleFilePrinter: new TimeTracker(),
164  timeSumPrinter: new TimeSumPrinter(),
165};
166// Create instance of the TimeAndMem performance printer
167export let performanceTimeAndMemPrinter: PerformanceTimeAndMemPrinter = {
168  filesPrinter: new TimeAndMemTimeTracker(),
169  singleFilePrinter: new TimeAndMemTimeTracker(),
170};
171
172// When the module is compiled, call this function to clear global collections.
173export function clearGlobalCaches(): void {
174  PropCollections.clearPropsCollections();
175  UnobfuscationCollections.clear();
176  LocalVariableCollections.clear();
177  AtKeepCollections.clear();
178  AtIntentCollections.clear();
179  renameFileNameModule.clearCaches();
180  clearUnobfuscationNamesObj();
181  clearHistoryUnobfuscatedMap();
182  clearTimeAndMemPrinterData();
183  disablePrinterTimeAndMemConfig();
184  ApiExtractor.mConstructorPropertySet.clear();
185  ApiExtractor.mEnumMemberSet.clear();
186  BytecodeObfuscationCollections.clear();
187}
188
189export type ObfuscationResultType = {
190  content: string;
191  sourceMap?: RawSourceMap;
192  nameCache?: { [k: string]: string | {} };
193  filePath?: string;
194  unobfuscationNameMap?: Map<string, Set<string>>;
195};
196
197export interface RecordInfo {
198  recordStage: string;
199  recordIndex: number;
200};
201
202const JSON_TEXT_INDENT_LENGTH: number = 2;
203export class ArkObfuscator {
204  // Used only for testing
205  protected mWriteOriginalFile: boolean = false;
206
207  // A text writer of Printer
208  private mTextWriter: EmitTextWriter;
209
210  // Compiler Options for typescript, use to parse ast
211  private readonly mCompilerOptions: CompilerOptions;
212
213  // User custom obfuscation profiles.
214  protected mCustomProfiles: IOptions;
215
216  private mTransformers: TransformerFactory<Node>[];
217
218  private static memoryDottingCallback: (stage: string) => RecordInfo;
219
220  private static memoryDottingStopCallback: (recordInfo: RecordInfo) => void;
221
222  static mProjectInfo: ProjectInfo | undefined;
223
224  // If isKeptCurrentFile is true, both identifier and property obfuscation are skipped.
225  static mIsKeptCurrentFile: boolean = false;
226
227  public isIncremental: boolean = false;
228
229  public shouldReObfuscate: boolean = false;
230
231  public filePathManager: FilePathManager | undefined;
232
233  public fileContentManager: FileContentManager | undefined;
234
235  public obfConfigResolver: ObConfigResolver;
236
237  public constructor() {
238    this.mCompilerOptions = {};
239    this.mTransformers = [];
240  }
241
242  public getWriteOriginalFileForTest(): boolean {
243    return this.mWriteOriginalFile;
244  }
245
246  public setWriteOriginalFile(flag: boolean): void {
247    this.mWriteOriginalFile = flag;
248  }
249
250  // Pass the collected whitelists related to property obfuscation to Arkguard.
251  public addReservedSetForPropertyObf(properties: ReseverdSetForArkguard): void {
252    if (properties.structPropertySet && properties.structPropertySet.size > 0) {
253      for (let reservedProperty of properties.structPropertySet) {
254        UnobfuscationCollections.reservedStruct.add(reservedProperty);
255      }
256    }
257
258    if (properties.stringPropertySet && properties.stringPropertySet.size > 0) {
259      UnobfuscationCollections.reservedStrProp = properties.stringPropertySet;
260    }
261
262    if (properties.exportNameAndPropSet && properties.exportNameAndPropSet.size > 0) {
263      UnobfuscationCollections.reservedExportNameAndProp = properties.exportNameAndPropSet;
264    }
265
266    if (properties.enumPropertySet && properties.enumPropertySet.size > 0) {
267      for (let reservedEnum of properties.enumPropertySet) {
268        UnobfuscationCollections.reservedEnum.add(reservedEnum);
269      }
270    }
271  }
272
273  public addReservedSetForDefaultObf(properties: ReseverdSetForArkguard): void {
274    if (properties.exportNameSet && properties.exportNameSet.size > 0) {
275      UnobfuscationCollections.reservedExportName = properties.exportNameSet;
276    }
277  }
278
279  public setKeepSourceOfPaths(mKeepSourceOfPaths: Set<string>): void {
280    this.mCustomProfiles.mKeepFileSourceCode.mKeepSourceOfPaths = mKeepSourceOfPaths;
281  }
282
283  public handleTsHarComments(sourceFile: SourceFile, originalPath: string | undefined): void {
284    if (ArkObfuscator.projectInfo?.useTsHar && (originalPath?.endsWith(Extension.ETS) && !originalPath?.endsWith(Extension.DETS))) {
285      // @ts-ignore
286      sourceFile.writeTsHarComments = true;
287    }
288  }
289
290  public get customProfiles(): IOptions {
291    return this.mCustomProfiles;
292  }
293
294  public static get isKeptCurrentFile(): boolean {
295    return ArkObfuscator.mIsKeptCurrentFile;
296  }
297
298  public static set isKeptCurrentFile(isKeptFile: boolean) {
299    ArkObfuscator.mIsKeptCurrentFile = isKeptFile;
300  }
301
302  public static get projectInfo(): ProjectInfo {
303    return ArkObfuscator.mProjectInfo;
304  }
305
306  public static set projectInfo(projectInfo: ProjectInfo) {
307    ArkObfuscator.mProjectInfo = projectInfo;
308  }
309
310  public static recordStage(stage: string): RecordInfo | null {
311    if (ArkObfuscator.memoryDottingCallback) {
312      return ArkObfuscator.memoryDottingCallback(stage);
313    }
314    return null;
315  }
316
317  public static stopRecordStage(recordInfo: RecordInfo | null): void {
318    if (ArkObfuscator.memoryDottingStopCallback) {
319      if (recordInfo !== null) {
320        ArkObfuscator.memoryDottingStopCallback(recordInfo);
321      }
322    }
323  }
324
325  public isCurrentFileInKeepPathsForTest(customProfiles: IOptions, originalFilePath: string): boolean {
326    return this.isCurrentFileInKeepPaths(customProfiles, originalFilePath);
327  }
328
329  private isCurrentFileInKeepPaths(customProfiles: IOptions, originalFilePath: string): boolean {
330    const keepFileSourceCode = customProfiles.mKeepFileSourceCode;
331    if (keepFileSourceCode === undefined || keepFileSourceCode.mKeepSourceOfPaths.size === 0) {
332      return false;
333    }
334    const keepPaths: Set<string> = keepFileSourceCode.mKeepSourceOfPaths;
335    const originalPath = FileUtils.toUnixPath(originalFilePath);
336    return keepPaths.has(originalPath);
337  }
338
339  /**
340   * init ArkObfuscator according to user config
341   * should be called after constructor
342   */
343  public init(config: IOptions | undefined, cachePath?: string): boolean {
344    if (!config) {
345      console.error('obfuscation config file is not found and no given config.');
346      return false;
347    }
348
349    handleReservedConfig(config, 'mRenameFileName', 'mReservedFileNames', 'mUniversalReservedFileNames');
350    handleReservedConfig(config, 'mRemoveDeclarationComments', 'mReservedComments', 'mUniversalReservedComments', 'mEnable');
351    this.mCustomProfiles = config;
352
353    if (this.mCustomProfiles.mCompact) {
354      this.mTextWriter = createObfTextSingleLineWriter();
355    } else {
356      this.mTextWriter = createTextWriter('\n');
357    }
358
359    if (this.mCustomProfiles.mEnableSourceMap) {
360      this.mCompilerOptions.sourceMap = true;
361    }
362
363    if (this.mCustomProfiles.mAllowEtsAnnotations) {
364      this.mCompilerOptions.etsAnnotationsEnable = true;
365    }
366
367    const enableTopLevel: boolean = this.mCustomProfiles.mNameObfuscation?.mTopLevel;
368    const exportObfuscation: boolean = this.mCustomProfiles.mExportObfuscation;
369    const propertyObfuscation: boolean = this.mCustomProfiles.mNameObfuscation?.mRenameProperties;
370    /**
371     * clean mangledNames in case skip name check when generating names
372     */
373    cleanFileMangledNames = enableTopLevel && !exportObfuscation && !propertyObfuscation;
374
375    // load transformers
376    this.mTransformers = new TransformerManager(this.mCustomProfiles).getTransformers();
377
378    initPerformancePrinter(this.mCustomProfiles);
379
380    initPerformanceTimeAndMemPrinter();
381    if (needReadApiInfo(this.mCustomProfiles)) {
382      // if -enable-property-obfuscation or -enable-export-obfuscation, collect language reserved keywords.
383      let languageSet: Set<string> = new Set();
384      let presetLanguageWhitelist = this.mCustomProfiles.mStripLanguageDefaultWhitelist ? optimizeEsInfo : esInfo;
385      for (const key of Object.keys(presetLanguageWhitelist)) {
386        const valueArray = presetLanguageWhitelist[key];
387        valueArray.forEach((element: string) => {
388          languageSet.add(element);
389        });
390      }
391      UnobfuscationCollections.reservedLangForProperty = languageSet;
392    }
393
394    if (cachePath) {
395      this.initIncrementalCache(cachePath, !!this.mCustomProfiles.mNameObfuscation.mEnableAtKeep);
396    }
397
398    return true;
399  }
400
401  public static setMemoryDottingCallBack(memoryDottingCallback: (stage: string) => RecordInfo,
402    memoryDottingStopCallback: (recordInfo: RecordInfo) => void): void {
403    if (memoryDottingCallback) {
404      ArkObfuscator.memoryDottingCallback = memoryDottingCallback;
405    }
406    if (memoryDottingStopCallback) {
407      ArkObfuscator.memoryDottingStopCallback = memoryDottingStopCallback;
408    }
409  }
410
411  public static clearMemoryDottingCallBack(): void {
412    ArkObfuscator.memoryDottingCallback = undefined;
413    ArkObfuscator.memoryDottingStopCallback = undefined;
414  }
415
416  /**
417   * Init incremental cache according to cachePath
418   */
419  private initIncrementalCache(cachePath: string, enableAtKeep: boolean): void {
420    this.filePathManager = new FilePathManager(cachePath);
421
422    this.isIncremental = this.filePathManager.isIncremental();
423
424    this.fileContentManager = new FileContentManager(cachePath, this.isIncremental);
425
426    initProjectWhiteListManager(cachePath, this.isIncremental, enableAtKeep);
427  }
428
429  /**
430   * A Printer to output obfuscated codes.
431   */
432  public createObfsPrinter(isDeclarationFile: boolean): Printer {
433    // set print options
434    let printerOptions: PrinterOptions = {};
435    let removeOption = this.mCustomProfiles.mRemoveDeclarationComments;
436    let hasReservedList = removeOption?.mReservedComments?.length || removeOption?.mUniversalReservedComments?.length;
437    let keepDeclarationComments = hasReservedList || !removeOption?.mEnable;
438
439    if (isDeclarationFile && keepDeclarationComments) {
440      printerOptions.removeComments = false;
441    }
442    if ((!isDeclarationFile && this.mCustomProfiles.mRemoveComments) || (isDeclarationFile && !keepDeclarationComments)) {
443      printerOptions.removeComments = true;
444    }
445
446    return createPrinter(printerOptions);
447  }
448
449  protected isObfsIgnoreFile(fileName: string): boolean {
450    let suffix: string = FileUtils.getFileExtension(fileName);
451
452    return suffix !== 'js' && suffix !== 'ts' && suffix !== 'ets';
453  }
454
455  private convertLineBasedOnSourceMap(targetCache: string, sourceMapLink?: SourceMapLink): Map<string, string> {
456    let originalCache: Map<string, string> = renameIdentifierModule.nameCache.get(targetCache);
457    let updatedCache: Map<string, string> = new Map<string, string>();
458    for (const [key, value] of originalCache) {
459      if (!key.includes(':')) {
460        // No need to save line info for identifier which is not function-like, i.e. key without ':' here.
461        updatedCache[key] = value;
462        continue;
463      }
464      const [scopeName, oldStartLine, oldStartColumn, oldEndLine, oldEndColumn] = key.split(':');
465      let newKey: string = key;
466      if (!sourceMapLink) {
467        // In Arkguard, we save line info of source code, so do not need to use sourcemap mapping.
468        newKey = `${scopeName}:${oldStartLine}:${oldEndLine}`;
469        updatedCache[newKey] = value;
470        continue;
471      }
472
473      const parts = scopeName.split('#');
474      // 1: Get the last word 'zz' in '#xx#yy#zz'.
475      const lastScopeName: string = parts[parts.length - 1];
476
477      const startPosition: SourceMapSegmentObj | null = sourceMapLink.traceSegment(
478        // 1: The line number in originalCache starts from 1 while in source map starts from 0.
479        Number(oldStartLine) - 1, Number(oldStartColumn) - 1, ''); // Minus 1 to get the correct original position.
480      if (!startPosition && lastScopeName === value) {
481        // Do not save methods that do not exist in the source code and not be obfuscated, e.g. 'build' in ArkUI.
482        continue;
483      }
484      const endPosition: SourceMapSegmentObj | null = sourceMapLink.traceSegment(
485        Number(oldEndLine) - 1, Number(oldEndColumn) - 1, ''); // 1: Same as above.
486      if (!endPosition && lastScopeName === value) {
487        // Do not save methods that do not exist in the source code and not be obfuscated, e.g. 'build' in ArkUI.
488        continue;
489      }
490
491      if (!startPosition || !endPosition) {
492        updatedCache[scopeName] = value;
493      } else {
494        const startLine = startPosition.line + 1; // 1: The final line number in updatedCache should starts from 1.
495        const endLine = endPosition.line + 1; // 1: Same as above.
496        newKey = `${scopeName}:${startLine}:${endLine}`;
497        updatedCache[newKey] = value;
498      }
499    }
500    return updatedCache;
501  }
502
503  public convertLineBasedOnSourceMapForTest(
504    targetCache: string,
505    sourceMapLink?: SourceMapLink
506  ): Map<string, string> {
507    return this.convertLineBasedOnSourceMap(targetCache, sourceMapLink);
508  }
509
510  /**
511   * Obfuscate ast of a file.
512   * @param content ast or source code of a source file
513   * @param sourceFilePathObj
514   * @param previousStageSourceMap
515   * @param historyNameCache
516   * @param originalFilePath When filename obfuscation is enabled, it is used as the source code path.
517   */
518  public async obfuscate(
519    content: SourceFile | string,
520    sourceFilePathObj: FilePathObj,
521    previousStageSourceMap?: RawSourceMap,
522    historyNameCache?: Map<string, string>,
523    originalFilePath?: string,
524    projectInfo?: ProjectInfo,
525  ): Promise<ObfuscationResultType> {
526    startFilesEvent(EventList.CONFIG_INITIALIZATION);
527    ArkObfuscator.projectInfo = projectInfo;
528    let result: ObfuscationResultType = { content: undefined };
529    if (this.isObfsIgnoreFile(sourceFilePathObj.buildFilePath)) {
530      // need add return value
531      endFilesEvent(EventList.CONFIG_INITIALIZATION);
532      return result;
533    }
534
535    let ast: SourceFile = this.createAst(content, sourceFilePathObj.buildFilePath);
536    if (ast.statements.length === 0) {
537      endFilesEvent(EventList.CONFIG_INITIALIZATION);
538      return result;
539    }
540
541    if (historyNameCache && historyNameCache.size > 0 && this.mCustomProfiles.mNameObfuscation) {
542      renameIdentifierModule.historyNameCache = historyNameCache;
543    }
544
545    if (this.mCustomProfiles.mUnobfuscationOption?.mPrintKeptNames) {
546      let historyUnobfuscatedNames = historyAllUnobfuscatedNamesMap.get(sourceFilePathObj.relativeFilePath);
547      if (historyUnobfuscatedNames) {
548        renameIdentifierModule.historyUnobfuscatedNamesMap = new Map(Object.entries(historyUnobfuscatedNames));
549      }
550    }
551
552    originalFilePath = originalFilePath ?? ast.fileName;
553    if (this.mCustomProfiles.mRenameFileName?.mEnable) {
554      orignalFilePathForSearching = originalFilePath;
555    }
556    ArkObfuscator.isKeptCurrentFile = this.isCurrentFileInKeepPaths(this.mCustomProfiles, originalFilePath);
557    endFilesEvent(EventList.CONFIG_INITIALIZATION);
558
559    this.handleDeclarationFile(ast);
560
561    ast = this.obfuscateAst(ast);
562
563    startSingleFileEvent(EventList.WRITE_OBFUSCATION_RESULT);
564    this.writeObfuscationResult(ast, sourceFilePathObj.buildFilePath, result, previousStageSourceMap, originalFilePath);
565    endSingleFileEvent(EventList.WRITE_OBFUSCATION_RESULT);
566
567    this.clearCaches();
568    return result;
569  }
570
571  private createAst(content: SourceFile | string, sourceFilePath: string): SourceFile {
572    const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.CREATE_AST);
573    startSingleFileEvent(EventList.CREATE_AST, performancePrinter.timeSumPrinter);
574    let ast: SourceFile;
575    if (typeof content === 'string') {
576      ast = TypeUtils.createObfSourceFile(sourceFilePath, content, this.mCompilerOptions);
577    } else {
578      ast = content;
579    }
580    endSingleFileEvent(EventList.CREATE_AST, performancePrinter.timeSumPrinter);
581    ArkObfuscator.stopRecordStage(recordInfo);
582
583    return ast;
584  }
585
586  private obfuscateAst(ast: SourceFile): SourceFile {
587    const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.OBFUSCATE_AST);
588    startSingleFileEvent(EventList.OBFUSCATE_AST, performancePrinter.timeSumPrinter);
589    let transformedResult: TransformationResult<Node> = transform(ast, this.mTransformers, this.mCompilerOptions);
590    endSingleFileEvent(EventList.OBFUSCATE_AST, performancePrinter.timeSumPrinter);
591    ArkObfuscator.stopRecordStage(recordInfo);
592    ast = transformedResult.transformed[0] as SourceFile;
593    return ast;
594  }
595
596  private handleDeclarationFile(ast: SourceFile): void {
597    if (ast.isDeclarationFile) {
598      if (!this.mCustomProfiles.mRemoveDeclarationComments || !this.mCustomProfiles.mRemoveDeclarationComments.mEnable) {
599        //@ts-ignore
600        ast.reservedComments = undefined;
601        //@ts-ignore
602        ast.universalReservedComments = undefined;
603      } else {
604        //@ts-ignore
605        ast.reservedComments ??= this.mCustomProfiles.mRemoveDeclarationComments.mReservedComments ?
606          this.mCustomProfiles.mRemoveDeclarationComments.mReservedComments : [];
607        //@ts-ignore
608        ast.universalReservedComments = this.mCustomProfiles.mRemoveDeclarationComments.mUniversalReservedComments ?? [];
609      }
610    } else {
611      //@ts-ignore
612      ast.reservedComments = this.mCustomProfiles.mRemoveComments ? [] : undefined;
613      //@ts-ignore
614      ast.universalReservedComments = this.mCustomProfiles.mRemoveComments ? [] : undefined;
615    }
616  }
617
618  /**
619   * write obfuscated code, sourcemap and namecache
620   */
621  private writeObfuscationResult(ast: SourceFile, sourceFilePath: string, result: ObfuscationResultType,
622    previousStageSourceMap?: RawSourceMap, originalFilePath?: string): void {
623    // convert ast to output source file and generate sourcemap if needed.
624    let sourceMapGenerator: SourceMapGenerator = undefined;
625    if (this.mCustomProfiles.mEnableSourceMap) {
626      startSingleFileEvent(EventList.GET_SOURCEMAP_GENERATOR, performancePrinter.timeSumPrinter);
627      sourceMapGenerator = getSourceMapGenerator(sourceFilePath);
628      endSingleFileEvent(EventList.GET_SOURCEMAP_GENERATOR, performancePrinter.timeSumPrinter);
629    }
630
631    if (sourceFilePath.endsWith('.js')) {
632      TypeUtils.tsToJs(ast);
633    }
634    this.handleTsHarComments(ast, originalFilePath);
635    const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.CREATE_PRINTER);
636    startSingleFileEvent(EventList.CREATE_PRINTER, performancePrinter.timeSumPrinter);
637    this.createObfsPrinter(ast.isDeclarationFile).writeFile(ast, this.mTextWriter, sourceMapGenerator);
638    endSingleFileEvent(EventList.CREATE_PRINTER, performancePrinter.timeSumPrinter);
639    ArkObfuscator.stopRecordStage(recordInfo);
640
641    startSingleFileEvent(EventList.GET_OBFUSCATED_CODE);
642    result.filePath = ast.fileName;
643    result.content = this.mTextWriter.getText();
644    endSingleFileEvent(EventList.GET_OBFUSCATED_CODE);
645
646    if (this.mCustomProfiles.mUnobfuscationOption?.mPrintKeptNames) {
647      this.handleUnobfuscationNames(result);
648    }
649
650    startSingleFileEvent(EventList.PROCESS_SOURCEMAP);
651    if (this.mCustomProfiles.mEnableSourceMap && sourceMapGenerator) {
652      this.handleSourceMapAndNameCache(sourceMapGenerator, sourceFilePath, result, previousStageSourceMap);
653    }
654    endSingleFileEvent(EventList.PROCESS_SOURCEMAP);
655  }
656
657  private handleUnobfuscationNames(result: ObfuscationResultType): void {
658    result.unobfuscationNameMap = new Map(UnobfuscationCollections.unobfuscatedNamesMap);
659  }
660
661  private handleSourceMapAndNameCache(sourceMapGenerator: SourceMapGenerator, sourceFilePath: string,
662    result: ObfuscationResultType, previousStageSourceMap?: RawSourceMap): void {
663    startSingleFileEvent(EventList.SOURCEMAP_MERGE, performancePrinter.timeSumPrinter);
664    let sourceMapJson: RawSourceMap = sourceMapGenerator.toJSON();
665    sourceMapJson.sourceRoot = '';
666    sourceMapJson.file = path.basename(sourceFilePath);
667    if (previousStageSourceMap) {
668      sourceMapJson = mergeSourceMap(previousStageSourceMap as RawSourceMap, sourceMapJson);
669    }
670    result.sourceMap = sourceMapJson;
671    endSingleFileEvent(EventList.SOURCEMAP_MERGE, performancePrinter.timeSumPrinter);
672    startSingleFileEvent(EventList.CREATE_NAMECACHE, performancePrinter.timeSumPrinter);
673    let nameCache = renameIdentifierModule.nameCache;
674    if (this.mCustomProfiles.mEnableNameCache) {
675      let newIdentifierCache!: Object;
676      let newMemberMethodCache!: Object;
677      if (previousStageSourceMap) {
678        // The process in sdk, need to use sourcemap mapping.
679        // 1: Only one file in the source map; 0: The first and the only one.
680        const sourceFileName = previousStageSourceMap.sources?.length === 1 ? previousStageSourceMap.sources[0] : '';
681        const source: Source = new Source(sourceFileName, null);
682        const decodedSourceMap: ExistingDecodedSourceMap = decodeSourcemap(previousStageSourceMap);
683        let sourceMapLink: SourceMapLink = new SourceMapLink(decodedSourceMap, [source]);
684        newIdentifierCache = this.convertLineBasedOnSourceMap(IDENTIFIER_CACHE, sourceMapLink);
685        newMemberMethodCache = this.convertLineBasedOnSourceMap(MEM_METHOD_CACHE, sourceMapLink);
686      } else {
687        // The process in Arkguard.
688        newIdentifierCache = this.convertLineBasedOnSourceMap(IDENTIFIER_CACHE);
689        newMemberMethodCache = this.convertLineBasedOnSourceMap(MEM_METHOD_CACHE);
690      }
691      nameCache.set(IDENTIFIER_CACHE, newIdentifierCache);
692      nameCache.set(MEM_METHOD_CACHE, newMemberMethodCache);
693      result.nameCache = { [IDENTIFIER_CACHE]: newIdentifierCache, [MEM_METHOD_CACHE]: newMemberMethodCache };
694    }
695    endSingleFileEvent(EventList.CREATE_NAMECACHE, performancePrinter.timeSumPrinter);
696  }
697
698  private clearCaches(): void {
699    // clear cache of text writer
700    this.mTextWriter.clear();
701    renameIdentifierModule.clearCaches();
702    if (cleanFileMangledNames) {
703      PropCollections.globalMangledTable.clear();
704    }
705    UnobfuscationCollections.unobfuscatedNamesMap.clear();
706    nodeSymbolMap.clear();
707  }
708}
709