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  SyntaxKind,
18  factory,
19  forEachChild,
20  isBreakOrContinueStatement,
21  isConstructorDeclaration,
22  isExportSpecifier,
23  isIdentifier,
24  isImportSpecifier,
25  isLabeledStatement,
26  isMetaProperty,
27  isSourceFile,
28  isStructDeclaration,
29  setParentRecursive,
30  visitEachChild,
31  isPropertyDeclaration,
32  isMethodDeclaration,
33  isGetAccessor,
34  isSetAccessor,
35  isClassDeclaration,
36  isFunctionExpression,
37  isArrowFunction,
38  isVariableDeclaration,
39  isPropertyAssignment,
40  isPrivateIdentifier
41} from 'typescript';
42
43import type {
44  ClassElement,
45  Identifier,
46  Node,
47  SourceFile,
48  StructDeclaration,
49  Symbol,
50  TransformationContext,
51  Transformer,
52  TransformerFactory,
53  TypeChecker
54} from 'typescript';
55
56import {
57  createScopeManager,
58  isClassScope,
59  isGlobalScope,
60  isEnumScope,
61  isInterfaceScope,
62  isObjectLiteralScope,
63  noSymbolIdentifier,
64} from '../../utils/ScopeAnalyzer';
65
66import type {
67  Label,
68  Scope,
69  ScopeManager
70} from '../../utils/ScopeAnalyzer';
71
72import {
73  IDENTIFIER_CACHE,
74  MEM_METHOD_CACHE
75} from '../../utils/NameCacheUtil';
76
77import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator';
78import type {IOptions} from '../../configs/IOptions';
79import type { INameObfuscationOption, IUnobfuscationOption } from '../../configs/INameObfuscationOption';
80import type {TransformPlugin} from '../TransformPlugin';
81import type { MangledSymbolInfo } from '../../common/type';
82import {TransformerOrder} from '../TransformPlugin';
83import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory';
84import {TypeUtils} from '../../utils/TypeUtils';
85import {
86  isInTopLevelWhitelist,
87  isInLocalWhitelist,
88  isReservedLocalVariable,
89  isReservedTopLevel,
90  recordHistoryUnobfuscatedNames
91} from '../../utils/TransformUtil';
92import {NodeUtils} from '../../utils/NodeUtils';
93import {ApiExtractor} from '../../common/ApiExtractor';
94import {performancePrinter, ArkObfuscator, cleanFileMangledNames} from '../../ArkObfuscator';
95import { EventList } from '../../utils/PrinterUtils';
96import { isViewPUBasedClass } from '../../utils/OhsUtil';
97import {
98  PropCollections,
99  UnobfuscationCollections,
100  LocalVariableCollections
101} from '../../utils/CommonCollections';
102
103namespace secharmony {
104  /**
105   * Rename Identifiers, including:
106   * 1. variable name
107   * 2. function name
108   * 3. label name
109   * 4. class name/interface name/ label name
110   * we need implement some features:
111   * 1. rename identifiers
112   * 2. store/restore name to/from nameCache file.
113   * 3. do scope analysis for identifier obfuscations
114   *
115   * @param option
116   */
117  const createRenameIdentifierFactory = function (option: IOptions): TransformerFactory<Node> {
118    const profile: INameObfuscationOption | undefined = option?.mNameObfuscation;
119    const unobfuscationOption: IUnobfuscationOption | undefined = option?.mUnobfuscationOption;
120    if (!profile || !profile.mEnable) {
121      return null;
122    }
123
124    let options: NameGeneratorOptions = {};
125    let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options);
126
127    const openTopLevel: boolean = option?.mNameObfuscation?.mTopLevel;
128    const exportObfuscation: boolean = option?.mExportObfuscation;
129    let isInitializedReservedList = false;
130    return renameIdentifierFactory;
131
132    function renameIdentifierFactory(context: TransformationContext): Transformer<Node> {
133      initWhitelist();
134      let mangledSymbolNames: Map<Symbol, MangledSymbolInfo> = new Map<Symbol, MangledSymbolInfo>();
135      let mangledLabelNames: Map<Label, string> = new Map<Label, string>();
136      let fileExportNames: Set<string> = undefined;
137      let fileImportNames: Set<string> = undefined;
138      noSymbolIdentifier.clear();
139
140      let historyMangledNames: Set<string> = undefined;
141      if (historyNameCache && historyNameCache.size > 0) {
142        historyMangledNames = new Set<string>(Array.from(historyNameCache.values()));
143      }
144
145      let checker: TypeChecker = undefined;
146      let manager: ScopeManager = createScopeManager();
147
148      return renameTransformer;
149
150      /**
151       * Transformer to rename identifiers
152       *
153       * @param node ast node of a file.
154       */
155      function renameTransformer(node: Node): Node {
156        if (nameCache.size === 0) {
157          nameCache.set(IDENTIFIER_CACHE, new Map<string, string>());
158          nameCache.set(MEM_METHOD_CACHE, new Map<string, string>());
159        }
160
161        if (!isSourceFile(node) || ArkObfuscator.isKeptCurrentFile) {
162          return node;
163        }
164
165        performancePrinter?.singleFilePrinter?.startEvent(EventList.CREATE_CHECKER, performancePrinter.timeSumPrinter);
166        checker = TypeUtils.createChecker(node);
167        performancePrinter?.singleFilePrinter?.endEvent(EventList.CREATE_CHECKER, performancePrinter.timeSumPrinter);
168
169        performancePrinter?.singleFilePrinter?.startEvent(EventList.SCOPE_ANALYZE, performancePrinter.timeSumPrinter);
170        manager.analyze(node, checker, exportObfuscation);
171        performancePrinter?.singleFilePrinter?.endEvent(EventList.SCOPE_ANALYZE, performancePrinter.timeSumPrinter);
172
173        let root: Scope = manager.getRootScope();
174        fileExportNames = root.fileExportNames;
175        fileImportNames = root.fileImportNames;
176
177        performancePrinter?.singleFilePrinter?.startEvent(EventList.CREATE_OBFUSCATED_NAMES, performancePrinter.timeSumPrinter);
178        renameInScope(root);
179        performancePrinter?.singleFilePrinter?.endEvent(EventList.CREATE_OBFUSCATED_NAMES, performancePrinter.timeSumPrinter);
180
181        root = undefined;
182
183        performancePrinter?.singleFilePrinter?.startEvent(EventList.OBFUSCATE_NODES, performancePrinter.timeSumPrinter);
184        let ret: Node = visit(node);
185
186        let parentNodes = setParentRecursive(ret, true);
187        performancePrinter?.singleFilePrinter?.endEvent(EventList.OBFUSCATE_NODES, performancePrinter.timeSumPrinter);
188        return parentNodes;
189      }
190
191      /**
192       * rename symbol table store in scopes...
193       *
194       * @param scope scope, such as global, module, function, block
195       */
196      function renameInScope(scope: Scope): void {
197        // process labels in scope, the label can't rename as the name of top labels.
198        renameLabelsInScope(scope);
199        // process symbols in scope, exclude property name.
200        renameNamesInScope(scope);
201
202        let subScope = undefined;
203        while (scope.children.length > 0) {
204          subScope = scope.children.pop();
205          renameInScope(subScope);
206          subScope = undefined;
207        }
208      }
209
210      function renameNamesInScope(scope: Scope): void {
211        if (isExcludeScope(scope)) {
212          return;
213        }
214
215        if (!exportObfuscation) {
216          scope.defs.forEach((def) => {
217            let parentScope = scope;
218            while (parentScope) {
219              if (parentScope.importNames && parentScope.importNames.has(def.name)) {
220                scope.defs.delete(def);
221                scope.mangledNames.add(def.name);
222              }
223              parentScope = parentScope.parent;
224            }
225          });
226        }
227
228        renames(scope, scope.defs, generator);
229      }
230
231      function renames(scope: Scope, defs: Set<Symbol>, generator: INameGenerator): void {
232        defs.forEach((def) => {
233          const original: string = def.name;
234          let mangled: string = original;
235          const path: string = scope.loc + '#' + original;
236          // No allow to rename reserved names.
237          if (!Reflect.has(def, 'obfuscateAsProperty') &&
238            isInLocalWhitelist(original, UnobfuscationCollections.unobfuscatedNamesMap, path) ||
239            (!exportObfuscation && scope.exportNames.has(def.name)) ||
240            isSkippedGlobal(openTopLevel, scope)) {
241            scope.mangledNames.add(mangled);
242            mangledSymbolNames.set(def, {mangledName: mangled, originalNameWithScope: path});
243            return;
244          }
245
246          if (mangledSymbolNames.has(def)) {
247            return;
248          }
249
250          const historyName: string = historyNameCache?.get(path);
251          if (historyName) {
252            recordHistoryUnobfuscatedNames(path); // For incremental build
253            mangled = historyName;
254          } else if (Reflect.has(def, 'obfuscateAsProperty')) {
255            // obfuscate toplevel, export
256            mangled = getPropertyMangledName(original, path);
257          } else {
258            // obfuscate local variable
259            mangled = getMangled(scope, generator);
260          }
261          // add new names to name cache
262          let identifierCache = nameCache?.get(IDENTIFIER_CACHE);
263          (identifierCache as Map<string, string>).set(path, mangled);
264          let symbolInfo: MangledSymbolInfo = {
265            mangledName: mangled,
266            originalNameWithScope: path
267          };
268          scope.mangledNames.add(mangled);
269          mangledSymbolNames.set(def, symbolInfo);
270        });
271      }
272
273      function getPropertyMangledName(original: string, nameWithScope: string): string {
274        if (isInTopLevelWhitelist(original, UnobfuscationCollections.unobfuscatedNamesMap, nameWithScope)) {
275          return original;
276        }
277
278        const historyName: string = PropCollections.historyMangledTable?.get(original);
279        let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(original);
280        while (!mangledName) {
281          let tmpName = generator.getName();
282          if (isReservedTopLevel(tmpName) ||
283            tmpName === original) {
284            continue;
285          }
286
287          /**
288           * In case b is obfuscated as a when only enable toplevel obfuscation:
289           * let b = 1;
290           * export let a = 1;
291           */
292          if (cleanFileMangledNames && fileExportNames && fileExportNames.has(tmpName)) {
293            continue;
294          }
295
296          /**
297           * In case b is obfuscated as a when only enable toplevel obfuscation:
298           * import {a} from 'filePath';
299           * let b = 1;
300           */
301          if (cleanFileMangledNames && fileImportNames.has(tmpName)) {
302            continue;
303          }
304
305          /**
306           * In case a newly added variable get an obfuscated name that is already in history namecache
307           */
308          if (historyMangledNames && historyMangledNames.has(tmpName)) {
309            continue;
310          }
311
312          if (PropCollections.newlyOccupiedMangledProps.has(tmpName) || PropCollections.mangledPropsInNameCache.has(tmpName)) {
313            continue;
314          }
315
316          if (ApiExtractor.mConstructorPropertySet?.has(tmpName)) {
317            continue;
318          }
319
320          mangledName = tmpName;
321        }
322
323        PropCollections.globalMangledTable.set(original, mangledName);
324        PropCollections.newlyOccupiedMangledProps.add(mangledName);
325        return mangledName;
326      }
327
328      function isExcludeScope(scope: Scope): boolean {
329        if (isClassScope(scope)) {
330          return true;
331        }
332
333        if (isInterfaceScope(scope)) {
334          return true;
335        }
336
337        if (isEnumScope(scope)) {
338          return true;
339        }
340
341        return isObjectLiteralScope(scope);
342      }
343
344      function searchMangledInParent(scope: Scope, name: string): boolean {
345        let found: boolean = false;
346        let parentScope = scope;
347        while (parentScope) {
348          if (parentScope.mangledNames.has(name)) {
349            found = true;
350            break;
351          }
352
353          parentScope = parentScope.parent;
354        }
355
356        return found;
357      }
358
359      function getMangled(scope: Scope, localGenerator: INameGenerator): string {
360        let mangled: string = '';
361        do {
362          mangled = localGenerator.getName()!;
363          // if it is a globally reserved name, it needs to be regenerated
364          if (isReservedLocalVariable(mangled)) {
365            mangled = '';
366            continue;
367          }
368
369          if (fileExportNames && fileExportNames.has(mangled)) {
370            mangled = '';
371            continue;
372          }
373
374          if (historyMangledNames && historyMangledNames.has(mangled)) {
375            mangled = '';
376            continue;
377          }
378
379          if (searchMangledInParent(scope, mangled)) {
380            mangled = '';
381            continue;
382          }
383
384          if (ApiExtractor.mConstructorPropertySet?.has(mangled)) {
385            mangled = '';
386          }
387        } while (mangled === '');
388
389        return mangled;
390      }
391
392      function renameLabelsInScope(scope: Scope): void {
393        const labels: Label[] = scope.labels;
394        if (labels.length > 0) {
395          let upperMangledLabels = getUpperMangledLabelNames(labels[0]);
396          for (const label of labels) {
397            let mangledLabel = getMangledLabel(label, upperMangledLabels);
398            mangledLabelNames.set(label, mangledLabel);
399          }
400        }
401      }
402
403      function getMangledLabel(label: Label, mangledLabels: string[]): string {
404        let mangledLabel: string = '';
405        do {
406          mangledLabel = generator.getName();
407          if (mangledLabel === label.name) {
408            mangledLabel = '';
409          }
410
411          if (mangledLabels.includes(mangledLabel)) {
412            mangledLabel = '';
413          }
414        } while (mangledLabel === '');
415
416        return mangledLabel;
417      }
418
419      function getUpperMangledLabelNames(label: Label): string[] {
420        const results: string[] = [];
421        let parent: Label = label.parent;
422        while (parent) {
423          let mangledLabelName: string = mangledLabelNames.get(parent);
424          if (mangledLabelName) {
425            results.push(mangledLabelName);
426          }
427          parent = parent.parent;
428        }
429
430        return results;
431      }
432
433      function isFunctionLike(node: Node): boolean {
434        switch (node.kind) {
435          case SyntaxKind.FunctionDeclaration:
436          case SyntaxKind.MethodDeclaration:
437          case SyntaxKind.GetAccessor:
438          case SyntaxKind.SetAccessor:
439          case SyntaxKind.Constructor:
440          case SyntaxKind.FunctionExpression:
441          case SyntaxKind.ArrowFunction:
442            return true;
443        }
444        return false;
445      }
446
447      function nodeHasFunctionLikeChild(node: Node): boolean {
448        let hasFunctionLikeChild: boolean = false;
449        let childVisitor: (child: Node) => Node = (child: Node): Node => {
450          if (!hasFunctionLikeChild && child && isFunctionLike(child)) {
451            hasFunctionLikeChild = true;
452          }
453          return child;
454        };
455        visitEachChild(node, childVisitor, context);
456        return hasFunctionLikeChild;
457      }
458
459      /**
460       * visit each node to change identifier name to mangled name
461       *  - calculate shadow name index to find shadow node
462       * @param node
463       */
464      function visit(node: Node): Node {
465        let needHandlePositionInfo: boolean = isFunctionLike(node) || nodeHasFunctionLikeChild(node);
466        if (needHandlePositionInfo) {
467          // Obtain line info for nameCache.
468          handlePositionInfo(node);
469        }
470
471        if (!isIdentifier(node) || !node.parent) {
472          return visitEachChild(node, visit, context);
473        }
474
475        if (isLabeledStatement(node.parent) || isBreakOrContinueStatement(node.parent)) {
476          return updateLabelNode(node);
477        }
478
479        return updateNameNode(node);
480      }
481
482      function handlePositionInfo(node: Node): void {
483        const sourceFile = NodeUtils.getSourceFileOfNode(node);
484        if (node && node.pos < 0 && node.end < 0) {
485          // Node must have a real position for following operations.
486          // Adapting to the situation that the node does not have a real postion.
487          return;
488        }
489        const startPosition = sourceFile.getLineAndCharacterOfPosition(node.getStart());
490        const endPosition = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
491        // 1: The line number in sourceFile starts from 0 while in IDE starts from 1.
492        const startLine = startPosition.line + 1;
493        const startCharacter = startPosition.character + 1; // 1: Same as above.
494        const endLine = endPosition.line + 1; // 1: Same as above.
495        const endCharacter = endPosition.character + 1; // 1: Same as above.
496        const lineAndColum: string = ':' + startLine + ':' + startCharacter + ':' + endLine + ':' + endCharacter;
497
498        let isProperty: boolean = isMethodDeclaration(node) || isGetAccessor(node) ||
499                                  isSetAccessor(node) || (isConstructorDeclaration(node) &&
500                                  !(isClassDeclaration(node.parent) && isViewPUBasedClass(node.parent)));
501        // Arrow functions are anoymous, only function expressions are considered.
502        let isPropertyParent: boolean = isFunctionExpression(node) &&
503                                        (isPropertyDeclaration(node.parent) || isPropertyAssignment(node.parent));
504        let isMemberMethod: boolean = isProperty || isPropertyParent;
505        if (isMemberMethod) {
506          writeMemberMethodCache(node, lineAndColum);
507          return;
508        }
509
510        let name = Reflect.get(node, 'name') as Identifier;
511        if (name?.kind === SyntaxKind.Identifier) {
512          identifierLineMap.set(name, lineAndColum);
513        } else if ((isFunctionExpression(node) || isArrowFunction(node)) && isVariableDeclaration(node.parent) &&
514          node.parent.name?.kind === SyntaxKind.Identifier) {
515          // The node is anonymous, and we need to find its parent node.
516          // e.g.: let foo = function() {};
517          identifierLineMap.set(node.parent.name, lineAndColum);
518        }
519      }
520
521      function writeMemberMethodCache(node: Node, lineAndColum: string): void {
522        let gotNode;
523        if (node.kind === SyntaxKind.Constructor) {
524          gotNode = node.parent;
525        } else if ((node.kind === SyntaxKind.FunctionExpression &&
526          (isPropertyDeclaration(node.parent) || isPropertyAssignment(node.parent)))) {
527          gotNode = node.parent.initializer ?? node.parent;
528        } else {
529          gotNode = node;
530        }
531
532        let isIdentifierNode: boolean = gotNode.name && (isIdentifier(gotNode.name) || isPrivateIdentifier(gotNode.name));
533        let valueName: string = '';
534
535        if (isIdentifierNode) {
536          // The original method for retrieving method names used gotNode.name.escapedText. This approach limited the collection
537          // of method records in MemberMethodCache to cases where gotNode.name was an Identifier or PrivateIdentifier.
538          // To address the issue where method names starting with double underscores were transformed to start with triple underscores,
539          // we changed the retrieval method to use gotNode.name.text instead of escapedText. However, this change introduced the possibility
540          // of collecting method records when gotNode.name is a NumericLiteral or StringLiteral, which is not desired.
541          // To avoid altering the collection specifications of MemberMethodCache, we restricted the collection scenarios 
542          // to match the original cases where only identifiers and private identifiers are collected.
543          valueName = gotNode.name.text;
544        } 
545
546        if (valueName === '') {
547          return;
548        }
549
550        let originalName: string = valueName;
551        let keyName = originalName + lineAndColum;
552        if (node.kind === SyntaxKind.Constructor && classMangledName.has(gotNode.name)) {
553          valueName = classMangledName.get(gotNode.name);
554          classInfoInMemberMethodCache.add(keyName);
555        }
556        let memberMethodCache = nameCache?.get(MEM_METHOD_CACHE);
557        if (memberMethodCache) {
558          (memberMethodCache as Map<string, string>).set(keyName, valueName);
559        }
560      }
561
562      function updateNameNode(node: Identifier): Node {
563        // skip property in property access expression
564        if (NodeUtils.isPropertyAccessNode(node)) {
565          return node;
566        }
567
568        if (NodeUtils.isNewTargetNode(node)) {
569          return node;
570        }
571
572        let sym: Symbol | undefined = NodeUtils.findSymbolOfIdentifier(checker, node);
573        let mangledPropertyNameOfNoSymbolImportExport = '';
574        if (!sym) {
575          if (exportObfuscation && noSymbolIdentifier.has(node.text) && trySearchImportExportSpecifier(node)) {
576            mangledPropertyNameOfNoSymbolImportExport = mangleNoSymbolImportExportPropertyName(node.text);
577          } else {
578            return node;
579          }
580        }
581
582        // Add new names to name cache
583        const symbolInfo: MangledSymbolInfo = mangledSymbolNames.get(sym);
584        const identifierCache = nameCache?.get(IDENTIFIER_CACHE);
585        const lineAndColumn = identifierLineMap?.get(node);
586        // We only save the line info of FunctionLike.
587        const isFunction: boolean = sym ? Reflect.has(sym, 'isFunction') : false;
588        if (isFunction && symbolInfo && lineAndColumn) {
589          const originalName = symbolInfo.originalNameWithScope;
590          const pathWithLine: string = originalName + lineAndColumn;
591          (identifierCache as Map<string, string>).set(pathWithLine, symbolInfo.mangledName);
592          (identifierCache as Map<string, string>).delete(originalName);
593        }
594
595        let mangledName: string = mangledSymbolNames.get(sym)?.mangledName;
596        if (node?.parent.kind === SyntaxKind.ClassDeclaration) {
597          classMangledName.set(node, mangledName);
598        }
599        if (!mangledName && mangledPropertyNameOfNoSymbolImportExport !== '') {
600          mangledName = mangledPropertyNameOfNoSymbolImportExport;
601        }
602
603        if (!mangledName || mangledName === sym?.name) {
604          return node;
605        }
606
607        return factory.createIdentifier(mangledName);
608      }
609
610      function updateLabelNode(node: Identifier): Node {
611        let label: Label | undefined;
612        let labelName: string = '';
613
614        mangledLabelNames.forEach((value, key) => {
615          if (key.refs.includes(node)) {
616            label = key;
617            labelName = value;
618          }
619        });
620
621        return label ? factory.createIdentifier(labelName) : node;
622      }
623
624      /**
625       * import {A as B} from 'modulename';
626       * import {C as D} from 'modulename';
627       * above A、C have no symbol, so deal with them specially.
628       */
629      function mangleNoSymbolImportExportPropertyName(original: string): string {
630        const path: string = '#' + original;
631        const historyName: string = historyNameCache?.get(path);
632        let mangled = historyName ?? getPropertyMangledName(original, path);
633        if (nameCache && nameCache.get(IDENTIFIER_CACHE)) {
634          (nameCache.get(IDENTIFIER_CACHE) as Map<string, string>).set(path, mangled);
635        }
636        return mangled;
637      }
638
639      function trySearchImportExportSpecifier(node: Node): boolean {
640        while (node.parent) {
641          node = node.parent;
642          if ((isImportSpecifier(node) || isExportSpecifier(node)) && node.propertyName && isIdentifier(node.propertyName)) {
643            return true;
644          }
645        }
646        return false;
647      }
648    }
649
650    function initWhitelist(): void {
651      if (isInitializedReservedList) {
652        return;
653      }
654      if (profile?.mRenameProperties) {
655        PropCollections.enablePropertyObfuscation = true;
656        const tmpReservedProps: string[] = profile?.mReservedProperties ?? [];
657        tmpReservedProps.forEach(item => {
658          PropCollections.reservedProperties.add(item);
659        });
660        PropCollections.mangledPropsInNameCache = new Set(PropCollections.historyMangledTable?.values());
661        if (profile?.mUniversalReservedProperties) {
662          PropCollections.universalReservedProperties = [...profile.mUniversalReservedProperties];
663        }
664        UnobfuscationCollections.reservedLangForTopLevel.forEach(element => {
665          UnobfuscationCollections.reservedLangForProperty.add(element);
666        });
667        UnobfuscationCollections.reservedExportName.forEach(element => {
668          UnobfuscationCollections.reservedExportNameAndProp.add(element);
669        });
670        UnobfuscationCollections.reservedSdkApiForGlobal.forEach(element => {
671          UnobfuscationCollections.reservedSdkApiForProp.add(element);
672        });
673      }
674      LocalVariableCollections.reservedConfig = new Set(profile?.mReservedNames ?? []);
675      LocalVariableCollections.reservedStruct = new Set();
676      profile?.mReservedToplevelNames?.forEach(item => PropCollections.reservedProperties.add(item));
677      profile?.mUniversalReservedToplevelNames?.forEach(item => PropCollections.universalReservedProperties.push(item));
678      isInitializedReservedList = true;
679    }
680  };
681
682  function isSkippedGlobal(enableTopLevel: boolean, scope: Scope): boolean {
683    return !enableTopLevel && isGlobalScope(scope);
684  }
685
686  export let transformerPlugin: TransformPlugin = {
687    'name': 'renameIdentifierPlugin',
688    'order': TransformerOrder.RENAME_IDENTIFIER_TRANSFORMER,
689    'createTransformerFactory': createRenameIdentifierFactory
690  };
691
692  export let nameCache: Map<string, string | Map<string, string>> = new Map();
693  export let historyNameCache: Map<string, string> = undefined;
694  export let historyUnobfuscatedNamesMap: Map<string, string[]> = undefined;
695  export let identifierLineMap: Map<Identifier, string> = new Map();
696  export let classMangledName: Map<Node, string> = new Map();
697  // Record the original class name and line number range to distinguish between class names and member method names.
698  export let classInfoInMemberMethodCache: Set<string> = new Set();
699
700  export function clearCaches(): void {
701    nameCache.clear();
702    historyNameCache = undefined;
703    historyUnobfuscatedNamesMap = undefined;
704    identifierLineMap.clear();
705    classMangledName.clear();
706    classInfoInMemberMethodCache.clear();
707    UnobfuscationCollections.unobfuscatedNamesMap.clear();
708  }
709}
710
711export = secharmony;