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