• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import type {Expression, Identifier, Node, ObjectBindingPattern, SourceFile, TypeChecker} from 'typescript';
17import {
18  Symbol,
19  SyntaxKind,
20  getModifiers,
21  isBinaryExpression,
22  isBindingElement,
23  isCallExpression,
24  isClassDeclaration,
25  isClassExpression,
26  isComputedPropertyName,
27  isConstructorDeclaration,
28  isElementAccessExpression,
29  isEnumMember,
30  isGetAccessor,
31  isIdentifier,
32  isMetaProperty,
33  isMethodDeclaration,
34  isMethodSignature,
35  isParameter,
36  isPrivateIdentifier,
37  isPropertyAccessExpression,
38  isPropertyAssignment,
39  isPropertyDeclaration,
40  isPropertySignature,
41  isQualifiedName,
42  isSetAccessor,
43  isVariableDeclaration,
44} from 'typescript';
45import { isParameterPropertyModifier } from './OhsUtil';
46import { Extension } from '../common/type';
47
48export class NodeUtils {
49  public static isPropertyDeclarationNode(node: Node): boolean {
50    let parent: Node | undefined = node.parent;
51    if (!parent) {
52      return false;
53    }
54
55    /** eg: { 'name'' : 'akira' }, pass */
56    if (isPropertyAssignment(parent)) {
57      return parent.name === node;
58    }
59
60    if (isComputedPropertyName(parent) && parent.expression === node) {
61      return true;
62    }
63
64    /** object binding pattern */
65    if (isBindingElement(parent) && parent.propertyName === node) {
66      return true;
67    }
68
69    /** eg: interface/type inf { 'name' : string}, pass */
70    if (isPropertySignature(parent) && parent.name === node) {
71      return true;
72    }
73
74    /** eg: interface/type T1 { func(arg: string): number;} */
75    if (isMethodSignature(parent) && parent.name === node) {
76      return true;
77    }
78
79    /** eg: enum { xxx = 1}; */
80    if (isEnumMember(parent) && parent.name === node) {
81      return true;
82    }
83
84    /** class { private name= 1}; */
85    if (isPropertyDeclaration(parent) && parent.name === node) {
86      return true;
87    }
88
89    /** class {'getName': function() {}} let _ = { getName() [}} */
90    if (isMethodDeclaration(parent) && parent.name === node) {
91      return true;
92    }
93
94    if (isSetAccessor(parent) && parent.name === node) {
95      return true;
96    }
97
98    return isGetAccessor(parent) && parent.name === node;
99  }
100
101  public static isPropertyOrElementAccessNode(node: Node): boolean {
102    return this.isPropertyAccessNode(node) || this.isElementAccessNode(node) || false;
103  }
104
105  public static isPropertyAccessNode(node: Node): boolean {
106    let parent: Node | undefined = node.parent;
107    if (!parent) {
108      return false;
109    }
110
111    /** eg: a.b = 1 */
112    if (isPropertyAccessExpression(parent) && parent.name === node) {
113      return true;
114    }
115    if (isPrivateIdentifier(node) && NodeUtils.isInClassDeclaration(parent)) {
116      return NodeUtils.isInExpression(parent);
117    }
118    return isQualifiedName(parent) && parent.right === node;
119  }
120
121  private static isInClassDeclaration(node: Node | undefined): boolean {
122    if (!node) {
123      return false;
124    }
125
126    if (isClassDeclaration(node) || isClassExpression(node)) {
127      return true;
128    }
129
130    return NodeUtils.isInClassDeclaration(node.parent);
131  }
132
133  public static isInClassDeclarationForTest(node: Node | undefined): boolean {
134    return NodeUtils.isInClassDeclaration(node);
135  }
136
137  private static isInExpression(node: Node | undefined): boolean {
138    return !!node && NodeUtils.isInOperator(node);
139  }
140
141  public static isInExpressionForTest(node: Node | undefined): boolean {
142    return NodeUtils.isInExpression(node);
143  }
144
145  private static isInOperator(node: Node): boolean {
146    return isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.InKeyword;
147  }
148
149  public static isInOperatorForTest(node: Node | undefined): boolean {
150    return NodeUtils.isInOperator(node);
151  }
152
153  public static isElementAccessNode(node: Node): boolean {
154    let parent: Node | undefined = node.parent;
155    if (!parent) {
156      return false;
157    }
158
159    return isElementAccessExpression(parent) && parent.argumentExpression === node;
160  }
161
162  public static isClassPropertyInConstructorParams(node: Node): boolean {
163    if (!isIdentifier(node)) {
164      return false;
165    }
166
167    if (!node.parent || !isParameter(node.parent)) {
168      return false;
169    }
170
171    const modifiers = getModifiers(node.parent);
172    if (!modifiers || modifiers.length === 0 || !modifiers.find(modifier => isParameterPropertyModifier(modifier))) {
173      return false;
174    }
175
176    return node.parent.parent && isConstructorDeclaration(node.parent.parent);
177  }
178
179  public static isClassPropertyInConstructorBody(node: Node, constructorParams: Set<string>): boolean {
180    if (!isIdentifier(node)) {
181      return false;
182    }
183
184    const id: string = node.escapedText.toString();
185    let curNode: Node = node.parent;
186    while (curNode) {
187      if (isConstructorDeclaration(curNode) && constructorParams.has(id)) {
188        return true;
189      }
190
191      curNode = curNode.parent;
192    }
193
194    return false;
195  }
196
197  public static isPropertyNode(node: Node): boolean {
198    if (this.isPropertyOrElementAccessNode(node)) {
199      return true;
200    }
201
202    return this.isPropertyDeclarationNode(node);
203  }
204
205  public static isObjectBindingPatternAssignment(node: ObjectBindingPattern): boolean {
206    if (!node || !node.parent || !isVariableDeclaration(node.parent)) {
207      return false;
208    }
209
210    const initializer: Expression = node.parent.initializer;
211    return initializer && isCallExpression(initializer);
212  }
213
214  public static isDeclarationFile(node: SourceFile): boolean {
215    return node.isDeclarationFile;
216  }
217
218  public static getSourceFileOfNode(node: Node): SourceFile {
219    while (node && node.kind !== SyntaxKind.SourceFile) {
220      node = node.parent;
221    }
222    return <SourceFile>node;
223  }
224
225  public static isDETSFile(node: Node | undefined): boolean {
226    return !!node && NodeUtils.getSourceFileOfNode(node).fileName.endsWith(Extension.DETS);
227  }
228
229  public static isNewTargetNode(node: Identifier): boolean {
230    if (isMetaProperty(node.parent) && node.parent.keywordToken === SyntaxKind.NewKeyword && node.escapedText === 'target') {
231      return true;
232    }
233    return false;
234  }
235
236  public static findSymbolOfIdentifier(checker: TypeChecker, node: Identifier): Symbol | undefined {
237    let sym: Symbol | undefined = checker.getSymbolAtLocation(node);
238    if (!sym || (sym && sym.name !== 'default')) {
239      return sym;
240    }
241    /* Handle default exports, eg. export default class Ability {};
242       The expected symbol we want to find to obfuscate is named "Ability",
243       but `getSymbolAtLocation` will return the symbol named "default", so we need to continue to search.
244    */
245    let localSyms: Symbol[] = checker.getSymbolsInScope(node, sym.flags);
246    for (let i = 0; i < localSyms.length; i++) {
247      const localSym = localSyms[i];
248      // `localSym` named "Ability" has property `exportSymbol` named "default" that we find by `getSymbolAtLocation`,
249      // So the `localSym` is what we want to obfuscate.
250      if (localSym && localSym.name === node.text && localSym.exportSymbol === sym) {
251        sym = localSym;
252        break;
253      }
254    }
255    return sym;
256  }
257}