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 ImportEqualsDeclaration, 50 ImportSpecifier, 51 InterfaceDeclaration, 52 LabeledStatement, 53 ModuleDeclaration, 54 NamespaceExport, 55 Node, 56 ObjectBindingPattern, 57 ObjectLiteralExpression, 58 ParameterDeclaration, 59 SourceFile, 60 Symbol, 61 SymbolTable, 62 TypeAliasDeclaration, 63 TypeChecker, 64 TypeElement 65} from 'typescript'; 66 67import {NodeUtils} from './NodeUtils'; 68import {isParameterPropertyModifier, isViewPUBasedClass} from './OhsUtil'; 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 450 case SyntaxKind.ImportEqualsDeclaration: 451 analyzeImportEqualsDeclaration(node as ImportEqualsDeclaration); 452 break; 453 454 default: 455 forEachChild(node, analyzeScope); 456 break; 457 } 458 } 459 460 function analyzeImportNames(node: ImportSpecifier): void { 461 try { 462 const propetyNameNode: Identifier | undefined = node.propertyName; 463 if (exportObfuscation && propetyNameNode && isIdentifier(propetyNameNode)) { 464 let propertySymbol = checker.getSymbolAtLocation(propetyNameNode); 465 if (!propertySymbol) { 466 noSymbolIdentifier.add(propetyNameNode.text); 467 } else { 468 current.addDefinition(propertySymbol); 469 } 470 471 const nameSymbol = checker.getSymbolAtLocation(node.name); 472 if (nameSymbol) { 473 current.addDefinition(nameSymbol); 474 } 475 } else { 476 const nameText = propetyNameNode ? propetyNameNode.text : node.name.text; 477 current.importNames.add(nameText); 478 root.fileImportNames.add(nameText); 479 } 480 forEachChild(node, analyzeScope); 481 } catch (e) { 482 console.error(e); 483 } 484 } 485 486 /** example 487 * const { x1, y: customY, z = 0 }: { x: number; y?: number; z?: number } = { x: 1, y: 2 }; 488 * bindingElement.name is x1 for the first element. 489 * bindingElement.name is customY for the second element. 490 */ 491 function analyzeObjectBindingPatternRequire(node: ObjectBindingPattern): void { 492 if (!NodeUtils.isObjectBindingPatternAssignment(node)) { 493 forEachChild(node, analyzeScope); 494 return; 495 } 496 497 if (!node.elements) { 498 return; 499 } 500 501 node.elements.forEach((bindingElement) => { 502 if (!bindingElement) { 503 return; 504 } 505 506 findNoSymbolIdentifiers(bindingElement); 507 508 if (!bindingElement.name || !isIdentifier(bindingElement.name)) { 509 return; 510 } 511 512 if (bindingElement.propertyName) { 513 return; 514 } 515 516 current.importNames.add(bindingElement.name.text); 517 root.fileImportNames.add(bindingElement.name.text); 518 }); 519 } 520 521 function analyzeObjectLiteralExpression(node: ObjectLiteralExpression): void { 522 let scopeName: string = '$' + current.children.length; 523 current = new Scope(scopeName, node, ScopeKind.OBJECT_LITERAL, false, current); 524 scopes.push(current); 525 526 addSymbolInScope(node); 527 forEachChild(node, analyzeScope); 528 current = current.parent || current; 529 } 530 531 function analyzeExportNames(node: ExportSpecifier): void { 532 // get export names. 533 current.exportNames.add(node.name.text); 534 root.fileExportNames.add(node.name.text); 535 addExportSymbolInScope(node); 536 const propetyNameNode: Identifier | undefined = node.propertyName; 537 if (exportObfuscation && propetyNameNode && isIdentifier(propetyNameNode)) { 538 let propertySymbol = checker.getSymbolAtLocation(propetyNameNode); 539 if (!propertySymbol) { 540 noSymbolIdentifier.add(propetyNameNode.text); 541 } 542 } 543 forEachChild(node, analyzeScope); 544 } 545 546 function analyzeNamespaceExport(node: NamespaceExport): void { 547 if (!exportObfuscation) { 548 return; 549 } 550 551 let symbol = checker.getSymbolAtLocation(node.name); 552 if (symbol) { 553 current.addDefinition(symbol, true); 554 } 555 } 556 557 function analyzeBreakOrContinue(node: BreakOrContinueStatement): void { 558 let labelName: string = node?.label?.text ?? ''; 559 let label: Label = findTargetLabel(labelName); 560 if (!label) { 561 return; 562 } 563 564 if (node.label) { 565 label?.refs.push(node.label); 566 } 567 568 forEachChild(node, analyzeScope); 569 } 570 571 function findTargetLabel(labelName: string): Label | null { 572 if (!labelName) { 573 return null; 574 } 575 576 let label: Label | undefined = upperLabel; 577 // avoid loop 578 while (label && label?.name !== labelName) { 579 label = label?.parent; 580 } 581 582 return label; 583 } 584 585 function analyzeSourceFile(node: SourceFile): void { 586 let scopeName: string = ''; 587 root = new Scope(scopeName, node, ScopeKind.GLOBAL, true); 588 root.fileExportNames = new Set<string>(); 589 root.fileImportNames = new Set<string>(); 590 current = root; 591 scopes.push(current); 592 // locals of a node(scope) is symbol that defines in current scope(node). 593 addSymbolInScope(node); 594 forEachChild(node, analyzeScope); 595 current = current.parent || current; 596 extractImportExports(); 597 } 598 599 function analyzeCatchClause(node: CatchClause): void { 600 let scopeName: string = '$' + current.children.length; 601 current = new Scope(scopeName, node, ScopeKind.CATCH, false, current); 602 scopes.push(current); 603 // add in catch declaration. 604 addSymbolInScope(node); 605 if (node.block) { 606 // add in block declaration. 607 addSymbolInScope(node.block); 608 } 609 610 forEachChild(node, analyzeScope); 611 current = current.parent || current; 612 } 613 614 function extractImportExports(): void { 615 for (const def of current.defs) { 616 if (def.exportSymbol) { 617 if (!current.exportNames.has(def.name)) { 618 current.exportNames.add(def.name); 619 root.fileExportNames.add(def.name); 620 } 621 const name: string = def.exportSymbol.name; 622 if (!current.exportNames.has(name)) { 623 current.exportNames.add(name); 624 root.fileExportNames.add(def.name); 625 } 626 } 627 } 628 } 629 630 function analyzeTypeAliasDeclaration(node: TypeAliasDeclaration): void { 631 let scopeName: string = node.name.text ?? '$' + current.children.length; 632 current = new Scope(scopeName, node, ScopeKind.INTERFACE, true, current); 633 scopes.push(current); 634 addSymbolInScope(node); 635 forEachChild(node, analyzeScope); 636 current = current.parent || current; 637 } 638 639 /** 640 * namespace ns { 641 * ... 642 * } 643 * @param node 644 */ 645 function analyzeModule(node: ModuleDeclaration): void { 646 /** 647 * if it is an anonymous scope, generate the scope name with a number, 648 * which is based on the order of its child scopes in the upper scope 649 */ 650 let scopeName: string = node.name.text ?? '$' + current.children.length; 651 current = new Scope(scopeName, node, ScopeKind.MODULE, true, current); 652 scopes.push(current); 653 addSymbolInScope(node); 654 node.forEachChild((sub: Node) => { 655 if (isIdentifier(sub)) { 656 return; 657 } 658 analyzeScope(sub); 659 }); 660 current = current.parent || current; 661 } 662 663 /** 664 * exclude constructor's parameter witch should be treated as property, example: 665 * constructor(public name){}, name should be treated as property 666 * @param node 667 */ 668 function excludeConstructorParameter(node: Node): void { 669 if (!isConstructorDeclaration(node)) { 670 return; 671 } 672 673 const visitParam = (param: ParameterDeclaration): void => { 674 const modifiers = getModifiers(param); 675 if (!modifiers || modifiers.length <= 0) { 676 return; 677 } 678 679 const findRet = modifiers.find(modifier => isParameterPropertyModifier(modifier)); 680 if (!isIdentifier(param.name) || findRet === undefined) { 681 return; 682 } 683 684 current.defs.forEach((def) => { 685 if (isIdentifier(param.name) && (def.name === param.name.text)) { 686 current.defs.delete(def); 687 current.mangledNames.add(def.name); 688 } 689 }); 690 }; 691 692 node.parameters.forEach((param) => { 693 visitParam(param); 694 }); 695 } 696 697 /** 698 * function func(param1...) { 699 * ... 700 * } 701 * @param node 702 */ 703 function analyzeFunctionLike(node: FunctionLikeDeclaration): void { 704 // For example, the constructor of the StructDeclaration, inserted by arkui, will add a virtual attribute. 705 // @ts-ignore 706 if (getOriginalNode(node).virtual) { 707 return; 708 } 709 let scopeName: string = (node?.name as Identifier)?.text ?? '$' + current.children.length; 710 let loc: string = current?.loc ? current.loc + '#' + scopeName : scopeName; 711 let overloading: boolean = false; 712 for (const sub of current.children) { 713 if (sub.loc === loc) { 714 overloading = true; 715 current = sub; 716 break; 717 } 718 } 719 720 if (!overloading) { 721 current = new Scope(scopeName, node, ScopeKind.FUNCTION, true, current); 722 scopes.push(current); 723 } 724 725 let symbol: Symbol; 726 if ((isFunctionExpression(node) || isArrowFunction(node)) && isVariableDeclaration(node.parent)) { 727 symbol = checker.getSymbolAtLocation(node.name ? node.name : node.parent.name); 728 } else { 729 if (isFunctionDeclaration(node)) { 730 symbol = NodeUtils.findSymbolOfIdentifier(checker, node.name); 731 } else { 732 symbol = checker.getSymbolAtLocation(node.name); 733 } 734 } 735 if (symbol) { 736 Reflect.set(symbol, 'isFunction', true); 737 } 738 739 addSymbolInScope(node); 740 /** 741 * { 742 * get name(): "INT"; 743 * set orignal(): 0; 744 * } 745 * // the above getaccessor and setaccessor were obfuscated as identifiers. 746 */ 747 if (!(isGetAccessor(node) || isSetAccessor(node)) && symbol && current.parent && !current.parent.defs.has(symbol)) { 748 /* 749 Handle the case when `FunctionLikeDeclaration` node is as initializer of variable declaration. 750 eg. const foo = function bar() {}; 751 The `current` scope is the function's scope, the `current.parent` scope is where the function is defined. 752 `foo` has already added in the parent scope, we need to add `bar` here too. 753 */ 754 current.parent.defs.add(symbol); 755 } 756 757 if (isFunctionDeclaration(node) || isMethodDeclaration(node)) { 758 // function declaration requires skipping function names 759 node.forEachChild((sub: Node) => { 760 if (isIdentifier(sub)) { 761 tryAddNoSymbolIdentifiers(sub); 762 return; 763 } 764 765 analyzeScope(sub); 766 }); 767 } else { 768 forEachChild(node, analyzeScope); 769 } 770 771 excludeConstructorParameter(node); 772 current = current.parent || current; 773 } 774 775 function analyzeSwitch(node: CaseBlock): void { 776 let scopeName: string = '$' + current.children.length; 777 current = new Scope(scopeName, node, ScopeKind.SWITCH, false, current); 778 scopes.push(current); 779 addSymbolInScope(node); 780 forEachChild(node, analyzeScope); 781 current = current.parent || current; 782 } 783 784 /** 785 * ES6+ class like scope, The members of a class aren't not allow to rename in rename identifiers transformer, but 786 * rename in rename properties transformer. 787 * 788 * @param node 789 */ 790 function analyzeClassLike(node: ClassLikeDeclaration): void { 791 if (isClassDeclaration(node) && isViewPUBasedClass(node)) { 792 reservedNames.add(node.name.text); 793 } 794 795 try { 796 let scopeName: string = node?.name?.text ?? '$' + current.children.length; 797 current = new Scope(scopeName, node, ScopeKind.CLASS, true, current); 798 scopes.push(current); 799 addSymbolInScope(node); 800 // Class members are seen as attribute names, and the reference of external symbols can be renamed as the same 801 node.members?.forEach((elm: ClassElement) => { 802 // @ts-ignore 803 if (elm?.symbol && !getOriginalNode(elm).virtual) { 804 current.addDefinition(elm.symbol); 805 } 806 }); 807 808 forEachChild(node, analyzeScope); 809 } catch (e) { 810 console.error(e); 811 } 812 813 current = current.parent || current; 814 } 815 816 function analyzeForLike(node: ForLikeStatement): void { 817 let scopeName: string = '$' + current.children.length; 818 current = new Scope(scopeName, node, ScopeKind.FOR, false, current); 819 scopes.push(current); 820 addSymbolInScope(node); 821 forEachChild(node, analyzeScope); 822 current = current.parent || current; 823 } 824 825 function analyzeBlock(node: Node): void { 826 // when block is body of a function 827 if ((isFunctionScope(current) && isFunctionLike(node.parent)) || isCatchClause(node.parent)) { 828 // skip direct block scope in function scope 829 forEachChild(node, analyzeScope); 830 return; 831 } 832 833 let scopeName: string = '$' + current.children.length; 834 current = new Scope(scopeName, node, ScopeKind.BLOCK, false, current); 835 scopes.push(current); 836 addSymbolInScope(node); 837 forEachChild(node, analyzeScope); 838 current = current.parent || current; 839 } 840 841 function analyzeInterface(node: InterfaceDeclaration): void { 842 let scopeName: string = node.name.text; 843 current = new Scope(scopeName, node, ScopeKind.INTERFACE, true, current); 844 scopes.push(current); 845 try { 846 addSymbolInScope(node); 847 } catch (e) { 848 console.error(''); 849 } 850 851 node.members?.forEach((elm: TypeElement) => { 852 if (elm?.symbol) { 853 current.addDefinition(elm.symbol); 854 } 855 }); 856 857 forEachChild(node, analyzeScope); 858 current = current.parent || current; 859 } 860 861 function analyzeEnum(node: EnumDeclaration): void { 862 let scopeName: string = node.name.text; 863 current = new Scope(scopeName, node, ScopeKind.ENUM, true, current); 864 scopes.push(current); 865 for (const member of node.members) { 866 if (member.symbol) { 867 current.addDefinition(member.symbol); 868 } 869 } 870 871 forEachChild(node, analyzeScope); 872 current = current.parent || current; 873 } 874 875 function analyzeSymbol(node: Identifier): void { 876 // ignore all identifiers that treat as property in property access 877 if (NodeUtils.isPropertyAccessNode(node)) { 878 return; 879 } 880 881 let symbol: Symbol = null; 882 883 try { 884 symbol = NodeUtils.findSymbolOfIdentifier(checker, node); 885 } catch (e) { 886 console.error(e); 887 return; 888 } 889 890 if (!symbol) { 891 current.mangledNames.add(node.text); 892 return; 893 } 894 895 // ignore all identifiers that treat as property in property declaration 896 if (NodeUtils.isPropertyDeclarationNode(node)) { 897 return; 898 } 899 900 // add def symbol that don't found in current defs. 901 addSymbolIntoDefsIfNeeded(node, symbol, current.defs); 902 } 903 904 function addSymbolIntoDefsIfNeeded(node: Identifier, symbol: Symbol, currentDefs: Set<Symbol>): boolean { 905 // process a new def not in currentDefs 906 let isSameName: boolean = false; 907 for (const def of currentDefs) { 908 if (def.name === node.text) { 909 isSameName = true; 910 break; 911 } 912 } 913 914 if (isSameName) { 915 // exclude the possibility of external symbols, as those with duplicate names have been added to currentDefs (this avoids the possibility of omissions) 916 if (!currentDefs.has(symbol)) { 917 currentDefs.add(symbol); 918 } 919 920 if (symbol.exportSymbol && !currentDefs.has(symbol.exportSymbol)) { 921 Reflect.set(symbol, 'obfuscateAsProperty', true); 922 currentDefs.add(symbol); 923 } 924 } 925 926 return isSameName; 927 } 928 929 function analyzeLabel(node: LabeledStatement): void { 930 // labels within the same scope are allowed to be duplicated, so label names need to have numbering information to distinguish them 931 upperLabel = upperLabel ? createLabel(node, current, upperLabel) : createLabel(node, current); 932 forEachChild(node, analyzeScope); 933 upperLabel = upperLabel?.parent; 934 } 935 936 function getScopeOfNode(node: Node): Scope | undefined { 937 if (!isIdentifier(node)) { 938 return undefined; 939 } 940 941 let sym: Symbol = checker.getSymbolAtLocation(node); 942 if (!sym) { 943 return undefined; 944 } 945 946 for (const scope of scopes) { 947 if (scope?.defs.has(sym)) { 948 return scope; 949 } 950 } 951 952 return undefined; 953 } 954 955 function analyzeImportEqualsDeclaration(node: ImportEqualsDeclaration): void { 956 let hasExport: boolean = false; 957 if (node.modifiers) { 958 for (const modifier of node.modifiers) { 959 if (modifier.kind === SyntaxKind.ExportKeyword) { 960 hasExport = true; 961 break; 962 } 963 } 964 } 965 if (hasExport) { 966 current.exportNames.add(node.name.text); 967 root.fileExportNames.add(node.name.text); 968 let sym: Symbol | undefined = checker.getSymbolAtLocation(node.name); 969 if (sym) { 970 current.addDefinition(sym, true); 971 } 972 } 973 forEachChild(node, analyzeScope); 974 } 975 976 function tryAddNoSymbolIdentifiers(node: Identifier): void { 977 if (!isIdentifier(node)) { 978 return; 979 } 980 981 // skip property in property access expression 982 if (NodeUtils.isPropertyAccessNode(node)) { 983 return; 984 } 985 986 const sym: Symbol | undefined = checker.getSymbolAtLocation(node); 987 if (!sym) { 988 current.mangledNames.add((node as Identifier).text); 989 } 990 } 991 992 function findNoSymbolIdentifiers(node: Node): void { 993 const noSymbolVisit = (targetNode: Node): void => { 994 if (!isIdentifier(targetNode)) { 995 forEachChild(targetNode, noSymbolVisit); 996 return; 997 } 998 tryAddNoSymbolIdentifiers(targetNode); 999 }; 1000 1001 noSymbolVisit(node); 1002 } 1003 } 1004} 1005 1006export = secharmony; 1007