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  ImportEqualsDeclaration,
50  ImportSpecifier,
51  InterfaceDeclaration,
52  LabeledStatement,
53  ModuleDeclaration,
54  NamespaceExport,
55  Node,
56  ObjectBindingPattern,
57  ObjectLiteralExpression,
58  ParameterDeclaration,
59  SourceFile,
60  Symbol,
61  SymbolTable,
62  TypeAliasDeclaration,
63  TypeChecker,
64  TypeElement
65} from 'typescript';
66
67import {NodeUtils} from './NodeUtils';
68import {isParameterPropertyModifier, isViewPUBasedClass} from './OhsUtil';
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
450        case SyntaxKind.ImportEqualsDeclaration:
451          analyzeImportEqualsDeclaration(node as ImportEqualsDeclaration);
452          break;
453
454        default:
455          forEachChild(node, analyzeScope);
456          break;
457      }
458    }
459
460    function analyzeImportNames(node: ImportSpecifier): void {
461      try {
462        const propetyNameNode: Identifier | undefined = node.propertyName;
463        if (exportObfuscation && propetyNameNode && isIdentifier(propetyNameNode)) {
464          let propertySymbol = checker.getSymbolAtLocation(propetyNameNode);
465          if (!propertySymbol) {
466            noSymbolIdentifier.add(propetyNameNode.text);
467          } else {
468            current.addDefinition(propertySymbol);
469          }
470
471          const nameSymbol = checker.getSymbolAtLocation(node.name);
472          if (nameSymbol) {
473            current.addDefinition(nameSymbol);
474          }
475        } else {
476          const nameText = propetyNameNode ? propetyNameNode.text : node.name.text;
477          current.importNames.add(nameText);
478          root.fileImportNames.add(nameText);
479        }
480        forEachChild(node, analyzeScope);
481      } catch (e) {
482        console.error(e);
483      }
484    }
485
486    /** example
487     * const { x1, y: customY, z = 0 }: { x: number; y?: number; z?: number } = { x: 1, y: 2 };
488     * bindingElement.name is x1 for the first element.
489     * bindingElement.name is customY for the second element.
490     */
491    function analyzeObjectBindingPatternRequire(node: ObjectBindingPattern): void {
492      if (!NodeUtils.isObjectBindingPatternAssignment(node)) {
493        forEachChild(node, analyzeScope);
494        return;
495      }
496
497      if (!node.elements) {
498        return;
499      }
500
501      node.elements.forEach((bindingElement) => {
502        if (!bindingElement) {
503          return;
504        }
505
506        findNoSymbolIdentifiers(bindingElement);
507
508        if (!bindingElement.name || !isIdentifier(bindingElement.name)) {
509          return;
510        }
511
512        if (bindingElement.propertyName) {
513          return;
514        }
515
516        current.importNames.add(bindingElement.name.text);
517        root.fileImportNames.add(bindingElement.name.text);
518      });
519    }
520
521    function analyzeObjectLiteralExpression(node: ObjectLiteralExpression): void {
522      let scopeName: string = '$' + current.children.length;
523      current = new Scope(scopeName, node, ScopeKind.OBJECT_LITERAL, false, current);
524      scopes.push(current);
525
526      addSymbolInScope(node);
527      forEachChild(node, analyzeScope);
528      current = current.parent || current;
529    }
530
531    function analyzeExportNames(node: ExportSpecifier): void {
532      // get export names.
533      current.exportNames.add(node.name.text);
534      root.fileExportNames.add(node.name.text);
535      addExportSymbolInScope(node);
536      const propetyNameNode: Identifier | undefined = node.propertyName;
537      if (exportObfuscation && propetyNameNode && isIdentifier(propetyNameNode)) {
538        let propertySymbol = checker.getSymbolAtLocation(propetyNameNode);
539        if (!propertySymbol) {
540          noSymbolIdentifier.add(propetyNameNode.text);
541        }
542      }
543      forEachChild(node, analyzeScope);
544    }
545
546    function analyzeNamespaceExport(node: NamespaceExport): void {
547      if (!exportObfuscation) {
548        return;
549      }
550
551      let symbol = checker.getSymbolAtLocation(node.name);
552      if (symbol) {
553        current.addDefinition(symbol, true);
554      }
555    }
556
557    function analyzeBreakOrContinue(node: BreakOrContinueStatement): void {
558      let labelName: string = node?.label?.text ?? '';
559      let label: Label = findTargetLabel(labelName);
560      if (!label) {
561        return;
562      }
563
564      if (node.label) {
565        label?.refs.push(node.label);
566      }
567
568      forEachChild(node, analyzeScope);
569    }
570
571    function findTargetLabel(labelName: string): Label | null {
572      if (!labelName) {
573        return null;
574      }
575
576      let label: Label | undefined = upperLabel;
577      // avoid loop
578      while (label && label?.name !== labelName) {
579        label = label?.parent;
580      }
581
582      return label;
583    }
584
585    function analyzeSourceFile(node: SourceFile): void {
586      let scopeName: string = '';
587      root = new Scope(scopeName, node, ScopeKind.GLOBAL, true);
588      root.fileExportNames = new Set<string>();
589      root.fileImportNames = new Set<string>();
590      current = root;
591      scopes.push(current);
592      // locals of a node(scope) is symbol that defines in current scope(node).
593      addSymbolInScope(node);
594      forEachChild(node, analyzeScope);
595      current = current.parent || current;
596      extractImportExports();
597    }
598
599    function analyzeCatchClause(node: CatchClause): void {
600      let scopeName: string = '$' + current.children.length;
601      current = new Scope(scopeName, node, ScopeKind.CATCH, false, current);
602      scopes.push(current);
603      // add in catch declaration.
604      addSymbolInScope(node);
605      if (node.block) {
606        // add in block declaration.
607        addSymbolInScope(node.block);
608      }
609
610      forEachChild(node, analyzeScope);
611      current = current.parent || current;
612    }
613
614    function extractImportExports(): void {
615      for (const def of current.defs) {
616        if (def.exportSymbol) {
617          if (!current.exportNames.has(def.name)) {
618            current.exportNames.add(def.name);
619            root.fileExportNames.add(def.name);
620          }
621          const name: string = def.exportSymbol.name;
622          if (!current.exportNames.has(name)) {
623            current.exportNames.add(name);
624            root.fileExportNames.add(def.name);
625          }
626        }
627      }
628    }
629
630    function analyzeTypeAliasDeclaration(node: TypeAliasDeclaration): void {
631      let scopeName: string = node.name.text ?? '$' + current.children.length;
632      current = new Scope(scopeName, node, ScopeKind.INTERFACE, true, current);
633      scopes.push(current);
634      addSymbolInScope(node);
635      forEachChild(node, analyzeScope);
636      current = current.parent || current;
637    }
638
639    /**
640     * namespace ns {
641     *     ...
642     * }
643     * @param node
644     */
645    function analyzeModule(node: ModuleDeclaration): void {
646      /**
647       * if it is an anonymous scope, generate the scope name with a number,
648       * which is based on the order of its child scopes in the upper scope
649       */
650      let scopeName: string = node.name.text ?? '$' + current.children.length;
651      current = new Scope(scopeName, node, ScopeKind.MODULE, true, current);
652      scopes.push(current);
653      addSymbolInScope(node);
654      node.forEachChild((sub: Node) => {
655        if (isIdentifier(sub)) {
656          return;
657        }
658        analyzeScope(sub);
659      });
660      current = current.parent || current;
661    }
662
663    /**
664     * exclude constructor's parameter witch should be treated as property, example:
665     *  constructor(public name){}, name should be treated as property
666     * @param node
667     */
668    function excludeConstructorParameter(node: Node): void {
669      if (!isConstructorDeclaration(node)) {
670        return;
671      }
672
673      const visitParam = (param: ParameterDeclaration): void => {
674        const modifiers = getModifiers(param);
675        if (!modifiers || modifiers.length <= 0) {
676          return;
677        }
678
679        const findRet = modifiers.find(modifier => isParameterPropertyModifier(modifier));
680        if (!isIdentifier(param.name) || findRet === undefined) {
681          return;
682        }
683
684        current.defs.forEach((def) => {
685          if (isIdentifier(param.name) && (def.name === param.name.text)) {
686            current.defs.delete(def);
687            current.mangledNames.add(def.name);
688          }
689        });
690      };
691
692      node.parameters.forEach((param) => {
693        visitParam(param);
694      });
695    }
696
697    /**
698     * function func(param1...) {
699     *     ...
700     * }
701     * @param node
702     */
703    function analyzeFunctionLike(node: FunctionLikeDeclaration): void {
704      // For example, the constructor of the StructDeclaration, inserted by arkui, will add a virtual attribute.
705      // @ts-ignore
706      if (getOriginalNode(node).virtual) {
707        return;
708      }
709      let scopeName: string = (node?.name as Identifier)?.text ?? '$' + current.children.length;
710      let loc: string = current?.loc ? current.loc + '#' + scopeName : scopeName;
711      let overloading: boolean = false;
712      for (const sub of current.children) {
713        if (sub.loc === loc) {
714          overloading = true;
715          current = sub;
716          break;
717        }
718      }
719
720      if (!overloading) {
721        current = new Scope(scopeName, node, ScopeKind.FUNCTION, true, current);
722        scopes.push(current);
723      }
724
725      let symbol: Symbol;
726      if ((isFunctionExpression(node) || isArrowFunction(node)) && isVariableDeclaration(node.parent)) {
727        symbol = checker.getSymbolAtLocation(node.name ? node.name : node.parent.name);
728      } else {
729        if (isFunctionDeclaration(node)) {
730          symbol = NodeUtils.findSymbolOfIdentifier(checker, node.name);
731        } else {
732          symbol = checker.getSymbolAtLocation(node.name);
733        }
734      }
735      if (symbol) {
736        Reflect.set(symbol, 'isFunction', true);
737      }
738
739      addSymbolInScope(node);
740      /**
741       * {
742       *   get name(): "INT";
743       *   set orignal(): 0;
744       * }
745       * // the above getaccessor and setaccessor were obfuscated as identifiers.
746       */
747      if (!(isGetAccessor(node) || isSetAccessor(node)) && symbol && current.parent && !current.parent.defs.has(symbol)) {
748        /*
749          Handle the case when `FunctionLikeDeclaration` node is as initializer of variable declaration.
750          eg. const foo = function bar() {};
751          The `current` scope is the function's scope, the `current.parent` scope is where the function is defined.
752          `foo` has already added in the parent scope, we need to add `bar` here too.
753         */
754        current.parent.defs.add(symbol);
755      }
756
757      if (isFunctionDeclaration(node) || isMethodDeclaration(node)) {
758        // function declaration requires skipping function names
759        node.forEachChild((sub: Node) => {
760          if (isIdentifier(sub)) {
761            tryAddNoSymbolIdentifiers(sub);
762            return;
763          }
764
765          analyzeScope(sub);
766        });
767      } else {
768        forEachChild(node, analyzeScope);
769      }
770
771      excludeConstructorParameter(node);
772      current = current.parent || current;
773    }
774
775    function analyzeSwitch(node: CaseBlock): void {
776      let scopeName: string = '$' + current.children.length;
777      current = new Scope(scopeName, node, ScopeKind.SWITCH, false, current);
778      scopes.push(current);
779      addSymbolInScope(node);
780      forEachChild(node, analyzeScope);
781      current = current.parent || current;
782    }
783
784    /**
785     * ES6+ class like scope, The members of a class aren't not allow to rename in rename identifiers transformer, but
786     * rename in rename properties transformer.
787     *
788     * @param node
789     */
790    function analyzeClassLike(node: ClassLikeDeclaration): void {
791      if (isClassDeclaration(node) && isViewPUBasedClass(node)) {
792        reservedNames.add(node.name.text);
793      }
794
795      try {
796        let scopeName: string = node?.name?.text ?? '$' + current.children.length;
797        current = new Scope(scopeName, node, ScopeKind.CLASS, true, current);
798        scopes.push(current);
799        addSymbolInScope(node);
800        // Class members are seen as attribute names, and  the reference of external symbols can be renamed as the same
801        node.members?.forEach((elm: ClassElement) => {
802          // @ts-ignore
803          if (elm?.symbol && !getOriginalNode(elm).virtual) {
804            current.addDefinition(elm.symbol);
805          }
806        });
807
808        forEachChild(node, analyzeScope);
809      } catch (e) {
810        console.error(e);
811      }
812
813      current = current.parent || current;
814    }
815
816    function analyzeForLike(node: ForLikeStatement): void {
817      let scopeName: string = '$' + current.children.length;
818      current = new Scope(scopeName, node, ScopeKind.FOR, false, current);
819      scopes.push(current);
820      addSymbolInScope(node);
821      forEachChild(node, analyzeScope);
822      current = current.parent || current;
823    }
824
825    function analyzeBlock(node: Node): void {
826      // when block is body of a function
827      if ((isFunctionScope(current) && isFunctionLike(node.parent)) || isCatchClause(node.parent)) {
828        // skip direct block scope in function scope
829        forEachChild(node, analyzeScope);
830        return;
831      }
832
833      let scopeName: string = '$' + current.children.length;
834      current = new Scope(scopeName, node, ScopeKind.BLOCK, false, current);
835      scopes.push(current);
836      addSymbolInScope(node);
837      forEachChild(node, analyzeScope);
838      current = current.parent || current;
839    }
840
841    function analyzeInterface(node: InterfaceDeclaration): void {
842      let scopeName: string = node.name.text;
843      current = new Scope(scopeName, node, ScopeKind.INTERFACE, true, current);
844      scopes.push(current);
845      try {
846        addSymbolInScope(node);
847      } catch (e) {
848        console.error('');
849      }
850
851      node.members?.forEach((elm: TypeElement) => {
852        if (elm?.symbol) {
853          current.addDefinition(elm.symbol);
854        }
855      });
856
857      forEachChild(node, analyzeScope);
858      current = current.parent || current;
859    }
860
861    function analyzeEnum(node: EnumDeclaration): void {
862      let scopeName: string = node.name.text;
863      current = new Scope(scopeName, node, ScopeKind.ENUM, true, current);
864      scopes.push(current);
865      for (const member of node.members) {
866        if (member.symbol) {
867          current.addDefinition(member.symbol);
868        }
869      }
870
871      forEachChild(node, analyzeScope);
872      current = current.parent || current;
873    }
874
875    function analyzeSymbol(node: Identifier): void {
876      // ignore all identifiers that treat as property in property access
877      if (NodeUtils.isPropertyAccessNode(node)) {
878        return;
879      }
880
881      let symbol: Symbol = null;
882
883      try {
884        symbol = NodeUtils.findSymbolOfIdentifier(checker, node);
885      } catch (e) {
886        console.error(e);
887        return;
888      }
889
890      if (!symbol) {
891        current.mangledNames.add(node.text);
892        return;
893      }
894
895      // ignore all identifiers that treat as property in property declaration
896      if (NodeUtils.isPropertyDeclarationNode(node)) {
897        return;
898      }
899
900      // add def symbol that don't found in current defs.
901      addSymbolIntoDefsIfNeeded(node, symbol, current.defs);
902    }
903
904    function addSymbolIntoDefsIfNeeded(node: Identifier, symbol: Symbol, currentDefs: Set<Symbol>): boolean {
905      // process a new def not in currentDefs
906      let isSameName: boolean = false;
907      for (const def of currentDefs) {
908        if (def.name === node.text) {
909          isSameName = true;
910          break;
911        }
912      }
913
914      if (isSameName) {
915        // exclude the possibility of external symbols, as those with duplicate names have been added to currentDefs (this avoids the possibility of omissions)
916        if (!currentDefs.has(symbol)) {
917          currentDefs.add(symbol);
918        }
919
920        if (symbol.exportSymbol && !currentDefs.has(symbol.exportSymbol)) {
921          Reflect.set(symbol, 'obfuscateAsProperty', true);
922          currentDefs.add(symbol);
923        }
924      }
925
926      return isSameName;
927    }
928
929    function analyzeLabel(node: LabeledStatement): void {
930      // labels within the same scope are allowed to be duplicated, so label names need to have numbering information to distinguish them
931      upperLabel = upperLabel ? createLabel(node, current, upperLabel) : createLabel(node, current);
932      forEachChild(node, analyzeScope);
933      upperLabel = upperLabel?.parent;
934    }
935
936    function getScopeOfNode(node: Node): Scope | undefined {
937      if (!isIdentifier(node)) {
938        return undefined;
939      }
940
941      let sym: Symbol = checker.getSymbolAtLocation(node);
942      if (!sym) {
943        return undefined;
944      }
945
946      for (const scope of scopes) {
947        if (scope?.defs.has(sym)) {
948          return scope;
949        }
950      }
951
952      return undefined;
953    }
954
955    function analyzeImportEqualsDeclaration(node: ImportEqualsDeclaration): void {
956      let hasExport: boolean = false;
957      if (node.modifiers) {
958        for (const modifier of node.modifiers) {
959          if (modifier.kind === SyntaxKind.ExportKeyword) {
960            hasExport = true;
961            break;
962          }
963        }
964      }
965      if (hasExport) {
966        current.exportNames.add(node.name.text);
967        root.fileExportNames.add(node.name.text);
968        let sym: Symbol | undefined = checker.getSymbolAtLocation(node.name);
969        if (sym) {
970          current.addDefinition(sym, true);
971        }
972      }
973      forEachChild(node, analyzeScope);
974    }
975
976    function tryAddNoSymbolIdentifiers(node: Identifier): void {
977      if (!isIdentifier(node)) {
978        return;
979      }
980
981      // skip property in property access expression
982      if (NodeUtils.isPropertyAccessNode(node)) {
983        return;
984      }
985
986      const sym: Symbol | undefined = checker.getSymbolAtLocation(node);
987      if (!sym) {
988        current.mangledNames.add((node as Identifier).text);
989      }
990    }
991
992    function findNoSymbolIdentifiers(node: Node): void {
993      const noSymbolVisit = (targetNode: Node): void => {
994        if (!isIdentifier(targetNode)) {
995          forEachChild(targetNode, noSymbolVisit);
996          return;
997        }
998        tryAddNoSymbolIdentifiers(targetNode);
999      };
1000
1001      noSymbolVisit(node);
1002    }
1003  }
1004}
1005
1006export = secharmony;
1007