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