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;