• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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