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