1/* 2 * Copyright (c) 2025 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 * as arkts from '@koalaui/libarkts'; 17import { isAnnotation } from '../../common/arkts-utils'; 18import { BuilderLambdaNames, Dollars } from '../utils'; 19 20export type BuilderLambdaDeclInfo = { 21 isFunctionCall: boolean; // isFunctionCall means it is from $_instantiate. 22 params: readonly arkts.Expression[]; 23 returnType: arkts.TypeNode | undefined; 24}; 25 26export enum BindableDecl { 27 BINDABLE = 'Bindable', 28} 29 30export type BuilderLambdaAstNode = arkts.ScriptFunction | arkts.ETSParameterExpression | arkts.FunctionDeclaration; 31 32export type InstanceCallInfo = { 33 isReceiver: boolean; 34 call: arkts.CallExpression; 35}; 36 37/** 38 * Used in finding "XXX" in BuilderLambda("XXX") 39 * @deprecated 40 */ 41export function builderLambdaArgumentName(annotation: arkts.AnnotationUsage): string | undefined { 42 if (!isBuilderLambdaAnnotation(annotation)) { 43 return undefined; 44 } 45 46 const property = annotation.properties.at(0); 47 if (!property || !arkts.isClassProperty(property)) { 48 return undefined; 49 } 50 if (!property.value || !arkts.isStringLiteral(property.value)) { 51 return undefined; 52 } 53 54 return property.value.str; 55} 56 57/** 58 * Determine whether it is a custom component. 59 * 60 * @param node class declaration node 61 */ 62export function isBuilderLambda(node: arkts.AstNode, isExternal?: boolean): boolean { 63 const builderLambdaCall: arkts.AstNode | undefined = getDeclForBuilderLambda(node); 64 if (!builderLambdaCall) { 65 return arkts.isCallExpression(node) && node.arguments.length > 0 && isBuilderLambda(node.arguments[0]); 66 } 67 return !!builderLambdaCall; 68} 69 70/** 71 * Determine whether it is a function with receiver method definition. 72 * 73 * @param node method definition node 74 */ 75export function isFunctionWithReceiver(node: arkts.MethodDefinition): boolean { 76 if (node.scriptFunction && arkts.isScriptFunction(node.scriptFunction)) { 77 return node.scriptFunction.hasReceiver; 78 } 79 return false; 80} 81 82/** 83 * Determine whether it is a function with receiver call. 84 * 85 * @param node identifier node 86 */ 87export function isFunctionWithReceiverCall(node: arkts.Identifier): boolean { 88 const decl: arkts.AstNode | undefined = arkts.getDecl(node); 89 if (decl && arkts.isMethodDefinition(decl)) { 90 return isFunctionWithReceiver(decl); 91 } 92 return false; 93} 94 95/** 96 * Determine whether it is a style chained call. 97 * 98 * @param node call expression node 99 */ 100export function isStyleChainedCall(node: arkts.CallExpression): boolean { 101 return ( 102 arkts.isMemberExpression(node.expression) && 103 arkts.isIdentifier(node.expression.property) && 104 arkts.isCallExpression(node.expression.object) 105 ); 106} 107 108/** 109 * Determine whether it is a style function with receiver call. 110 * 111 * @param node call expression node 112 */ 113export function isStyleWithReceiverCall(node: arkts.CallExpression): boolean { 114 return ( 115 arkts.isIdentifier(node.expression) && 116 isFunctionWithReceiverCall(node.expression) && 117 !!node.arguments.length && 118 arkts.isCallExpression(node.arguments[0]) 119 ); 120} 121 122/** 123 * replace $_instantiate with _instantiateImpl. 124 * 125 * @param name origin name 126 */ 127export function replaceBuilderLambdaDeclMethodName(name: string | undefined): string | undefined { 128 if (!!name && name === BuilderLambdaNames.ORIGIN_METHOD_NAME) { 129 return BuilderLambdaNames.TRANSFORM_METHOD_NAME; 130 } 131 return undefined; 132} 133 134export function isBuilderLambdaMethodDecl(node: arkts.AstNode, isExternal?: boolean): boolean { 135 const builderLambdaMethodDecl: arkts.AstNode | undefined = getDeclForBuilderLambdaMethodDecl(node); 136 return !!builderLambdaMethodDecl; 137} 138 139export function getDeclForBuilderLambdaMethodDecl( 140 node: arkts.AstNode, 141 isExternal?: boolean 142): arkts.AstNode | undefined { 143 if (!node || !arkts.isMethodDefinition(node)) { 144 return undefined; 145 } 146 147 const isBuilderLambda: boolean = !!node.name && isBuilderLambdaCall(node.name); 148 const isMethodDecl: boolean = 149 !!node.scriptFunction && 150 arkts.hasModifierFlag(node.scriptFunction, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); 151 if (isBuilderLambda && isMethodDecl) { 152 return node; 153 } 154 return undefined; 155} 156 157export function getDeclForBuilderLambda(node: arkts.AstNode, isExternal?: boolean): arkts.AstNode | undefined { 158 if (!node || !arkts.isCallExpression(node)) { 159 return undefined; 160 } 161 162 let currNode: arkts.AstNode = node; 163 while ( 164 !!currNode && 165 arkts.isCallExpression(currNode) && 166 !!currNode.expression && 167 arkts.isMemberExpression(currNode.expression) 168 ) { 169 const _node: arkts.MemberExpression = currNode.expression; 170 171 if (!!_node.property && arkts.isIdentifier(_node.property) && isBuilderLambdaCall(_node.property)) { 172 return node; 173 } 174 175 if (!!_node.object && arkts.isCallExpression(_node.object) && isBuilderLambdaCall(_node.object)) { 176 return node; 177 } 178 179 currNode = _node.object; 180 } 181 182 if (isBuilderLambdaCall(node)) { 183 return node; 184 } 185 return undefined; 186} 187 188export function isBuilderLambdaCall(node: arkts.CallExpression | arkts.Identifier): boolean { 189 const expr = arkts.isIdentifier(node) ? node : node.expression; 190 const decl = arkts.getDecl(expr); 191 192 if (!decl) { 193 return false; 194 } 195 196 if (arkts.isMethodDefinition(decl)) { 197 if (isFunctionWithReceiver(decl)) { 198 return ( 199 arkts.isCallExpression(node) && 200 node.arguments.length > 0 && 201 !!getDeclForBuilderLambda(node.arguments[0]) 202 ); 203 } 204 return isBuilderLambdaMethod(decl); 205 } 206 if (arkts.isFunctionExpression(decl)) { 207 return hasBuilderLambdaAnnotation(decl.scriptFunction); 208 } 209 return false; 210} 211 212export function isBuilderLambdaMethod(node: arkts.MethodDefinition): boolean { 213 if (!node || !arkts.isMethodDefinition(node)) { 214 return false; 215 } 216 217 const result = hasBuilderLambdaAnnotation(node.scriptFunction); 218 if (result) { 219 return true; 220 } 221 if (node.overloads.length > 0) { 222 return node.overloads.some(isBuilderLambdaMethod); 223 } 224 return false; 225} 226 227export function hasBuilderLambdaAnnotation(node: BuilderLambdaAstNode): boolean { 228 return node.annotations.some(isBuilderLambdaAnnotation); 229} 230 231export function isBuilderLambdaAnnotation(node: arkts.AnnotationUsage): boolean { 232 return isAnnotation(node, BuilderLambdaNames.ANNOTATION_NAME); 233} 234 235export function findBuilderLambdaAnnotation( 236 node: arkts.ScriptFunction | arkts.ETSParameterExpression 237): arkts.AnnotationUsage | undefined { 238 return node.annotations.find(isBuilderLambdaAnnotation); 239} 240 241export function findBuilderLambdaInMethod(node: arkts.MethodDefinition): arkts.AnnotationUsage | undefined { 242 if (!node || !arkts.isMethodDefinition(node)) { 243 return undefined; 244 } 245 const result = findBuilderLambdaAnnotation(node.scriptFunction); 246 if (!!result) { 247 return result; 248 } 249 node.overloads.forEach((overload) => { 250 const anno: arkts.AnnotationUsage | undefined = findBuilderLambdaInMethod(overload); 251 if (!!anno) { 252 return anno; 253 } 254 }); 255 return undefined; 256} 257 258export function findBuilderLambdaInCall( 259 node: arkts.CallExpression | arkts.Identifier 260): arkts.AnnotationUsage | undefined { 261 const decl = findBuilderLambdaDecl(node); 262 if (!decl) { 263 return undefined; 264 } 265 266 if (arkts.isMethodDefinition(decl)) { 267 return findBuilderLambdaInMethod(decl); 268 } 269 if (arkts.isFunctionExpression(decl)) { 270 return findBuilderLambdaAnnotation(decl.scriptFunction); 271 } 272 return undefined; 273} 274 275export function findBuilderLambdaDecl(node: arkts.CallExpression | arkts.Identifier): arkts.AstNode | undefined { 276 const expr = arkts.isIdentifier(node) ? node : node.expression; 277 const decl = arkts.getDecl(expr); 278 if (!decl) { 279 return undefined; 280 } 281 return decl; 282} 283 284/** 285 * check whether `<prop>` is the passing parameter. 286 * 287 * @param name origin name 288 */ 289export function isParameterPassing(prop: arkts.Property): boolean | undefined { 290 return ( 291 prop.key && 292 prop.value && 293 arkts.isIdentifier(prop.key) && 294 arkts.isMemberExpression(prop.value) && 295 arkts.isThisExpression(prop.value.object) && 296 arkts.isIdentifier(prop.value.property) 297 ); 298} 299 300export function findBuilderLambdaDeclInfo(decl: arkts.AstNode | undefined): BuilderLambdaDeclInfo | undefined { 301 if (!decl) { 302 return undefined; 303 } 304 305 if (arkts.isMethodDefinition(decl)) { 306 const params = decl.scriptFunction.params.map((p) => p.clone()); 307 const returnType = decl.scriptFunction.returnTypeAnnotation?.clone(); 308 const isFunctionCall = isBuilderLambdaFunctionCall(decl); 309 return { isFunctionCall, params, returnType }; 310 } 311 312 return undefined; 313} 314 315export function isBuilderLambdaFunctionCall(decl: arkts.AstNode | undefined): boolean { 316 if (!decl) { 317 return false; 318 } 319 if (arkts.isMethodDefinition(decl)) { 320 return ( 321 decl.name.name !== BuilderLambdaNames.ORIGIN_METHOD_NAME && 322 decl.name.name !== BuilderLambdaNames.TRANSFORM_METHOD_NAME 323 ); 324 } 325 return false; 326} 327 328export function callIsGoodForBuilderLambda(leaf: arkts.CallExpression): boolean { 329 const node = leaf.expression; 330 return arkts.isIdentifier(node) || arkts.isMemberExpression(node); 331} 332 333export function isSafeType(type: arkts.TypeNode | undefined): boolean { 334 if (!type) { 335 return false; 336 } 337 // type can be generic (not safe) if includes any type params in a type reference. 338 if (arkts.isETSTypeReference(type) && !!type.part && !!type.part.typeParams) { 339 return false; 340 } 341 return true; 342} 343 344export function builderLambdaMethodDeclType(method: arkts.MethodDefinition): arkts.TypeNode | undefined { 345 if (!method || !method.scriptFunction) { 346 return undefined; 347 } 348 return method.scriptFunction.returnTypeAnnotation; 349} 350 351export function builderLambdaTypeName(leaf: arkts.CallExpression): string | undefined { 352 if (!callIsGoodForBuilderLambda(leaf)) { 353 return undefined; 354 } 355 const node = leaf.expression; 356 let name: string | undefined; 357 if (arkts.isIdentifier(node)) { 358 name = node.name; 359 } 360 if (arkts.isMemberExpression(node) && arkts.isIdentifier(node.object)) { 361 name = node.object.name; 362 } 363 return name; 364} 365 366export function builderLambdaFunctionName(node: arkts.CallExpression): string | undefined { 367 const annotation = findBuilderLambdaInCall(node); 368 if (!annotation) { 369 return undefined; 370 } 371 if (arkts.isIdentifier(node.expression)) { 372 return node.expression.name; 373 } 374 if ( 375 arkts.isMemberExpression(node.expression) && 376 arkts.isIdentifier(node.expression.property) && 377 node.expression.property.name === BuilderLambdaNames.ORIGIN_METHOD_NAME 378 ) { 379 return BuilderLambdaNames.TRANSFORM_METHOD_NAME; 380 } 381 return undefined; 382} 383 384/** 385 * Determine whether the node `<type>` is `<bindableDecl>` bindable property. 386 * 387 * @param type type node 388 * @param bindableDecl bindable decalaration name 389 */ 390export function hasBindableProperty(type: arkts.AstNode, bindableDecl: BindableDecl): boolean { 391 let res: boolean = false; 392 if (arkts.isETSUnionType(type)) { 393 type.types.forEach((item: arkts.TypeNode) => { 394 res = res || hasBindableProperty(item, bindableDecl); 395 }); 396 } 397 if (arkts.isETSTypeReference(type)) { 398 res = 399 res || 400 (!!type.part && 401 !!type.part.name && 402 arkts.isIdentifier(type.part.name) && 403 type.part.name.name === bindableDecl); 404 } 405 406 return res; 407} 408 409/** 410 * Determine whether `<value>` is `$$()` call expression node. 411 * 412 * @param value expression node 413 */ 414export function isDoubleDollarCall(value: arkts.Expression): boolean { 415 if ( 416 arkts.isCallExpression(value) && 417 value.expression && 418 arkts.isIdentifier(value.expression) && 419 value.expression.name 420 ) { 421 return value.expression.name === Dollars.DOLLAR_DOLLAR; 422 } 423 return false; 424} 425 426/** 427 * get declaration type from `{xxx: <value>}` or `fun(<value>)`. 428 * 429 * @param value type node 430 */ 431export function getDecalTypeFromValue(value: arkts.Expression): arkts.TypeNode { 432 const decl: arkts.AstNode | undefined = arkts.getDecl(value); 433 if (!decl || !arkts.isClassProperty(decl)) { 434 throw new Error('cannot get declaration'); 435 } 436 if (isArrayType(decl.typeAnnotation!)) { 437 return getElementTypeFromArray(decl.typeAnnotation!)!; 438 } 439 return decl.typeAnnotation!; 440} 441 442/** 443 * Determine whether `<type>` is array type, e.g. `xxx[]` or `Array<xxx>`. 444 * 445 * @param type type node 446 */ 447export function isArrayType(type: arkts.TypeNode): boolean { 448 return ( 449 arkts.isTSArrayType(type) || 450 (arkts.isETSTypeReference(type) && 451 !!type.part && 452 arkts.isETSTypeReferencePart(type.part) && 453 !!type.part.name && 454 arkts.isIdentifier(type.part.name) && 455 type.part.name.name === 'Array') 456 ); 457} 458 459/** 460 * get element type from array type node `<arrayType>`. 461 * 462 * @param arrayType array type node 463 */ 464export function getElementTypeFromArray(arrayType: arkts.TypeNode): arkts.TypeNode | undefined { 465 if (arkts.isTSArrayType(arrayType)) { 466 return arrayType.elementType?.clone(); 467 } else if ( 468 arkts.isETSTypeReference(arrayType) && 469 !!arrayType.part && 470 arkts.isETSTypeReferencePart(arrayType.part) && 471 !!arrayType.part.typeParams && 472 arrayType.part.typeParams.params.length 473 ) { 474 return arrayType.part.typeParams.params[0].clone(); 475 } 476 return undefined; 477} 478