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 {
17  forEachChild,
18  isBinaryExpression,
19  isCallExpression,
20  isClassDeclaration,
21  isComputedPropertyName,
22  isConstructorDeclaration,
23  isEnumDeclaration,
24  isIdentifier,
25  isObjectLiteralExpression,
26  isParameter,
27  isPropertyAccessExpression,
28  isPropertyAssignment,
29  isPropertyDeclaration,
30  isStructDeclaration,
31  isStringLiteral,
32  isTypeLiteralNode,
33  isVariableStatement,
34  SyntaxKind,
35  isExpressionStatement,
36  isClassExpression,
37  getModifiers,
38  isGetAccessor,
39  isSetAccessor,
40  isShorthandPropertyAssignment,
41  isSpreadAssignment,
42  isMethodDeclaration,
43  isGetAccessorDeclaration,
44  isAccessor,
45  isTypeNode
46} from 'typescript';
47
48import type {
49  ClassDeclaration,
50  ClassExpression,
51  ElementAccessExpression,
52  EnumDeclaration,
53  Expression,
54  GetAccessorDeclaration,
55  HeritageClause,
56  Identifier,
57  InterfaceDeclaration,
58  MethodDeclaration,
59  Modifier,
60  Node,
61  NodeArray,
62  ObjectLiteralExpression,
63  PropertyAssignment,
64  PropertyName,
65  SetAccessorDeclaration,
66  ShorthandPropertyAssignment,
67  Statement,
68  StructDeclaration,
69  TypeAliasDeclaration
70} from 'typescript';
71
72import { ApiExtractor } from '../common/ApiExtractor';
73import { UnobfuscationCollections } from './CommonCollections';
74import { NodeUtils } from './NodeUtils';
75
76export const stringPropsSet: Set<string> = new Set();
77/**
78 * The struct properties may be initialized in other files, but the properties in the struct definition are not obfuscated.
79 * So the whitelist of struct properties is collected during the project scanning process.
80 */
81export const structPropsSet: Set<string> = new Set();
82
83/**
84 * Add enum elements into whitelist when compiling har module to avoid obfuscating enum elements
85 * since enum elements in js file cannot be obfuscated properly.
86 */
87export const enumPropsSet: Set<string> = new Set();
88
89/**
90 * Collect the original name of export elements to ensure we can collect their properties
91 */
92export const exportOriginalNameSet: Set<string> = new Set();
93
94function containViewPU(heritageClauses: NodeArray<HeritageClause>): boolean {
95  if (!heritageClauses) {
96    return false;
97  }
98
99  let hasViewPU: boolean = false;
100  heritageClauses.forEach(
101    (heritageClause) => {
102      if (!heritageClause || !heritageClause.types) {
103        return;
104      }
105
106      const types = heritageClause.types;
107      types.forEach((typeExpression) => {
108        if (!typeExpression || !typeExpression.expression) {
109          return;
110        }
111
112        const expression = typeExpression.expression;
113        if (isIdentifier(expression) && expression.text === 'ViewPU') {
114          hasViewPU = true;
115        }
116      });
117    });
118
119  return hasViewPU;
120}
121
122/**
123 * used to ignore user defined ui component class property name
124 * @param classNode
125 */
126export function isViewPUBasedClass(classNode: ClassDeclaration | undefined): boolean {
127  if (!classNode) {
128    return false;
129  }
130
131  if (!isClassDeclaration(classNode)) {
132    return false;
133  }
134
135  const heritageClause = classNode.heritageClauses;
136  return containViewPU(heritageClause);
137}
138
139export function collectPropertyNamesAndStrings(memberName: PropertyName, propertySet: Set<string>): void {
140  if (isIdentifier(memberName)) {
141    propertySet.add(memberName.text);
142  }
143
144  if (isStringLiteral(memberName)) {
145    propertySet.add(memberName.text);
146    stringPropsSet.add(memberName.text);
147  }
148
149  if (isComputedPropertyName(memberName) && isStringLiteral(memberName.expression)) {
150    propertySet.add(memberName.expression.text);
151    stringPropsSet.add(memberName.expression.text);
152  }
153}
154
155export function getElementAccessExpressionProperties(elementAccessExpressionNode: ElementAccessExpression, propertySet: Set<string>): void {
156  if (!elementAccessExpressionNode || !elementAccessExpressionNode.argumentExpression) {
157    return;
158  }
159
160  if (isStringLiteral(elementAccessExpressionNode.argumentExpression)) {
161    stringPropsSet.add(elementAccessExpressionNode.argumentExpression.text);
162  }
163}
164
165export function getTypeAliasProperties(typeAliasNode: TypeAliasDeclaration, propertySet: Set<string>): void {
166  if (!typeAliasNode || !typeAliasNode.type || !isTypeLiteralNode(typeAliasNode.type)) {
167    return;
168  }
169
170  typeAliasNode.type.members.forEach((member) => {
171    if (!member || !member.name) {
172      return;
173    }
174    let memberName: PropertyName = member.name;
175    collectPropertyNamesAndStrings(memberName, propertySet);
176  });
177}
178
179/**
180 * export interface interfaceName {
181 *  a1: number;
182 *  "a2": number;
183 *  ["a3"]: number;
184 * }
185 */
186
187export function getInterfaceProperties(interfaceNode: InterfaceDeclaration, propertySet: Set<string>): void {
188  if (!interfaceNode || !interfaceNode.members) {
189    return;
190  }
191
192  interfaceNode.members.forEach((member) => {
193    if (!member || !member.name) {
194      return;
195    }
196
197    let memberName: PropertyName = member.name;
198    collectPropertyNamesAndStrings(memberName, propertySet);
199  });
200}
201
202export function isParameterPropertyModifier(modifier: Modifier): boolean {
203  if (modifier.kind === SyntaxKind.PublicKeyword ||
204    modifier.kind === SyntaxKind.PrivateKeyword ||
205    modifier.kind === SyntaxKind.ProtectedKeyword ||
206    modifier.kind === SyntaxKind.ReadonlyKeyword) {
207    return true;
208  }
209  return false;
210}
211
212export function getClassProperties(classNode: ClassDeclaration | ClassExpression | StructDeclaration, propertySet: Set<string>): void {
213  if (!classNode || !classNode.members) {
214    return;
215  }
216
217  if (isStructDeclaration(classNode)) {
218    getStructProperties(classNode, structPropsSet);
219  }
220  traverseMembersOfClass(classNode, propertySet);
221  return;
222}
223
224function traverseMembersOfClass(classNode: ClassDeclaration | ClassExpression | StructDeclaration, propertySet: Set<string>): void {
225  classNode.members.forEach((member) => {
226    if (!member) {
227      return;
228    }
229
230    const memberName: PropertyName = member.name;
231    if (memberName) {
232      collectPropertyNamesAndStrings(memberName, propertySet);
233    }
234
235    if (isConstructorDeclaration(member) && member.parameters) {
236      member.parameters.forEach((parameter) => {
237        const modifiers = getModifiers(parameter);
238        if (isParameter(parameter) && modifiers && modifiers.length > 0) {
239          if (parameter.name && isIdentifier(parameter.name)) {
240            let hasParameterPropertyModifier = modifiers.find(modifier => isParameterPropertyModifier(modifier)) !== undefined;
241            if (hasParameterPropertyModifier) {
242              propertySet.add(parameter.name.text);
243              ApiExtractor.mConstructorPropertySet?.add(parameter.name.text);
244            }
245          }
246          processMemberInitializer(parameter.initializer, propertySet);
247        }
248      });
249
250      if (member.body) {
251        member.body.statements.forEach((statement) => {
252          if (isExpressionStatement(statement) && isBinaryExpression(statement.expression) &&
253            statement.expression.operatorToken.kind === SyntaxKind.EqualsToken) {
254            processMemberInitializer(statement.expression.right, propertySet);
255          }
256        });
257      }
258    }
259
260    if (!isPropertyDeclaration(member) || !member.initializer) {
261      return;
262    }
263    processMemberInitializer(member.initializer, propertySet);
264  });
265  return;
266}
267
268function processMemberInitializer(memberInitializer: Expression | undefined, propertySet: Set<string>): void {
269  if (!memberInitializer) {
270    return;
271  }
272
273  if (isObjectLiteralExpression(memberInitializer)) {
274    getObjectProperties(memberInitializer, propertySet);
275    return;
276  }
277
278  if (isClassDeclaration(memberInitializer) || isClassExpression(memberInitializer) || isStructDeclaration(memberInitializer)) {
279    getClassProperties(memberInitializer, propertySet);
280    return;
281  }
282
283  if (isEnumDeclaration(memberInitializer)) {
284    getEnumProperties(memberInitializer, propertySet);
285    return;
286  }
287}
288
289export function getEnumProperties(enumNode: EnumDeclaration, propertySet: Set<string>): void {
290  if (!enumNode || !enumNode.members) {
291    return;
292  }
293
294  enumNode.members.forEach((member) => {
295    if (!member || !member.name) {
296      return;
297    }
298
299    const memberName: PropertyName = member.name;
300    collectPropertyNamesAndStrings(memberName, propertySet);
301    //other kind ignore
302  });
303
304  return;
305}
306
307export function getObjectProperties(objNode: ObjectLiteralExpression, propertySet: Set<string>): void {
308  if (!objNode || !objNode.properties) {
309    return;
310  }
311
312  objNode.properties.forEach((propertyElement) => {
313    if (!propertyElement || !propertyElement.name) {
314      return;
315    }
316
317    const propertyName: PropertyName = propertyElement.name;
318    collectPropertyNamesAndStrings(propertyName, propertySet);
319
320    //extract class element's property, example: export const hello = {info={read: {}}}
321    if (!isPropertyAssignment(propertyElement) || !propertyElement.initializer) {
322      return;
323    }
324
325    if (isObjectLiteralExpression(propertyElement.initializer)) {
326      getObjectProperties(propertyElement.initializer, propertySet);
327      return;
328    }
329
330    if (isClassDeclaration(propertyElement.initializer)) {
331      getClassProperties(propertyElement.initializer, propertySet);
332      return;
333    }
334
335    if (isEnumDeclaration(propertyElement.initializer)) {
336      getEnumProperties(propertyElement.initializer, propertySet);
337      return;
338    }
339  });
340
341  return;
342}
343
344export function getStructProperties(structNode: StructDeclaration, propertySet: Set<string>): void {
345  structNode?.members?.forEach((member) => {
346    const memberName: PropertyName = member?.name;
347    if (!memberName) {
348      return;
349    }
350    collectPropertyNamesAndStrings(memberName, propertySet);
351  });
352}
353
354/**
355 * collect elements into export whitelist for module.exports = {A, B, C, D}
356 * since these elements can be import by `const {A, B, C, D} = require("./filePath");`
357 */
358export function getObjectExportNames(objNode: ObjectLiteralExpression, exportNames: Set<string>): void {
359  if (!objNode || !objNode.properties) {
360    return;
361  }
362
363  objNode.properties.forEach((propertyElement) => {
364    if (isPropertyAssignment(propertyElement)) {
365      /**
366       * { prop1: 123 } // collect prop1
367       * { 'prop2': 123 } // collect prop2
368       * { ['prop3']: 123 } // collect prop3
369       */
370      addExportPropertyName(propertyElement, exportNames);
371
372      let initializer = propertyElement.initializer;
373      if (isIdentifier(initializer)) {
374        /**
375         * { prop: testObj } // collect testObj into exportOriginalNameSet so that its properties can be collected
376         */
377        exportOriginalNameSet.add(initializer.text);
378      }
379      return;
380    }
381
382    if (isShorthandPropertyAssignment(propertyElement)) {
383      /**
384       * let shorthandNode = {prop1: 123};
385       * module.exports = { shorthandNode } // collect shorthandNode
386       */
387      exportNames.add(propertyElement.name.text);
388      return;
389    }
390
391    if (isMethodDeclaration(propertyElement) || isGetAccessor(propertyElement) || isSetAccessor(propertyElement)) {
392      /**
393       * { method() {} } // collect method
394       * { 'method'() {} } // collect method
395       * { ['method']() {} } // collect method
396       * { get getProp() {} } // collect getProp
397       * { get 'getProp'() {} } // collect getProp
398       * { get ['getProp']() {}} // collect getProp
399       */
400      addExportPropertyName(propertyElement, exportNames);
401      return;
402    }
403  });
404
405  return;
406}
407
408/**
409 * Collect property names in ObjectLiteralExpression
410 */
411export function addExportPropertyName(propertyElement: PropertyAssignment | MethodDeclaration |
412   GetAccessorDeclaration | SetAccessorDeclaration, exportNames: Set<string>): void {
413    let nameNode = propertyElement.name;
414
415    if (isIdentifier(nameNode) || isStringLiteral(nameNode)) {
416      exportNames.add(nameNode.text);
417    }
418
419    if (isComputedPropertyName(nameNode) && isStringLiteral(nameNode.expression)) {
420      exportNames.add(nameNode.expression.text);
421    }
422}
423
424/**
425 * Collect reserved names in enum
426 * e.g.
427 * enum H {
428 *   A,
429 *   B = A + 1
430 * }
431 * A is reserved
432 */
433export function visitEnumInitializer(childNode: Node): void {
434  if (NodeUtils.isPropertyNode(childNode)) {
435    return;
436  }
437
438  if (isTypeNode(childNode)) {
439    return;
440  }
441
442  if (!isIdentifier(childNode)) {
443    forEachChild(childNode, visitEnumInitializer);
444    return;
445  }
446
447  UnobfuscationCollections.reservedEnum.add(childNode.text);
448}
449
450/**
451 * collect properties of ViewPU class as reserved names
452 */
453export function getViewPUClassProperties(classNode: ClassDeclaration | ClassExpression): void {
454  if (!classNode || !classNode.members) {
455    return;
456  }
457
458  classNode.members.forEach((member) => {
459    const memberName: PropertyName = member.name;
460    if (!memberName) {
461      return;
462    }
463    collectPropertyNamesAndStrings(memberName, UnobfuscationCollections.reservedStruct);
464  });
465}