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}