• 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} from 'typescript';
37
38import type {
39  ClassDeclaration,
40  ClassExpression,
41  ElementAccessExpression,
42  EnumDeclaration,
43  Expression,
44  HeritageClause,
45  InterfaceDeclaration,
46  Modifier,
47  NodeArray,
48  ObjectLiteralExpression,
49  PropertyName,
50  Statement,
51  StructDeclaration,
52  TypeAliasDeclaration,
53} from 'typescript';
54
55import {OhPackType} from './TransformUtil';
56
57export const stringPropsSet: Set<string> = new Set();
58/**
59 * find openHarmony module import statement
60 * example:
61 *  jsbundle - var _ohos = _interopRequireDefault(requireModule('@ohos.hilog'));
62 *  esmodule - var hilog = globalThis.requireNapi('hilog') || ...
63 *
64 * @param node
65 * @param moduleName full name of imported module, must check format before called, example:
66 *  - '@ohos.hilog'
67 *  - '@ohos.application.Ability'
68 */
69export function findOhImportStatement(node: Statement, moduleName: string): OhPackType {
70  if (!isVariableStatement(node) || node.declarationList.declarations.length !== 1) {
71    return OhPackType.NONE;
72  }
73
74  const initializer: Expression = node.declarationList.declarations[0].initializer;
75  if (initializer === undefined) {
76    return OhPackType.NONE;
77  }
78
79  /** esmodule */
80  if (isBinaryExpression(initializer)) {
81    if (initializer.operatorToken.kind !== SyntaxKind.BarBarToken) {
82      return OhPackType.NONE;
83    }
84
85    if (!isCallExpression(initializer.left)) {
86      return OhPackType.NONE;
87    }
88
89    if (!isPropertyAccessExpression(initializer.left.expression)) {
90      return OhPackType.NONE;
91    }
92
93    if (!isIdentifier(initializer.left.expression.expression) ||
94      initializer.left.expression.expression.text !== 'globalThis') {
95      return OhPackType.NONE;
96    }
97
98    if (!isIdentifier(initializer.left.expression.name) ||
99      initializer.left.expression.name.text !== 'requireNapi') {
100      return OhPackType.NONE;
101    }
102
103    if (initializer.left.arguments.length !== 1) {
104      return OhPackType.NONE;
105    }
106
107    const arg: Expression = initializer.left.arguments[0];
108    if (isStringLiteral(arg) && arg.text === moduleName.substring('@ohos.'.length)) {
109      return OhPackType.ES_MODULE;
110    }
111  }
112
113  /** jsbundle */
114  if (isCallExpression(initializer)) {
115    if (initializer.arguments.length !== 1) {
116      return OhPackType.NONE;
117    }
118
119    if (!isIdentifier(initializer.expression) ||
120      initializer.expression.text !== '_interopRequireDefault') {
121      return OhPackType.NONE;
122    }
123
124    const arg: Expression = initializer.arguments[0];
125    if (!isCallExpression(arg)) {
126      return OhPackType.NONE;
127    }
128
129    if (!isIdentifier(arg.expression) || arg.expression.text !== 'requireModule') {
130      return OhPackType.NONE;
131    }
132
133    const innerArg: Expression = arg.arguments[0];
134    if (!isStringLiteral(innerArg) || innerArg.text !== moduleName) {
135      return OhPackType.NONE;
136    }
137
138    return OhPackType.JS_BUNDLE;
139  }
140
141  return OhPackType.NONE;
142}
143
144function containViewPU(heritageClauses: NodeArray<HeritageClause>): boolean {
145  if (!heritageClauses) {
146    return false;
147  }
148
149  let hasViewPU: boolean = false;
150  heritageClauses.forEach(
151    (heritageClause) => {
152      if (!heritageClause || !heritageClause.types) {
153        return;
154      }
155
156      const types = heritageClause.types;
157      types.forEach((typeExpression) => {
158        if (!typeExpression || !typeExpression.expression) {
159          return;
160        }
161
162        const expression = typeExpression.expression;
163        if (isIdentifier(expression) && expression.text === 'ViewPU') {
164          hasViewPU = true;
165        }
166      });
167    });
168
169  return hasViewPU;
170}
171
172/**
173 * used to ignore user defined ui component class property name
174 * @param classNode
175 */
176export function isViewPUBasedClass(classNode: ClassDeclaration): boolean {
177  if (!classNode) {
178    return false;
179  }
180
181  if (!isClassDeclaration(classNode)) {
182    return false;
183  }
184
185  const heritageClause = classNode.heritageClauses;
186  return containViewPU(heritageClause);
187}
188
189export function collectPropertyNamesAndStrings(memberName: PropertyName, propertySet: Set<string>): void {
190  if (isIdentifier(memberName)) {
191    propertySet.add(memberName.text);
192  }
193
194  if (isStringLiteral(memberName)) {
195    propertySet.add(memberName.text);
196    stringPropsSet.add(memberName.text);
197  }
198
199  if (isComputedPropertyName(memberName) && isStringLiteral(memberName.expression)) {
200    propertySet.add(memberName.expression.text);
201    stringPropsSet.add(memberName.expression.text);
202  }
203}
204
205export function getElementAccessExpressionProperties(elementAccessExpressionNode: ElementAccessExpression, propertySet: Set<string>): void {
206  if (!elementAccessExpressionNode || !elementAccessExpressionNode.argumentExpression) {
207    return;
208  }
209
210  if (isStringLiteral(elementAccessExpressionNode.argumentExpression)) {
211    stringPropsSet.add(elementAccessExpressionNode.argumentExpression.text);
212  }
213}
214
215export function getTypeAliasProperties(typeAliasNode: TypeAliasDeclaration, propertySet: Set<string>): void {
216  if (!typeAliasNode || !typeAliasNode.type || !isTypeLiteralNode(typeAliasNode.type)) {
217    return;
218  }
219
220  typeAliasNode.type.members.forEach((member) => {
221    if (!member || !member.name) {
222      return;
223    }
224    let memberName: PropertyName = member.name;
225    collectPropertyNamesAndStrings(memberName, propertySet);
226  });
227}
228
229/**
230 * export interface interfaceName {
231 *  a1: number;
232 *  "a2": number;
233 *  ["a3"]: number;
234 * }
235 */
236
237export function getInterfaceProperties(interfaceNode: InterfaceDeclaration, propertySet: Set<string>): void {
238  if (!interfaceNode || !interfaceNode.members) {
239    return;
240  }
241
242  interfaceNode.members.forEach((member) => {
243    if (!member || !member.name) {
244      return;
245    }
246
247    let memberName: PropertyName = member.name;
248    collectPropertyNamesAndStrings(memberName, propertySet);
249  });
250}
251
252function isParameterPropertyModifier(modifier: Modifier): boolean {
253  if (modifier.kind === SyntaxKind.PublicKeyword ||
254    modifier.kind === SyntaxKind.PrivateKeyword ||
255    modifier.kind === SyntaxKind.ProtectedKeyword ||
256    modifier.kind === SyntaxKind.ReadonlyKeyword) {
257    return true;
258  }
259  return false;
260}
261
262export function getClassProperties(classNode: ClassDeclaration | ClassExpression | StructDeclaration, propertySet: Set<string>): void {
263  if (!classNode || !classNode.members) {
264    return;
265  }
266
267  classNode.members.forEach((member) => {
268    if (!member) {
269      return;
270    }
271
272    const memberName: PropertyName = member.name;
273    if (memberName) {
274      collectPropertyNamesAndStrings(memberName, propertySet);
275    }
276
277
278    if (isConstructorDeclaration(member) && member.parameters) {
279      member.parameters.forEach((parameter) => {
280        if (isParameter(parameter) && parameter.modifiers) {
281          parameter.modifiers.forEach((modifier) => {
282            if (isParameterPropertyModifier(modifier) && parameter.name && isIdentifier(parameter.name)) {
283              propertySet.add(parameter.name.text);
284            }
285          });
286          processMemberInitializer(parameter.initializer, propertySet);
287        }
288      });
289
290      if (member.body) {
291        member.body.statements.forEach((statement) => {
292          if (isExpressionStatement(statement) && isBinaryExpression(statement.expression) &&
293            statement.expression.operatorToken.kind === SyntaxKind.EqualsToken) {
294            processMemberInitializer(statement.expression.right, propertySet);
295          }
296        });
297      }
298    }
299
300    if (!isPropertyDeclaration(member) || !member.initializer) {
301      return;
302    }
303    processMemberInitializer(member.initializer, propertySet);
304  });
305
306  return;
307}
308
309function processMemberInitializer(memberInitializer: Expression | undefined, propertySet: Set<string>): void {
310  if (!memberInitializer) {
311    return;
312  }
313
314  if (isObjectLiteralExpression(memberInitializer)) {
315    getObjectProperties(memberInitializer, propertySet);
316    return;
317  }
318
319  if (isClassDeclaration(memberInitializer) || isClassExpression(memberInitializer) || isStructDeclaration(memberInitializer)) {
320    getClassProperties(memberInitializer, propertySet);
321    return;
322  }
323
324  if (isEnumDeclaration(memberInitializer)) {
325    getEnumProperties(memberInitializer, propertySet);
326    return;
327  }
328}
329
330export function getEnumProperties(enumNode: EnumDeclaration, propertySet: Set<string>): void {
331  if (!enumNode || !enumNode.members) {
332    return;
333  }
334
335  enumNode.members.forEach((member) => {
336    if (!member || !member.name) {
337      return;
338    }
339
340    const memberName: PropertyName = member.name;
341    collectPropertyNamesAndStrings(memberName, propertySet);
342    //other kind ignore
343  });
344
345  return;
346}
347
348export function getObjectProperties(objNode: ObjectLiteralExpression, propertySet: Set<string>): void {
349  if (!objNode || !objNode.properties) {
350    return;
351  }
352
353  objNode.properties.forEach((propertyElement) => {
354    if (!propertyElement || !propertyElement.name) {
355      return;
356    }
357
358    const propertyName: PropertyName = propertyElement.name;
359    collectPropertyNamesAndStrings(propertyName, propertySet);
360
361    //extract class element's property, example: export const hello = {info={read: {}}}
362    if (!isPropertyAssignment(propertyElement) || !propertyElement.initializer) {
363      return;
364    }
365
366    if (isObjectLiteralExpression(propertyElement.initializer)) {
367      getObjectProperties(propertyElement.initializer, propertySet);
368      return;
369    }
370
371    if (isClassDeclaration(propertyElement.initializer)) {
372      getClassProperties(propertyElement.initializer, propertySet);
373      return;
374    }
375
376    if (isEnumDeclaration(propertyElement.initializer)) {
377      getEnumProperties(propertyElement.initializer, propertySet);
378      return;
379    }
380  });
381
382  return;
383}
384