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