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