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