• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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