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 {
17  Expression,
18  Identifier,
19  Node,
20  ObjectBindingPattern,
21  SourceFile,
22  TypeChecker,
23  TransformationContext,
24  TransformerFactory
25} from 'typescript';
26import {
27  Symbol,
28  SyntaxKind,
29  getModifiers,
30  isBinaryExpression,
31  isBindingElement,
32  isCallExpression,
33  isClassDeclaration,
34  isClassExpression,
35  isComputedPropertyName,
36  isConstructorDeclaration,
37  isElementAccessExpression,
38  isEnumMember,
39  isGetAccessor,
40  isIdentifier,
41  isMetaProperty,
42  isMethodDeclaration,
43  isMethodSignature,
44  isParameter,
45  isPrivateIdentifier,
46  isPropertyAccessExpression,
47  isPropertyAssignment,
48  isPropertyDeclaration,
49  isPropertySignature,
50  isQualifiedName,
51  isSetAccessor,
52  isVariableDeclaration,
53  visitEachChild
54} from 'typescript';
55import {
56  getViewPUClassProperties,
57  isParameterPropertyModifier,
58  isViewPUBasedClass,
59  visitEnumInitializer
60} from './OhsUtil';
61import { Extension } from '../common/type';
62import { MergedConfig } from '../initialization/ConfigResolver';
63
64export class NodeUtils {
65  public static isPropertyDeclarationNode(node: Node): boolean {
66    let parent: Node | undefined = node.parent;
67    if (!parent) {
68      return false;
69    }
70
71    /** eg: { 'name'' : 'akira' }, pass */
72    if (isPropertyAssignment(parent)) {
73      return parent.name === node;
74    }
75
76    if (isComputedPropertyName(parent) && parent.expression === node) {
77      return true;
78    }
79
80    /** object binding pattern */
81    if (isBindingElement(parent) && parent.propertyName === node) {
82      return true;
83    }
84
85    /** eg: interface/type inf { 'name' : string}, pass */
86    if (isPropertySignature(parent) && parent.name === node) {
87      return true;
88    }
89
90    /** eg: interface/type T1 { func(arg: string): number;} */
91    if (isMethodSignature(parent) && parent.name === node) {
92      return true;
93    }
94
95    /** eg: enum { xxx = 1}; */
96    if (isEnumMember(parent) && parent.name === node) {
97      return true;
98    }
99
100    /** class { private name= 1}; */
101    if (isPropertyDeclaration(parent) && parent.name === node) {
102      return true;
103    }
104
105    /** class {'getName': function() {}} let _ = { getName() [}} */
106    if (isMethodDeclaration(parent) && parent.name === node) {
107      return true;
108    }
109
110    if (isSetAccessor(parent) && parent.name === node) {
111      return true;
112    }
113
114    return isGetAccessor(parent) && parent.name === node;
115  }
116
117  public static isPropertyOrElementAccessNode(node: Node): boolean {
118    return this.isPropertyAccessNode(node) || this.isElementAccessNode(node) || false;
119  }
120
121  public static isPropertyAccessNode(node: Node): boolean {
122    let parent: Node | undefined = node.parent;
123    if (!parent) {
124      return false;
125    }
126
127    /** eg: a.b = 1 */
128    if (isPropertyAccessExpression(parent) && parent.name === node) {
129      return true;
130    }
131    if (isPrivateIdentifier(node) && NodeUtils.isInClassDeclaration(parent)) {
132      return NodeUtils.isInExpression(parent);
133    }
134    return isQualifiedName(parent) && parent.right === node;
135  }
136
137  private static isInClassDeclaration(node: Node | undefined): boolean {
138    if (!node) {
139      return false;
140    }
141
142    if (isClassDeclaration(node) || isClassExpression(node)) {
143      return true;
144    }
145
146    return NodeUtils.isInClassDeclaration(node.parent);
147  }
148  
149  public static isInClassDeclarationForTest(node: Node | undefined): boolean {
150    return NodeUtils.isInClassDeclaration(node);
151  }
152
153  private static isInExpression(node: Node | undefined): boolean {
154    return !!node && NodeUtils.isInOperator(node);
155  }
156
157  public static isInExpressionForTest(node: Node | undefined): boolean {
158    return NodeUtils.isInExpression(node);
159  }
160
161  private static isInOperator(node: Node): boolean {
162    return isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.InKeyword;
163  }
164
165  public static isInOperatorForTest(node: Node | undefined): boolean {
166    return NodeUtils.isInOperator(node);
167  }
168
169  public static isElementAccessNode(node: Node): boolean {
170    let parent: Node | undefined = node.parent;
171    if (!parent) {
172      return false;
173    }
174
175    return isElementAccessExpression(parent) && parent.argumentExpression === node;
176  }
177
178  public static isClassPropertyInConstructorParams(node: Node): boolean {
179    if (!isIdentifier(node)) {
180      return false;
181    }
182
183    if (!node.parent || !isParameter(node.parent)) {
184      return false;
185    }
186
187    const modifiers = getModifiers(node.parent);
188    if (!modifiers || modifiers.length === 0 || !modifiers.find(modifier => isParameterPropertyModifier(modifier))) {
189      return false;
190    }
191
192    return node.parent.parent && isConstructorDeclaration(node.parent.parent);
193  }
194
195  public static isClassPropertyInConstructorBody(node: Node, constructorParams: Set<string>): boolean {
196    if (!isIdentifier(node)) {
197      return false;
198    }
199
200    const id: string = node.escapedText.toString();
201    let curNode: Node = node.parent;
202    while (curNode) {
203      if (isConstructorDeclaration(curNode) && constructorParams.has(id)) {
204        return true;
205      }
206
207      curNode = curNode.parent;
208    }
209
210    return false;
211  }
212
213  public static isPropertyNode(node: Node): boolean {
214    if (this.isPropertyOrElementAccessNode(node)) {
215      return true;
216    }
217
218    return this.isPropertyDeclarationNode(node);
219  }
220
221  public static isObjectBindingPatternAssignment(node: ObjectBindingPattern): boolean {
222    if (!node || !node.parent || !isVariableDeclaration(node.parent)) {
223      return false;
224    }
225
226    const initializer: Expression = node.parent.initializer;
227    return initializer && isCallExpression(initializer);
228  }
229
230  public static isDeclarationFile(node: SourceFile): boolean {
231    return node.isDeclarationFile;
232  }
233
234  public static getSourceFileOfNode(node: Node): SourceFile {
235    while (node && node.kind !== SyntaxKind.SourceFile) {
236      node = node.parent;
237    }
238    return <SourceFile>node;
239  }
240
241  public static isDETSFile(node: Node | undefined): boolean {
242    return !!node && NodeUtils.getSourceFileOfNode(node).fileName.endsWith(Extension.DETS);
243  }
244
245  public static isNewTargetNode(node: Identifier): boolean {
246    if (isMetaProperty(node.parent) && node.parent.keywordToken === SyntaxKind.NewKeyword && node.escapedText === 'target') {
247      return true;
248    }
249    return false;
250  }
251
252  public static findSymbolOfIdentifier(checker: TypeChecker, node: Identifier): Symbol | undefined {
253    let sym: Symbol | undefined = checker.getSymbolAtLocation(node);
254    if (!sym || (sym && sym.name !== 'default')) {
255      return sym;
256    }
257    /* Handle default exports, eg. export default class Ability {};
258       The expected symbol we want to find to obfuscate is named "Ability",
259       but `getSymbolAtLocation` will return the symbol named "default", so we need to continue to search.
260    */
261    let localSyms: Symbol[] = checker.getSymbolsInScope(node, sym.flags);
262    for (let i = 0; i < localSyms.length; i++) {
263      const localSym = localSyms[i];
264      // `localSym` named "Ability" has property `exportSymbol` named "default" that we find by `getSymbolAtLocation`,
265      // So the `localSym` is what we want to obfuscate.
266      if (localSym && localSym.name === node.text && localSym.exportSymbol === sym) {
267        sym = localSym;
268        break;
269      }
270    }
271    return sym;
272  }
273}
274
275/**
276 * When enabling property obfuscation, collect the properties of struct.
277 * When enabling property obfuscation and the compilation output is a TS file,
278 * collect the Identifier names in the initialization expressions of enum members.
279 */
280export function collectReservedNameForObf(obfuscationConfig: MergedConfig | undefined, shouldTransformToJs: boolean): TransformerFactory<SourceFile> {
281  const disableObf = obfuscationConfig?.options === undefined || obfuscationConfig.options.disableObfuscation;
282  const enablePropertyObf = obfuscationConfig?.options.enablePropertyObfuscation;
283  // process.env.compiler === 'on': indicates that during the Webpack packaging process,
284  // the code is executed here for the first time.
285  // During the Webpack packaging process, this step will be executed twice,
286  // but only the first time will it perform subsequent operations to prevent repetition.
287  const shouldCollect = (process.env.compiler === 'on' || process.env.compileTool === 'rollup') &&
288                        !disableObf && enablePropertyObf;
289
290  return (context: TransformationContext) => {
291    return (node: SourceFile) => {
292      if (shouldCollect) {
293        node = visitEachChild(node, collectReservedNames, context);
294      }
295      return node;
296    };
297
298    function collectReservedNames(node: Node): Node {
299      // collect properties of struct
300      if (isClassDeclaration(node) && isViewPUBasedClass(node)) {
301        getViewPUClassProperties(node);
302      }
303
304      // collect enum properties
305      if (!shouldTransformToJs && isEnumMember(node) && node.initializer) {
306        node.initializer.forEachChild(visitEnumInitializer);
307        return node;
308      }
309
310      return visitEachChild(node, collectReservedNames, context);
311    }
312  };
313}