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