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