1/* 2 * Copyright (c) 2023 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 forEachChild, 18 getModifiers, 19 isCatchClause, 20 isClassDeclaration, 21 isConstructorDeclaration, 22 isFunctionDeclaration, 23 isFunctionLike, 24 isIdentifier, 25 isMethodDeclaration, 26 SyntaxKind 27} from 'typescript'; 28 29import type { 30 BreakOrContinueStatement, 31 CaseBlock, 32 CatchClause, 33 ClassDeclaration, 34 ClassElement, 35 ClassExpression, 36 EnumDeclaration, 37 ExportSpecifier, 38 ForInOrOfStatement, 39 ForStatement, 40 FunctionLikeDeclaration, 41 Identifier, 42 ImportSpecifier, 43 InterfaceDeclaration, 44 LabeledStatement, 45 ModuleDeclaration, 46 Node, 47 ObjectBindingPattern, 48 ObjectLiteralExpression, 49 ParameterDeclaration, 50 SourceFile, 51 Symbol, 52 SymbolTable, 53 TypeAliasDeclaration, 54 TypeChecker, 55 TypeElement 56} from 'typescript'; 57 58import {NodeUtils} from './NodeUtils'; 59import {isParameterPropertyModifier, isViewPUBasedClass} from './OhsUtil'; 60 61/** 62 * kind of a scope 63 */ 64namespace secharmony { 65 type ForLikeStatement = ForStatement | ForInOrOfStatement; 66 type ClassLikeDeclaration = ClassDeclaration | ClassExpression; 67 export const noSymbolIdentifier: Set<string> = new Set(); 68 69 /** 70 * type of scope 71 */ 72 export enum ScopeKind { 73 GLOBAL, 74 MODULE, 75 FUNCTION, 76 CLASS, 77 FOR, 78 SWITCH, 79 BLOCK, 80 INTERFACE, 81 CATCH, 82 ENUM, 83 OBJECT_LITERAL 84 } 85 86 export function isGlobalScope(scope: Scope): boolean { 87 return scope.kind === ScopeKind.GLOBAL; 88 } 89 90 export function isFunctionScope(scope: Scope): boolean { 91 return scope.kind === ScopeKind.FUNCTION; 92 } 93 94 export function isClassScope(scope: Scope): boolean { 95 return scope.kind === ScopeKind.CLASS; 96 } 97 98 export function isInterfaceScope(scope: Scope): boolean { 99 return scope.kind === ScopeKind.INTERFACE; 100 } 101 102 export function isEnumScope(scope: Scope): boolean { 103 return scope.kind === ScopeKind.ENUM; 104 } 105 106 export function isObjectLiteralScope(scope: Scope): boolean { 107 return scope.kind === ScopeKind.OBJECT_LITERAL; 108 } 109 110 /** 111 * Structure of a scope 112 */ 113 export interface Scope { 114 /** 115 * name of a scope 116 */ 117 name: string; 118 119 /** 120 * kind of current scope 121 */ 122 kind: ScopeKind; 123 124 /** 125 * node of current scope in ast 126 */ 127 block: Node; 128 129 /** 130 * parent scope of current scope 131 */ 132 parent: Scope | undefined; 133 134 /** 135 * sub scopes of current scope, 136 */ 137 children: Scope[]; 138 139 /** 140 * symbols define in current scope 141 */ 142 defs: Set<Symbol>; 143 144 /** 145 * labels in current scope 146 */ 147 labels: Label[]; 148 149 /** 150 * location path description of current scope, 151 */ 152 loc: string; 153 154 importNames?: Set<string>; 155 156 exportNames?: Set<string>; 157 158 mangledNames?: Set<string>; 159 160 constructorReservedParams?: Set<string>; 161 162 /** 163 * add a sub scope to current scope 164 * 165 * @param child 166 */ 167 addChild(child: Scope): void; 168 169 /** 170 * add definition symbol into current scope 171 * 172 * @param def definition symbol 173 */ 174 addDefinition(def: Symbol, obfuscateAsProperty?: boolean): void; 175 176 /** 177 * add label to current scope 178 * 179 * @param label label statement 180 */ 181 addLabel(label: Label): void; 182 183 /** 184 * get symbol location 185 * 186 * @param sym symbol 187 */ 188 getSymbolLocation(sym: Symbol): string; 189 190 /** 191 * get label location 192 * 193 * @param label 194 */ 195 getLabelLocation(label: Label): string; 196 } 197 198 export function createScope(name: string, node: Node, type: ScopeKind, lexicalScope: boolean = false, upper ?: Scope): Scope { 199 // scope name 200 let scopeName: string = name; 201 // kind of a scope, such as global ,function like, block .. 202 let kind: ScopeKind = type; 203 // node of a current scope in ast. 204 let block: Node = node; 205 // parent scope of current scope 206 let parent: Scope | undefined = upper; 207 // sub scopes of current scope 208 let children: Scope[] = []; 209 210 // symbols define in current scope 211 let defs: Set<Symbol> = new Set<Symbol>(); 212 213 // labels in current scope 214 let labels: Label[] = []; 215 216 let importNames: Set<string> = new Set<string>(); 217 218 let exportNames: Set<string> = new Set<string>(); 219 220 let mangledNames: Set<string> = new Set<string>(); 221 222 let constructorReservedParams: Set<string> = new Set<string>(); 223 224 // location path 225 let loc: string = parent?.loc ? parent.loc + '#' + scopeName : scopeName; 226 227 // current scope 228 let current: Scope = { 229 'name': scopeName, 230 'kind': kind, 231 'block': block, 232 'parent': parent, 233 'children': children, 234 'defs': defs, 235 'labels': labels, 236 'loc': loc, 237 'importNames': importNames, 238 'exportNames': exportNames, 239 'mangledNames': mangledNames, 240 'constructorReservedParams': constructorReservedParams, 241 addChild, 242 addDefinition, 243 addLabel, 244 getSymbolLocation, 245 getLabelLocation, 246 }; 247 248 current.parent?.addChild(current); 249 return current; 250 251 function addChild(child: Scope): void { 252 current.children.push(child); 253 } 254 255 function addDefinition(def: Symbol, obfuscateAsProperty: boolean = false): void { 256 if (current.kind === ScopeKind.GLOBAL || obfuscateAsProperty) { 257 Reflect.set(def, 'obfuscateAsProperty', true); 258 } 259 current.defs.add(def); 260 } 261 262 function addLabel(label: Label): void { 263 current.labels.push(label); 264 } 265 266 function getSymbolLocation(sym: Symbol): string { 267 if (!defs.has(sym)) { 268 return ''; 269 } 270 271 return current.loc ? sym.name : current.loc + '#' + sym.name; 272 } 273 274 function getLabelLocation(label: Label): string { 275 if (!current.labels.includes(label)) { 276 return ''; 277 } 278 279 let index: number = current.labels.findIndex((lb: Label) => { 280 return lb === label; 281 }); 282 283 return current.loc ? label.name : current.loc + '#' + index + label.name; 284 } 285 } 286 287 export interface Label { 288 name: string; 289 locInfo: string; 290 refs: Identifier[]; 291 parent: Label | undefined; 292 children: Label[]; 293 scope: Scope; 294 } 295 296 export function createLabel(node: LabeledStatement, scope: Scope, parent?: Label | undefined): Label { 297 let labelName: string = '$' + scope.labels.length + '_' + node.label.text; 298 let label: Label = { 299 'name': node.label.text, 300 'locInfo': labelName, 301 'refs': [node.label], 302 'parent': parent, 303 'children': [], 304 'scope': scope, 305 }; 306 307 scope.labels.push(label); 308 parent?.children.push(label); 309 310 return label; 311 } 312 313 export interface ScopeManager { 314 315 /** 316 * get reserved names like ViewPU component class name 317 */ 318 getReservedNames(): Set<string>; 319 320 /** 321 * do scope analysis 322 * 323 * @param ast ast tree of a source file 324 * @param checker 325 */ 326 analyze(ast: SourceFile, checker: TypeChecker, isEnabledExportObfuscation: boolean): void; 327 328 /** 329 * get root scope of a file 330 */ 331 getRootScope(): Scope; 332 333 /** 334 * find block Scope of a node 335 * @param node 336 */ 337 getScopeOfNode(node: Node): Scope | undefined; 338 } 339 340 export function createScopeManager(): ScopeManager { 341 let reservedNames: Set<string> = new Set<string>(); 342 let root: Scope; 343 let current: Scope; 344 let scopes: Scope[] = []; 345 346 let checker: TypeChecker = null; 347 let upperLabel: Label | undefined = undefined; 348 let exportObfuscation: boolean = false; 349 350 return { 351 getReservedNames, 352 analyze, 353 getRootScope, 354 getScopeOfNode, 355 }; 356 357 function analyze(ast: SourceFile, typeChecker: TypeChecker, isEnabledExportObfuscation = false): void { 358 checker = typeChecker; 359 exportObfuscation = isEnabledExportObfuscation; 360 analyzeScope(ast); 361 } 362 363 function getReservedNames(): Set<string> { 364 return reservedNames; 365 } 366 367 function getRootScope(): Scope { 368 return root; 369 } 370 371 function addSymbolInScope(node: Node): void { 372 let defSymbols: SymbolTable = node?.locals; 373 if (!defSymbols) { 374 return; 375 } 376 377 defSymbols.forEach((def: Symbol) => { 378 // with export identification, special handling. 379 if (def.exportSymbol) { 380 current.exportNames.add(def.name); 381 current.addDefinition(def.exportSymbol, true); 382 } 383 384 current.addDefinition(def); 385 }); 386 } 387 388 function addExportSymbolInScope(node: Node): void { 389 let defSymbols: Symbol = node?.symbol; 390 391 if (!defSymbols) { 392 return; 393 } 394 current.addDefinition(defSymbols); 395 } 396 397 /** 398 * analyze chain of scopes 399 * @param node 400 */ 401 function analyzeScope(node: Node): void { 402 switch (node.kind) { 403 // global 404 case SyntaxKind.SourceFile: 405 analyzeSourceFile(node as SourceFile); 406 break; 407 408 // namespace or module 409 case SyntaxKind.ModuleDeclaration: 410 analyzeModule(node as ModuleDeclaration); 411 break; 412 413 // function like 414 case SyntaxKind.FunctionDeclaration: 415 case SyntaxKind.MethodDeclaration: 416 case SyntaxKind.GetAccessor: 417 case SyntaxKind.SetAccessor: 418 case SyntaxKind.Constructor: 419 case SyntaxKind.FunctionExpression: 420 case SyntaxKind.ArrowFunction: 421 analyzeFunctionLike(node as FunctionLikeDeclaration); 422 break; 423 424 // class like 425 case SyntaxKind.ClassExpression: 426 case SyntaxKind.ClassDeclaration: 427 case SyntaxKind.StructDeclaration: 428 analyzeClassLike(node as ClassLikeDeclaration); 429 break; 430 431 // for like 432 case SyntaxKind.ForStatement: 433 case SyntaxKind.ForInStatement: 434 case SyntaxKind.ForOfStatement: 435 analyzeForLike(node as ForLikeStatement); 436 break; 437 case SyntaxKind.CaseBlock: 438 // caseBlock property in switch statement 439 analyzeSwitch(node as CaseBlock); 440 break; 441 case SyntaxKind.Block: 442 // while, do ...while, block, if/else.. 443 analyzeBlock(node); 444 break; 445 446 case SyntaxKind.InterfaceDeclaration: 447 analyzeInterface(node as InterfaceDeclaration); 448 break; 449 450 case SyntaxKind.EnumDeclaration: 451 analyzeEnum(node as EnumDeclaration); 452 break; 453 454 case SyntaxKind.Identifier: 455 analyzeSymbol(node as Identifier); 456 break; 457 458 case SyntaxKind.TypeAliasDeclaration: 459 analyzeTypeAliasDeclaration(node as TypeAliasDeclaration); 460 break; 461 462 case SyntaxKind.LabeledStatement: 463 analyzeLabel(node as LabeledStatement); 464 break; 465 466 case SyntaxKind.BreakStatement: 467 case SyntaxKind.ContinueStatement: 468 analyzeBreakOrContinue(node as BreakOrContinueStatement); 469 break; 470 case SyntaxKind.ImportSpecifier: 471 analyzeImportNames(node as ImportSpecifier); 472 break; 473 474 case SyntaxKind.ObjectBindingPattern: 475 analyzeObjectBindingPatternRequire(node as ObjectBindingPattern); 476 break; 477 478 case SyntaxKind.ObjectLiteralExpression: 479 analyzeObjectLiteralExpression(node as ObjectLiteralExpression); 480 break; 481 482 case SyntaxKind.ExportSpecifier: 483 analyzeExportNames(node as ExportSpecifier); 484 break; 485 486 case SyntaxKind.CatchClause: 487 analyzeCatchClause(node as CatchClause); 488 break; 489 default: 490 forEachChild(node, analyzeScope); 491 break; 492 } 493 } 494 495 function analyzeImportNames(node: ImportSpecifier): void { 496 try { 497 const propetyNameNode: Identifier | undefined = node.propertyName; 498 if (exportObfuscation && propetyNameNode && isIdentifier(propetyNameNode)) { 499 let propertySymbol = checker.getSymbolAtLocation(propetyNameNode); 500 if (!propertySymbol) { 501 noSymbolIdentifier.add(propetyNameNode.escapedText as string); 502 } else { 503 current.addDefinition(propertySymbol); 504 } 505 506 const nameSymbol = checker.getSymbolAtLocation(node.name); 507 if (nameSymbol) { 508 current.addDefinition(nameSymbol); 509 } 510 } else { 511 const nameText = propetyNameNode ? propetyNameNode.text : node.name.text; 512 current.importNames.add(nameText); 513 } 514 forEachChild(node, analyzeScope); 515 } catch (e) { 516 console.error(e); 517 } 518 } 519 520 /** example 521 * const { x1, y: customY, z = 0 }: { x: number; y?: number; z?: number } = { x: 1, y: 2 }; 522 * bindingElement.name is x1 for the first element. 523 * bindingElement.name is customY for the second element. 524 */ 525 function analyzeObjectBindingPatternRequire(node: ObjectBindingPattern): void { 526 if (!NodeUtils.isObjectBindingPatternAssignment(node)) { 527 forEachChild(node, analyzeScope); 528 return; 529 } 530 531 if (!node.elements) { 532 return; 533 } 534 535 node.elements.forEach((bindingElement) => { 536 if (!bindingElement) { 537 return; 538 } 539 540 findNoSymbolIdentifiers(bindingElement); 541 542 if (!bindingElement.name || !isIdentifier(bindingElement.name)) { 543 return; 544 } 545 546 if (bindingElement.propertyName) { 547 return; 548 } 549 550 current.importNames.add(bindingElement.name.text); 551 }); 552 } 553 554 function analyzeObjectLiteralExpression(node: ObjectLiteralExpression): void { 555 let scopeName: string = '$' + current.children.length; 556 current = createScope(scopeName, node, ScopeKind.OBJECT_LITERAL, false, current); 557 scopes.push(current); 558 559 addSymbolInScope(node); 560 forEachChild(node, analyzeScope); 561 current = current.parent || current; 562 } 563 564 function analyzeExportNames(node: ExportSpecifier): void { 565 // get export names. 566 current.exportNames.add(node.name.text); 567 addExportSymbolInScope(node); 568 const propetyNameNode: Identifier | undefined = node.propertyName; 569 if (exportObfuscation && propetyNameNode && isIdentifier(propetyNameNode)) { 570 let propertySymbol = checker.getSymbolAtLocation(propetyNameNode); 571 if (!propertySymbol) { 572 noSymbolIdentifier.add(propetyNameNode.escapedText as string); 573 } 574 } 575 forEachChild(node, analyzeScope); 576 } 577 578 function analyzeBreakOrContinue(node: BreakOrContinueStatement): void { 579 let labelName: string = node?.label?.text ?? ''; 580 let label: Label = findTargetLabel(labelName); 581 if (!label) { 582 return; 583 } 584 585 if (node.label) { 586 label?.refs.push(node.label); 587 } 588 589 forEachChild(node, analyzeScope); 590 } 591 592 function findTargetLabel(labelName: string): Label | null { 593 if (!labelName) { 594 return null; 595 } 596 597 let label: Label | undefined = upperLabel; 598 // avoid loop 599 while (label && label?.name !== labelName) { 600 label = label?.parent; 601 } 602 603 return label; 604 } 605 606 function analyzeSourceFile(node: SourceFile): void { 607 let scopeName: string = ''; 608 root = createScope(scopeName, node, ScopeKind.GLOBAL, true); 609 current = root; 610 scopes.push(current); 611 // locals of a node(scope) is symbol that defines in current scope(node). 612 addSymbolInScope(node); 613 forEachChild(node, analyzeScope); 614 current = current.parent || current; 615 extractImportExports(); 616 } 617 618 function analyzeCatchClause(node: CatchClause): void { 619 let scopeName: string = '$' + current.children.length; 620 current = createScope(scopeName, node, ScopeKind.CATCH, false, current); 621 scopes.push(current); 622 // add in catch declaration. 623 addSymbolInScope(node); 624 if (node.block) { 625 // add in block declaration. 626 addSymbolInScope(node.block); 627 } 628 629 forEachChild(node, analyzeScope); 630 current = current.parent || current; 631 } 632 633 function extractImportExports(): void { 634 for (const def of current.defs) { 635 if (def.exportSymbol) { 636 if (!current.exportNames.has(def.name)) { 637 current.exportNames.add(def.name); 638 } 639 const name: string = def.exportSymbol.name; 640 if (!current.exportNames.has(name)) { 641 current.exportNames.add(name); 642 } 643 } 644 } 645 } 646 647 function analyzeTypeAliasDeclaration(node: TypeAliasDeclaration): void { 648 let scopeName: string = node.name.text ?? '$' + current.children.length; 649 current = createScope(scopeName, node, ScopeKind.INTERFACE, true, current); 650 scopes.push(current); 651 addSymbolInScope(node); 652 forEachChild(node, analyzeScope); 653 current = current.parent || current; 654 } 655 656 /** 657 * namespace ns { 658 * ... 659 * } 660 * @param node 661 */ 662 function analyzeModule(node: ModuleDeclaration): void { 663 /** 664 * if it is an anonymous scope, generate the scope name with a number, 665 * which is based on the order of its child scopes in the upper scope 666 */ 667 let scopeName: string = node.name.text ?? '$' + current.children.length; 668 current = createScope(scopeName, node, ScopeKind.MODULE, true, current); 669 scopes.push(current); 670 addSymbolInScope(node); 671 node.forEachChild((sub: Node) => { 672 if (isIdentifier(sub)) { 673 return; 674 } 675 analyzeScope(sub); 676 }) 677 current = current.parent || current; 678 } 679 680 /** 681 * exclude constructor's parameter witch should be treated as property, example: 682 * constructor(public name){}, name should be treated as property 683 * @param node 684 */ 685 function excludeConstructorParameter(node: Node): void { 686 if (!isConstructorDeclaration(node)) { 687 return; 688 } 689 690 const visitParam = (param: ParameterDeclaration): void => { 691 const modifiers = getModifiers(param); 692 if (!modifiers || modifiers.length <= 0) { 693 return; 694 } 695 696 const findRet = modifiers.find(modifier => isParameterPropertyModifier(modifier)); 697 if (!isIdentifier(param.name) || findRet === undefined) { 698 return; 699 } 700 701 current.defs.forEach((def) => { 702 if (def.name !== param.name.getText()) { 703 return; 704 } 705 706 current.defs.delete(def); 707 current.mangledNames.add(def.name); 708 root.constructorReservedParams.add(def.name); 709 }); 710 }; 711 712 node.parameters.forEach((param) => { 713 visitParam(param); 714 }); 715 } 716 717 /** 718 * function func(param1...) { 719 * ... 720 * } 721 * @param node 722 */ 723 function analyzeFunctionLike(node: FunctionLikeDeclaration): void { 724 // For example, the constructor of the StructDeclaration, inserted by arkui, will add a virtual attribute. 725 // @ts-ignore 726 if (node.virtual) { 727 return; 728 } 729 let scopeName: string = (node?.name as Identifier)?.text ?? '$' + current.children.length; 730 let loc: string = current?.loc ? current.loc + '#' + scopeName : scopeName; 731 let overloading: boolean = false; 732 for (const sub of current.children) { 733 if (sub.loc === loc) { 734 overloading = true; 735 current = sub; 736 break; 737 } 738 } 739 740 if (!overloading) { 741 current = createScope(scopeName, node, ScopeKind.FUNCTION, true, current); 742 scopes.push(current); 743 } 744 745 addSymbolInScope(node); 746 if (node.symbol && current.parent && !current.parent.defs.has(node.symbol)) { 747 current.parent.defs.add(node.symbol); 748 } 749 750 if (isFunctionDeclaration(node) || isMethodDeclaration(node)) { 751 // function declaration requires skipping function names 752 node.forEachChild((sub: Node) => { 753 if (isIdentifier(sub)) { 754 tryAddNoSymbolIdentifiers(sub); 755 return; 756 } 757 758 analyzeScope(sub); 759 }); 760 } else { 761 forEachChild(node, analyzeScope); 762 } 763 764 excludeConstructorParameter(node); 765 current = current.parent || current; 766 } 767 768 function analyzeSwitch(node: CaseBlock): void { 769 let scopeName: string = '$' + current.children.length; 770 current = createScope(scopeName, node, ScopeKind.SWITCH, false, current); 771 scopes.push(current); 772 addSymbolInScope(node); 773 forEachChild(node, analyzeScope); 774 current = current.parent || current; 775 } 776 777 /** 778 * ES6+ class like scope, The members of a class aren't not allow to rename in rename identifiers transformer, but 779 * rename in rename properties transformer. 780 * 781 * @param node 782 */ 783 function analyzeClassLike(node: ClassLikeDeclaration): void { 784 if (isClassDeclaration(node) && isViewPUBasedClass(node)) { 785 reservedNames.add(node.name.text); 786 } 787 788 try { 789 let scopeName: string = node?.name?.text ?? '$' + current.children.length; 790 current = createScope(scopeName, node, ScopeKind.CLASS, true, current); 791 scopes.push(current); 792 addSymbolInScope(node); 793 // Class members are seen as attribute names, and the reference of external symbols can be renamed as the same 794 node.members?.forEach((elm: ClassElement) => { 795 // @ts-ignore 796 if (elm?.symbol && !elm.virtual) { 797 current.addDefinition(elm.symbol); 798 } 799 }); 800 801 forEachChild(node, analyzeScope); 802 } catch (e) { 803 console.error(e); 804 } 805 806 current = current.parent || current; 807 } 808 809 function analyzeForLike(node: ForLikeStatement): void { 810 let scopeName: string = '$' + current.children.length; 811 current = createScope(scopeName, node, ScopeKind.FOR, false, current); 812 scopes.push(current); 813 addSymbolInScope(node); 814 forEachChild(node, analyzeScope); 815 current = current.parent || current; 816 } 817 818 function analyzeBlock(node: Node): void { 819 // when block is body of a function 820 if ((isFunctionScope(current) && isFunctionLike(node.parent)) || isCatchClause(node.parent)) { 821 // skip direct block scope in function scope 822 forEachChild(node, analyzeScope); 823 return; 824 } 825 826 let scopeName: string = '$' + current.children.length; 827 current = createScope(scopeName, node, ScopeKind.BLOCK, false, current); 828 scopes.push(current); 829 addSymbolInScope(node); 830 forEachChild(node, analyzeScope); 831 current = current.parent || current; 832 } 833 834 function analyzeInterface(node: InterfaceDeclaration): void { 835 let scopeName: string = node.name.text; 836 current = createScope(scopeName, node, ScopeKind.INTERFACE, true, current); 837 scopes.push(current); 838 try { 839 addSymbolInScope(node); 840 } catch (e) { 841 console.error(''); 842 } 843 844 node.members?.forEach((elm: TypeElement) => { 845 if (elm?.symbol) { 846 current.addDefinition(elm.symbol); 847 } 848 }); 849 850 forEachChild(node, analyzeScope); 851 current = current.parent || current; 852 } 853 854 function analyzeEnum(node: EnumDeclaration): void { 855 let scopeName: string = node.name.text; 856 current = createScope(scopeName, node, ScopeKind.ENUM, true, current); 857 scopes.push(current); 858 for (const member of node.members) { 859 if (member.symbol) { 860 current.addDefinition(member.symbol); 861 } 862 } 863 864 forEachChild(node, analyzeScope); 865 current = current.parent || current; 866 } 867 868 function analyzeSymbol(node: Identifier): void { 869 // ignore all identifiers that treat as property in property access 870 if (NodeUtils.isPropertyAccessNode(node)) { 871 return; 872 } 873 874 let symbol: Symbol = null; 875 876 try { 877 symbol = checker.getSymbolAtLocation(node); 878 } catch (e) { 879 console.error(e); 880 return; 881 } 882 883 if (!symbol) { 884 current.mangledNames.add(node.escapedText.toString()); 885 return; 886 } 887 888 // ignore all identifiers that treat as property in property declaration 889 if (NodeUtils.isPropertyDeclarationNode(node)) { 890 return; 891 } 892 893 // add def symbol that don't found in current defs. 894 addSymbolIntoDefsIfNeeded(node, symbol, current.defs); 895 } 896 897 function addSymbolIntoDefsIfNeeded(node: Identifier, symbol: Symbol, currentDefs: Set<Symbol>): boolean { 898 // process a new def not in currentDefs 899 let isSameName: boolean = false; 900 for (const def of currentDefs) { 901 if (def.name === node.text) { 902 isSameName = true; 903 break; 904 } 905 } 906 907 if (isSameName) { 908 // exclude the possibility of external symbols, as those with duplicate names have been added to currentDefs (this avoids the possibility of omissions) 909 if (!currentDefs.has(symbol)) { 910 currentDefs.add(symbol); 911 } 912 913 if (symbol.exportSymbol && !currentDefs.has(symbol.exportSymbol)) { 914 Reflect.set(symbol, 'obfuscateAsProperty', true); 915 currentDefs.add(symbol); 916 } 917 } 918 919 return isSameName; 920 } 921 922 function analyzeLabel(node: LabeledStatement): void { 923 // labels within the same scope are allowed to be duplicated, so label names need to have numbering information to distinguish them 924 upperLabel = upperLabel ? createLabel(node, current, upperLabel) : createLabel(node, current); 925 forEachChild(node, analyzeScope); 926 upperLabel = upperLabel?.parent; 927 } 928 929 function getScopeOfNode(node: Node): Scope | undefined { 930 if (!isIdentifier(node)) { 931 return undefined; 932 } 933 934 let sym: Symbol = checker.getSymbolAtLocation(node); 935 if (!sym) { 936 return undefined; 937 } 938 939 for (const scope of scopes) { 940 if (scope?.defs.has(sym)) { 941 return scope; 942 } 943 } 944 945 return undefined; 946 } 947 948 function tryAddNoSymbolIdentifiers(node: Identifier): void { 949 if (!isIdentifier(node)) { 950 return; 951 } 952 953 // skip property in property access expression 954 if (NodeUtils.isPropertyAccessNode(node)) { 955 return; 956 } 957 958 const sym: Symbol | undefined = checker.getSymbolAtLocation(node); 959 if (!sym) { 960 current.mangledNames.add((node as Identifier).escapedText.toString()); 961 } 962 } 963 964 function findNoSymbolIdentifiers(node: Node): void { 965 const noSymbolVisit = (targetNode: Node): void => { 966 if (!isIdentifier(targetNode)) { 967 forEachChild(targetNode, noSymbolVisit); 968 return; 969 } 970 tryAddNoSymbolIdentifiers(targetNode); 971 }; 972 973 noSymbolVisit(node); 974 } 975 } 976} 977 978export = secharmony; 979