• 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 { annotation } from '../../common/arkts-utils';
18import { factory } from './factory';
19
20export enum DecoratorNames {
21    STATE = 'State',
22    STORAGE_LINK = 'StorageLink',
23    STORAGE_PROP = 'StorageProp',
24    LINK = 'Link',
25    PROP = 'Prop',
26    PROVIDE = 'Provide',
27    CONSUME = 'Consume',
28    OBJECT_LINK = 'ObjectLink',
29    OBSERVED = 'Observed',
30    WATCH = 'Watch',
31    BUILDER_PARAM = 'BuilderParam',
32    BUILDER = 'Builder',
33    CUSTOM_DIALOG = 'CustomDialog',
34    LOCAL_STORAGE_PROP = 'LocalStorageProp',
35    LOCAL_STORAGE_LINK = 'LocalStorageLink',
36    REUSABLE = 'Reusable',
37    TRACK = 'Track',
38}
39
40export function collectPropertyDecorators(property: arkts.ClassProperty): string[] {
41    const properties: string[] = [];
42    property.annotations.forEach((anno) => {
43        if (!!anno.expr && arkts.isIdentifier(anno.expr)) {
44            properties.push(anno.expr.name);
45        }
46    });
47    return properties;
48}
49
50export function isDecoratorAnnotation(anno: arkts.AnnotationUsage, decoratorName: DecoratorNames): boolean {
51    return !!anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName;
52}
53
54export function hasDecorator(
55    property: arkts.ClassProperty | arkts.ClassDefinition | arkts.MethodDefinition,
56    decoratorName: DecoratorNames
57): boolean {
58    if (arkts.isMethodDefinition(property)) {
59        return property.scriptFunction.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName));
60    }
61    return property.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName));
62}
63
64/**
65 * Determine whether the node `<st>` is decorated by decorators that need initializing without assignment.
66 *
67 * @param st class property node
68 */
69export function needDefiniteOrOptionalModifier(st: arkts.ClassProperty): boolean {
70    return (
71        hasDecorator(st, DecoratorNames.LINK) ||
72        hasDecorator(st, DecoratorNames.CONSUME) ||
73        hasDecorator(st, DecoratorNames.OBJECT_LINK) ||
74        (hasDecorator(st, DecoratorNames.PROP) && !st.value)
75    );
76}
77
78export function getStateManagementType(node: arkts.ClassProperty): string {
79    if (hasDecorator(node, DecoratorNames.STATE)) {
80        return 'StateDecoratedVariable';
81    } else if (hasDecorator(node, DecoratorNames.LINK)) {
82        return 'DecoratedV1VariableBase';
83    } else if (hasDecorator(node, DecoratorNames.PROP)) {
84        return 'PropDecoratedVariable';
85    } else if (hasDecorator(node, DecoratorNames.STORAGE_LINK)) {
86        return 'StorageLinkDecoratedVariable';
87    } else if (hasDecorator(node, DecoratorNames.STORAGE_PROP)) {
88        return 'StoragePropDecoratedVariable';
89    } else if (hasDecorator(node, DecoratorNames.PROVIDE)) {
90        return 'ProvideDecoratedVariable';
91    } else if (hasDecorator(node, DecoratorNames.CONSUME)) {
92        return 'ConsumeDecoratedVariable';
93    } else if (hasDecorator(node, DecoratorNames.OBJECT_LINK)) {
94        return 'ObjectLinkDecoratedVariable';
95    } else if (hasDecorator(node, DecoratorNames.LOCAL_STORAGE_PROP)) {
96        return 'SyncedProperty';
97    }
98    return 'MutableState';
99}
100
101export function createGetter(
102    name: string,
103    type: arkts.TypeNode | undefined,
104    returns: arkts.Expression,
105    needMemo: boolean = false,
106): arkts.MethodDefinition {
107    const returnType: arkts.TypeNode | undefined = type?.clone();
108    if (needMemo) {
109        returnType?.setAnnotations([annotation('memo')]);
110    }
111    const body = arkts.factory.createBlock([arkts.factory.createReturnStatement(returns)]);
112    const scriptFunction = arkts.factory.createScriptFunction(
113        body,
114        arkts.FunctionSignature.createFunctionSignature(undefined, [], returnType, false),
115        arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER,
116        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC
117    );
118    return arkts.factory.createMethodDefinition(
119        arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET,
120        arkts.factory.createIdentifier(name),
121        scriptFunction,
122        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC,
123        false
124    );
125}
126
127export function createSetter(
128    name: string,
129    type: arkts.TypeNode | undefined,
130    left: arkts.Expression,
131    right: arkts.AstNode,
132    needMemo: boolean = false
133): arkts.MethodDefinition {
134    const body = arkts.factory.createBlock([
135        arkts.factory.createExpressionStatement(
136            arkts.factory.createAssignmentExpression(
137                left,
138                arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION,
139                right
140            )
141        ),
142    ]);
143    const param: arkts.ETSParameterExpression = arkts.factory.createParameterDeclaration(
144        arkts.factory.createIdentifier('value', type?.clone()),
145        undefined
146    );
147    if (needMemo) {
148        param.annotations = [annotation('memo')];
149    }
150    const scriptFunction = arkts.factory.createScriptFunction(
151        body,
152        arkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false),
153        arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER,
154        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC
155    );
156
157    return arkts.factory.createMethodDefinition(
158        arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET,
159        arkts.factory.createIdentifier(name),
160        scriptFunction,
161        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC,
162        false
163    );
164}
165
166export function createSetter2(
167    name: string,
168    type: arkts.TypeNode | undefined,
169    statement: arkts.AstNode
170): arkts.MethodDefinition {
171    const body = arkts.factory.createBlock([statement]);
172    const param: arkts.ETSParameterExpression = arkts.factory.createParameterDeclaration(
173        arkts.factory.createIdentifier('value', type?.clone()),
174        undefined
175    );
176    const scriptFunction = arkts.factory.createScriptFunction(
177        body,
178        arkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false),
179        arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER,
180        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC
181    );
182
183    return arkts.factory.createMethodDefinition(
184        arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET,
185        arkts.factory.createIdentifier(name),
186        scriptFunction,
187        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC,
188        false
189    );
190}
191
192export function generateThisBackingValue(
193    name: string,
194    optional: boolean = false,
195    nonNull: boolean = false
196): arkts.MemberExpression {
197    const member: arkts.Expression = generateThisBacking(name, optional, nonNull);
198    return arkts.factory.createMemberExpression(
199        member,
200        arkts.factory.createIdentifier('value'),
201        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
202        false,
203        false
204    );
205}
206
207export function generateThisBacking(
208    name: string,
209    optional: boolean = false,
210    nonNull: boolean = false
211): arkts.Expression {
212    const member: arkts.Expression = arkts.factory.createMemberExpression(
213        arkts.factory.createThisExpression(),
214        arkts.factory.createIdentifier(`${name}`),
215        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
216        false,
217        optional
218    );
219    return nonNull ? arkts.factory.createTSNonNullExpression(member) : member;
220}
221
222function getValueStr(node: arkts.AstNode): string | undefined {
223    if (!arkts.isClassProperty(node) || !node.value) return undefined;
224    return arkts.isStringLiteral(node.value) ? node.value.str : undefined;
225}
226
227function getAnnotationValue(anno: arkts.AnnotationUsage, decoratorName: DecoratorNames): string | undefined {
228    const isSuitableAnnotation: boolean =
229        !!anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName;
230    if (isSuitableAnnotation && anno.properties.length === 1) {
231        return getValueStr(anno.properties.at(0)!);
232    }
233    return undefined;
234}
235
236export function getValueInAnnotation(node: arkts.ClassProperty, decoratorName: DecoratorNames): string | undefined {
237    const annotations: readonly arkts.AnnotationUsage[] = node.annotations;
238    for (let i = 0; i < annotations.length; i++) {
239        const anno: arkts.AnnotationUsage = annotations[i];
240        const str: string | undefined = getAnnotationValue(anno, decoratorName);
241        if (!!str) {
242            return str;
243        }
244    }
245    return undefined;
246}
247
248export interface ProvideOptions {
249    alias: string;
250    allowOverride: boolean;
251}
252
253export function getValueInProvideAnnotation(node: arkts.ClassProperty): ProvideOptions | undefined {
254    const annotations: readonly arkts.AnnotationUsage[] = node.annotations;
255    for (let i = 0; i < annotations.length; i++) {
256        const anno: arkts.AnnotationUsage = annotations[i];
257        if (anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.PROVIDE) {
258            const alias: string = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'alias');
259            const allowOverride: boolean = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'allowOverride')
260                ? true
261                : false;
262            return { alias, allowOverride };
263        }
264    }
265    return undefined;
266}
267
268function getValueInObjectAnnotation(anno: arkts.AnnotationUsage, decoratorName: DecoratorNames, key: string): any {
269    const isSuitableAnnotation: boolean =
270        !!anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName;
271    if (!isSuitableAnnotation) {
272        return undefined;
273    }
274    const keyItem: arkts.AstNode | undefined = anno.properties.find(
275        (annoProp: arkts.AstNode) =>
276            arkts.isClassProperty(annoProp) &&
277            annoProp.key &&
278            arkts.isIdentifier(annoProp.key) &&
279            annoProp.key.name === key
280    );
281    if (keyItem && arkts.isClassProperty(keyItem) && keyItem.value) {
282        return getDifferentAnnoTypeValue(keyItem.value);
283    }
284    return undefined;
285}
286
287function getDifferentAnnoTypeValue(value: arkts.Expression): string | boolean {
288    if (arkts.isBooleanLiteral(value)) {
289        return value.value;
290    } else if (arkts.isStringLiteral(value)) {
291        return value.str;
292    }
293    return value.dumpSrc();
294}
295
296export function judgeIfAddWatchFunc(args: arkts.Expression[], property: arkts.ClassProperty): void {
297    if (hasDecorator(property, DecoratorNames.WATCH)) {
298        const watchStr: string | undefined = getValueInAnnotation(property, DecoratorNames.WATCH);
299        if (watchStr) {
300            args.push(factory.createWatchCallback(watchStr));
301        }
302    }
303}
304
305export function generateGetOrSetCall(beforCall: arkts.AstNode, type: string) {
306    return arkts.factory.createCallExpression(
307        arkts.factory.createMemberExpression(
308            beforCall,
309            arkts.factory.createIdentifier(type),
310            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
311            false,
312            false
313        ),
314        undefined,
315        type === 'set' ? [arkts.factory.createIdentifier('value')] : undefined,
316        undefined
317    );
318}
319
320export function generateToRecord(newName: string, originalName: string): arkts.Property {
321    return arkts.Property.createProperty(
322        arkts.factory.createStringLiteral(originalName),
323        arkts.factory.createBinaryExpression(
324            arkts.factory.createMemberExpression(
325                arkts.factory.createIdentifier('paramsCasted'),
326                arkts.factory.createIdentifier(originalName),
327                arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
328                false,
329                false
330            ),
331            arkts.ETSNewClassInstanceExpression.createETSNewClassInstanceExpression(
332                arkts.factory.createTypeReference(
333                    arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('Object'))
334                ),
335                []
336            ),
337            arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING
338        )
339    );
340}
341
342export function getStageManagementIdent(property: arkts.ClassProperty): string {
343    const useMutableState: boolean =
344        hasDecorator(property, DecoratorNames.STATE) ||
345        hasDecorator(property, DecoratorNames.STORAGE_LINK) ||
346        hasDecorator(property, DecoratorNames.PROVIDE) ||
347        hasDecorator(property, DecoratorNames.CONSUME) ||
348        hasDecorator(property, DecoratorNames.LINK) ||
349        hasDecorator(property, DecoratorNames.LOCAL_STORAGE_LINK) ||
350        hasDecorator(property, DecoratorNames.LINK);
351    const useSyncedProperty: boolean =
352        hasDecorator(property, DecoratorNames.PROP) ||
353        hasDecorator(property, DecoratorNames.STORAGE_PROP) ||
354        hasDecorator(property, DecoratorNames.LOCAL_STORAGE_PROP) ||
355        hasDecorator(property, DecoratorNames.OBJECT_LINK);
356    if (useMutableState) {
357        return 'MutableState';
358    } else if (useSyncedProperty) {
359        return 'SyncedProperty';
360    } else {
361        return '';
362    }
363}
364