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