• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-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 { ProjectConfig } from '../common/plugin-context';
18import { factory as structFactory } from './struct-translators/factory';
19import { factory as builderLambdaFactory } from './builder-lambda-translators/factory';
20import { factory as uiFactory } from './ui-factory';
21import { factory as entryFactory } from './entry-translators/factory';
22import { AbstractVisitor } from '../common/abstract-visitor';
23import { annotation, collect, filterDefined } from '../common/arkts-utils';
24import {
25    CustomComponentNames,
26    getCustomComponentOptionsName,
27    getTypeNameFromTypeParameter,
28    getTypeParamsFromClassDecl,
29    getGettersFromClassDecl,
30    addMemoAnnotation,
31} from './utils';
32import { hasDecorator, DecoratorNames } from './property-translators/utils';
33import {
34    isCustomComponentClass,
35    isKnownMethodDefinition,
36    isEtsGlobalClass,
37    isReourceNode,
38    ScopeInfoCollection,
39    CustomComponentScopeInfo,
40    isMemoCall,
41    findCanAddMemoFromArrowFunction,
42} from './struct-translators/utils';
43import { isBuilderLambda, isBuilderLambdaMethodDecl } from './builder-lambda-translators/utils';
44import { isEntryWrapperClass } from './entry-translators/utils';
45import { classifyObservedTrack, classifyProperty, PropertyTranslator } from './property-translators';
46import { ObservedTrackTranslator } from './property-translators/observedTrack';
47import { nodeByType } from '@koalaui/libarkts/build/src/reexport-for-generated';
48import { isArkUICompatible, updateArkUICompatible } from './interop';
49
50export class CheckedTransformer extends AbstractVisitor {
51    private scopeInfoCollection: ScopeInfoCollection;
52    projectConfig: ProjectConfig | undefined;
53
54    constructor(projectConfig: ProjectConfig | undefined) {
55        super();
56        this.projectConfig = projectConfig;
57        this.scopeInfoCollection = { customComponents: [] };
58    }
59
60    reset(): void {
61        super.reset();
62        this.scopeInfoCollection = { customComponents: [] };
63    }
64
65    enter(node: arkts.AstNode): void {
66        if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) {
67            this.scopeInfoCollection.customComponents.push({ name: node.definition!.ident!.name });
68        }
69        if (arkts.isMethodDefinition(node) && this.scopeInfoCollection.customComponents.length > 0) {
70            const name = node.name.name;
71            const scopeInfo = this.scopeInfoCollection.customComponents.pop()!;
72            scopeInfo.hasInitializeStruct ||= name === CustomComponentNames.COMPONENT_INITIALIZE_STRUCT;
73            scopeInfo.hasUpdateStruct ||= name === CustomComponentNames.COMPONENT_UPDATE_STRUCT;
74            scopeInfo.hasReusableRebind ||= name === CustomComponentNames.REUSABLE_COMPONENT_REBIND_STATE;
75            this.scopeInfoCollection.customComponents.push(scopeInfo);
76        }
77    }
78
79    exit(node: arkts.AstNode): void {
80        if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) {
81            this.scopeInfoCollection.customComponents.pop();
82        }
83    }
84
85    visitor(beforeChildren: arkts.AstNode): arkts.AstNode {
86        this.enter(beforeChildren);
87        if (arkts.isCallExpression(beforeChildren) && isBuilderLambda(beforeChildren)) {
88            const lambda = builderLambdaFactory.transformBuilderLambda(beforeChildren, this.projectConfig);
89            return this.visitEachChild(lambda);
90        } else if (arkts.isMethodDefinition(beforeChildren) && isBuilderLambdaMethodDecl(beforeChildren)) {
91            const lambda = builderLambdaFactory.transformBuilderLambdaMethodDecl(beforeChildren);
92            return this.visitEachChild(lambda);
93        }
94        const node = this.visitEachChild(beforeChildren);
95        if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) {
96            let scope: CustomComponentScopeInfo | undefined;
97            const scopeInfos: CustomComponentScopeInfo[] = this.scopeInfoCollection.customComponents;
98            if (scopeInfos.length > 0) {
99                scope = scopeInfos[scopeInfos.length - 1];
100            }
101            const newClass: arkts.ClassDeclaration = tranformClassMembers(
102                node,
103                arkts.hasModifierFlag(node, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE),
104                scope
105            );
106            this.exit(beforeChildren);
107            return newClass;
108        } else if (isEntryWrapperClass(node)) {
109            entryFactory.addMemoToEntryWrapperClassMethods(node);
110            return node;
111        } else if (arkts.isClassDeclaration(node) && isEtsGlobalClass(node)) {
112            return transformEtsGlobalClassMembers(node);
113        } else if (arkts.isCallExpression(node) && isReourceNode(node)) {
114            return transformResource(node, this.projectConfig);
115        } else if (findCanAddMemoFromArrowFunction(node)) {
116            return addMemoAnnotation(node);
117        } else if (arkts.isClassDeclaration(node)) {
118            return transformObservedTracked(node);
119        } else if (isArkUICompatible(node)) {
120            return updateArkUICompatible(node as arkts.CallExpression);
121        } else if (this.externalSourceName) {
122            return structFactory.transformExternalSource(this.externalSourceName, node);
123        }
124        return node;
125    }
126}
127
128export type ClassScopeInfo = {
129    isObserved: boolean;
130    classHasTrack: boolean;
131    getters: arkts.MethodDefinition[];
132};
133
134function transformObservedTracked(node: arkts.ClassDeclaration): arkts.ClassDeclaration {
135    if (!node.definition) {
136        return node;
137    }
138    const isObserved: boolean = hasDecorator(node.definition, DecoratorNames.OBSERVED);
139    const classHasTrack: boolean = node.definition.body.some(
140        (member) => arkts.isClassProperty(member) && hasDecorator(member, DecoratorNames.TRACK)
141    );
142    if (!isObserved && !classHasTrack) {
143        return node;
144    }
145
146    const updateClassDef: arkts.ClassDefinition = arkts.factory.updateClassDefinition(
147        node.definition,
148        node.definition.ident,
149        node.definition.typeParams,
150        node.definition.superTypeParams,
151        [
152            ...node.definition.implements,
153            arkts.TSClassImplements.createTSClassImplements(
154                arkts.factory.createTypeReference(
155                    arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('IObservedObject'))
156                )
157            ),
158        ],
159        undefined,
160        node.definition.super,
161        observedTrackPropertyMembers(classHasTrack, node.definition, isObserved),
162        node.definition.modifiers,
163        arkts.classDefinitionFlags(node.definition)
164    );
165    return arkts.factory.updateClassDeclaration(node, updateClassDef);
166}
167
168function observedTrackPropertyMembers(
169    classHasTrack: boolean,
170    definition: arkts.ClassDefinition,
171    isObserved: boolean
172): arkts.AstNode[] {
173    const watchMembers: arkts.AstNode[] = createWatchMembers();
174    const permissibleAddRefDepth: arkts.ClassProperty = arkts.factory.createClassProperty(
175        arkts.factory.createIdentifier('_permissibleAddRefDepth'),
176        arkts.factory.createNumericLiteral(0),
177        arkts.factory.createTypeReference(
178            arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('int32'))
179        ),
180        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC,
181        false
182    );
183
184    const meta: arkts.ClassProperty = arkts.factory.createClassProperty(
185        arkts.factory.createIdentifier('__meta'),
186        arkts.factory.createETSNewClassInstanceExpression(
187            arkts.factory.createTypeReference(
188                arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('MutableStateMeta'))
189            ),
190            [arkts.factory.createStringLiteral('@Observe properties (no @Track)')]
191        ),
192        arkts.factory.createTypeReference(
193            arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('MutableStateMeta'))
194        ),
195        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE,
196        false
197    );
198
199    const getters: arkts.MethodDefinition[] = getGettersFromClassDecl(definition);
200
201    const classScopeInfo: ClassScopeInfo = {
202        isObserved: isObserved,
203        classHasTrack: classHasTrack,
204        getters: getters,
205    };
206
207    const propertyTranslators: ObservedTrackTranslator[] = filterDefined(
208        definition.body.map((it) => classifyObservedTrack(it, classScopeInfo))
209    );
210
211    const propertyMembers = propertyTranslators.map((translator) => translator.translateMember());
212
213    const nonClassPropertyOrGetter: arkts.AstNode[] = definition.body.filter(
214        (member) =>
215            !arkts.isClassProperty(member) &&
216            !(
217                arkts.isMethodDefinition(member) &&
218                arkts.hasModifierFlag(member, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_GETTER)
219            )
220    );
221
222    return [
223        ...watchMembers,
224        ...(classHasTrack ? [permissibleAddRefDepth] : [permissibleAddRefDepth, meta]),
225        ...collect(...propertyMembers),
226        ...nonClassPropertyOrGetter,
227        ...classScopeInfo.getters,
228    ];
229}
230
231function createWatchMethod(
232    methodName: string,
233    returnType: arkts.Es2pandaPrimitiveType,
234    paramName: string,
235    paramType: string,
236    isReturnStatement: boolean
237): arkts.MethodDefinition {
238    return arkts.factory.createMethodDefinition(
239        arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD,
240        arkts.factory.createIdentifier(methodName),
241        arkts.factory.createScriptFunction(
242            arkts.factory.createBlock([
243                isReturnStatement
244                    ? arkts.factory.createReturnStatement(
245                            arkts.factory.createCallExpression(thisSubscribedWatchesMember(methodName), undefined, [
246                                arkts.factory.createIdentifier(paramName),
247                            ])
248                        )
249                    : arkts.factory.createExpressionStatement(
250                            arkts.factory.createCallExpression(thisSubscribedWatchesMember(methodName), undefined, [
251                                arkts.factory.createIdentifier(paramName),
252                            ])
253                        ),
254            ]),
255            arkts.factory.createFunctionSignature(
256                undefined,
257                [
258                    arkts.factory.createParameterDeclaration(
259                        arkts.factory.createIdentifier(
260                            paramName,
261                            arkts.factory.createTypeReference(
262                                arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier(paramType))
263                            )
264                        ),
265                        undefined
266                    ),
267                ],
268                arkts.factory.createPrimitiveType(returnType),
269                false
270            ),
271            arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD,
272            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC
273        ),
274        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC,
275        false
276    );
277}
278
279function createWatchMembers(): arkts.AstNode[] {
280    const subscribedWatches: arkts.ClassProperty = arkts.factory.createClassProperty(
281        arkts.factory.createIdentifier('subscribedWatches'),
282        arkts.factory.createETSNewClassInstanceExpression(
283            arkts.factory.createTypeReference(
284                arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('SubscribedWatches'))
285            ),
286            []
287        ),
288        arkts.factory.createTypeReference(
289            arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('SubscribedWatches'))
290        ),
291        arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE,
292        false
293    );
294
295    const addWatchSubscriber = createWatchMethod(
296        'addWatchSubscriber',
297        arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID,
298        'watchId',
299        'WatchIdType',
300        false
301      );
302
303      const removeWatchSubscriber = createWatchMethod(
304        'removeWatchSubscriber',
305        arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BOOLEAN,
306        'watchId',
307        'WatchIdType',
308        true
309      );
310
311      const executeOnSubscribingWatches = createWatchMethod(
312        'executeOnSubscribingWatches',
313        arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID,
314        'propertyName',
315        'string',
316        false
317      );
318
319    return [subscribedWatches, addWatchSubscriber, removeWatchSubscriber, executeOnSubscribingWatches];
320}
321
322function thisSubscribedWatchesMember(member: string): arkts.MemberExpression {
323    return arkts.factory.createMemberExpression(
324        arkts.factory.createMemberExpression(
325            arkts.factory.createThisExpression(),
326            arkts.factory.createIdentifier('subscribedWatches'),
327            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
328            false,
329            false
330        ),
331        arkts.factory.createIdentifier(member),
332        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
333        false,
334        false
335    );
336}
337
338/**
339 * @deprecated
340 */
341function tranformClassMembers(
342    node: arkts.ClassDeclaration,
343    isDecl?: boolean,
344    scope?: CustomComponentScopeInfo
345): arkts.ClassDeclaration {
346    if (!node.definition) {
347        return node;
348    }
349
350    let classTypeName: string | undefined;
351    let classOptionsName: string | undefined;
352    if (isDecl) {
353        const [classType, classOptions] = getTypeParamsFromClassDecl(node);
354        classTypeName = getTypeNameFromTypeParameter(classType);
355        classOptionsName = getTypeNameFromTypeParameter(classOptions);
356    }
357    const definition: arkts.ClassDefinition = node.definition;
358    const className: string | undefined = node.definition.ident?.name;
359    if (!className) {
360        throw new Error('Non Empty className expected for Component');
361    }
362
363    const propertyTranslators: PropertyTranslator[] = filterDefined(
364        definition.body.map((it) => classifyProperty(it, className))
365    );
366    const translatedMembers: arkts.AstNode[] = tranformPropertyMembers(
367        className,
368        propertyTranslators,
369        classOptionsName ?? getCustomComponentOptionsName(className),
370        isDecl,
371        scope
372    );
373    const updateMembers: arkts.AstNode[] = definition.body
374        .filter((member) => !arkts.isClassProperty(member))
375        .map((member: arkts.AstNode) =>
376            transformOtherMembersInClass(member, classTypeName, classOptionsName, className, isDecl)
377        );
378
379    const updateClassDef: arkts.ClassDefinition = structFactory.updateCustomComponentClass(definition, [
380        ...translatedMembers,
381        ...updateMembers,
382    ]);
383    return arkts.factory.updateClassDeclaration(node, updateClassDef);
384}
385
386/**
387 * @deprecated
388 */
389function transformOtherMembersInClass(
390    member: arkts.AstNode,
391    classTypeName: string | undefined,
392    classOptionsName: string | undefined,
393    className: string,
394    isDecl?: boolean
395): arkts.AstNode {
396    if (arkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.BUILDER)) {
397        member.scriptFunction.setAnnotations([annotation('memo')]);
398        return member;
399    }
400    if (
401        arkts.isMethodDefinition(member) &&
402        isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI) &&
403        !isDecl
404    ) {
405        return uiFactory.createConstructorMethod(member);
406    }
407    if (arkts.isMethodDefinition(member) && isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_BUILD_ORI)) {
408        return structFactory.transformBuildMethodWithOriginBuild(
409            member,
410            classTypeName ?? className,
411            classOptionsName ?? getCustomComponentOptionsName(className),
412            isDecl
413        );
414    }
415    return member;
416}
417
418/**
419 * @deprecated
420 */
421function tranformPropertyMembers(
422    className: string,
423    propertyTranslators: PropertyTranslator[],
424    optionsTypeName: string,
425    isDecl?: boolean,
426    scope?: CustomComponentScopeInfo
427): arkts.AstNode[] {
428    const propertyMembers = propertyTranslators.map((translator) => translator.translateMember());
429    const currentStructInfo: arkts.StructInfo = arkts.GlobalInfo.getInfoInstance().getStructInfo(className);
430    const collections = [];
431    if (!scope?.hasInitializeStruct) {
432        collections.push(structFactory.createInitializeStruct(currentStructInfo, optionsTypeName, isDecl));
433    }
434    if (!scope?.hasUpdateStruct) {
435        collections.push(structFactory.createUpdateStruct(currentStructInfo, optionsTypeName, isDecl));
436    }
437    if (currentStructInfo.isReusable) {
438        collections.push(structFactory.toRecord(optionsTypeName, currentStructInfo.toRecordBody));
439    }
440    return collect(...collections, ...propertyMembers);
441}
442
443/**
444 * @deprecated
445 */
446function transformEtsGlobalClassMembers(node: arkts.ClassDeclaration): arkts.ClassDeclaration {
447    if (!node.definition) {
448        return node;
449    }
450    node.definition.body.map((member: arkts.AstNode) => {
451        if (arkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.BUILDER)) {
452            member.scriptFunction.setAnnotations([annotation('memo')]);
453        }
454        return member;
455    });
456    return node;
457}
458
459/**
460 * @deprecated
461 */
462function transformResource(
463    resourceNode: arkts.CallExpression,
464    projectConfig: ProjectConfig | undefined
465): arkts.CallExpression {
466    const newArgs: arkts.AstNode[] = [
467        arkts.factory.create1StringLiteral(projectConfig?.bundleName ? projectConfig.bundleName : ''),
468        arkts.factory.create1StringLiteral(projectConfig?.moduleName ? projectConfig.moduleName : ''),
469        ...resourceNode.arguments,
470    ];
471    return structFactory.generateTransformedResource(resourceNode, newArgs);
472}
473