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