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