• 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 { AbstractVisitor } from '../../common/abstract-visitor';
18import { annotation, collect, filterDefined } from '../../common/arkts-utils';
19import { ProjectConfig } from '../../common/plugin-context';
20import { classifyProperty, PropertyTranslator } from '../property-translators';
21import {
22    addMemoAnnotation,
23    CustomComponentNames,
24    getCustomComponentOptionsName,
25    getTypeNameFromTypeParameter,
26    getTypeParamsFromClassDecl,
27} from '../utils';
28import { isCustomComponentClass, isKnownMethodDefinition, isEtsGlobalClass, isReourceNode, CustomComponentScopeInfo, findCanAddMemoFromArrowFunction } from './utils';
29import { factory as uiFactory } from '../ui-factory';
30import { factory } from './factory';
31import { isEntryWrapperClass } from '../entry-translators/utils';
32import { factory as entryFactory } from '../entry-translators/factory';
33import { DecoratorNames, hasDecorator } from '../property-translators/utils';
34import { ScopeInfoCollection } from './utils';
35
36function tranformPropertyMembers(
37    className: string,
38    propertyTranslators: PropertyTranslator[],
39    optionsTypeName: string,
40    isDecl?: boolean,
41    scope?: CustomComponentScopeInfo
42): arkts.AstNode[] {
43    const propertyMembers = propertyTranslators.map((translator) => translator.translateMember());
44    const currentStructInfo: arkts.StructInfo = arkts.GlobalInfo.getInfoInstance().getStructInfo(className);
45    const collections = [];
46    if (!scope?.hasInitializeStruct) {
47        collections.push(factory.createInitializeStruct(currentStructInfo, optionsTypeName, isDecl));
48    }
49    if (!scope?.hasUpdateStruct) {
50        collections.push(factory.createUpdateStruct(currentStructInfo, optionsTypeName, isDecl));
51    }
52    if (currentStructInfo.isReusable) {
53        collections.push(factory.toRecord(optionsTypeName, currentStructInfo.toRecordBody));
54    }
55    return collect(...collections, ...propertyMembers);
56}
57
58function transformEtsGlobalClassMembers(node: arkts.ClassDeclaration): arkts.ClassDeclaration {
59    if (!node.definition) {
60        return node;
61    }
62    node.definition.body.map((member: arkts.AstNode) => {
63        if (arkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.BUILDER)) {
64            member.scriptFunction.setAnnotations([annotation('memo')]);
65        }
66        return member;
67    });
68    return node;
69}
70
71function transformOtherMembersInClass(
72    member: arkts.AstNode,
73    classTypeName: string | undefined,
74    classOptionsName: string | undefined,
75    className: string,
76    isDecl?: boolean
77): arkts.AstNode {
78    if (arkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.BUILDER)) {
79        member.scriptFunction.setAnnotations([annotation('memo')]);
80        return member;
81    }
82    if (
83        arkts.isMethodDefinition(member) &&
84        isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI) &&
85        !isDecl
86    ) {
87        return uiFactory.createConstructorMethod(member);
88    }
89    if (arkts.isMethodDefinition(member) && isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_BUILD_ORI)) {
90        return factory.transformBuildMethodWithOriginBuild(
91            member,
92            classTypeName ?? className,
93            classOptionsName ?? getCustomComponentOptionsName(className),
94            isDecl
95        );
96    }
97    return member;
98}
99
100function tranformClassMembers(
101    node: arkts.ClassDeclaration,
102    isDecl?: boolean,
103    scope?: CustomComponentScopeInfo
104): arkts.ClassDeclaration {
105    if (!node.definition) {
106        return node;
107    }
108
109    let classTypeName: string | undefined;
110    let classOptionsName: string | undefined;
111    if (isDecl) {
112        const [classType, classOptions] = getTypeParamsFromClassDecl(node);
113        classTypeName = getTypeNameFromTypeParameter(classType);
114        classOptionsName = getTypeNameFromTypeParameter(classOptions);
115    }
116    const definition: arkts.ClassDefinition = node.definition;
117    const className: string | undefined = node.definition.ident?.name;
118    if (!className) {
119        throw new Error('Non Empty className expected for Component');
120    }
121
122    const propertyTranslators: PropertyTranslator[] = filterDefined(
123        definition.body.map((it) => classifyProperty(it, className))
124    );
125    const translatedMembers: arkts.AstNode[] = tranformPropertyMembers(
126        className,
127        propertyTranslators,
128        classOptionsName ?? getCustomComponentOptionsName(className),
129        isDecl,
130        scope
131    );
132    const updateMembers: arkts.AstNode[] = definition.body
133        .filter((member) => !arkts.isClassProperty(member))
134        .map((member: arkts.AstNode) =>
135            transformOtherMembersInClass(member, classTypeName, classOptionsName, className, isDecl)
136        );
137
138    const updateClassDef: arkts.ClassDefinition = factory.updateCustomComponentClass(definition, [
139        ...translatedMembers,
140        ...updateMembers,
141    ]);
142    return arkts.factory.updateClassDeclaration(node, updateClassDef);
143}
144
145function transformResource(
146    resourceNode: arkts.CallExpression,
147    projectConfig: ProjectConfig | undefined
148): arkts.CallExpression {
149    const newArgs: arkts.AstNode[] = [
150        arkts.factory.create1StringLiteral(projectConfig?.bundleName ? projectConfig.bundleName : ''),
151        arkts.factory.create1StringLiteral(projectConfig?.moduleName ? projectConfig.moduleName : ''),
152        ...resourceNode.arguments,
153    ];
154    return factory.generateTransformedResource(resourceNode, newArgs);
155}
156
157export class StructTransformer extends AbstractVisitor {
158    private scopeInfoCollection: ScopeInfoCollection;
159    projectConfig: ProjectConfig | undefined;
160
161    constructor(projectConfig: ProjectConfig | undefined) {
162        super();
163        this.projectConfig = projectConfig;
164        this.scopeInfoCollection = { customComponents: [] };
165    }
166
167    reset(): void {
168        super.reset();
169        this.scopeInfoCollection = { customComponents: [] };
170    }
171
172    enter(node: arkts.AstNode): void {
173        if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) {
174            this.scopeInfoCollection.customComponents.push({ name: node.definition!.ident!.name });
175        }
176        if (arkts.isMethodDefinition(node) && this.scopeInfoCollection.customComponents.length > 0) {
177            const name = node.name.name;
178            const scopeInfo = this.scopeInfoCollection.customComponents.pop()!;
179            scopeInfo.hasInitializeStruct ||= name === CustomComponentNames.COMPONENT_INITIALIZE_STRUCT;
180            scopeInfo.hasUpdateStruct ||= name === CustomComponentNames.COMPONENT_UPDATE_STRUCT;
181            scopeInfo.hasReusableRebind ||= name === CustomComponentNames.REUSABLE_COMPONENT_REBIND_STATE;
182            this.scopeInfoCollection.customComponents.push(scopeInfo);
183        }
184    }
185
186    exit(node: arkts.AstNode): void {
187        if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) {
188            this.scopeInfoCollection.customComponents.pop();
189        }
190    }
191
192    visitor(beforeChildren: arkts.AstNode): arkts.AstNode {
193        this.enter(beforeChildren);
194        const node = this.visitEachChild(beforeChildren);
195        if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) {
196            let scope: CustomComponentScopeInfo | undefined;
197            const scopeInfos: CustomComponentScopeInfo[] = this.scopeInfoCollection.customComponents;
198            if (scopeInfos.length > 0) {
199                scope = scopeInfos[scopeInfos.length - 1];
200            }
201            const newClass: arkts.ClassDeclaration = tranformClassMembers(
202                node,
203                arkts.hasModifierFlag(node, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE),
204                scope
205            );
206            this.exit(beforeChildren);
207            return newClass;
208        } else if (isEntryWrapperClass(node)) {
209            entryFactory.addMemoToEntryWrapperClassMethods(node);
210            return node;
211        } else if (arkts.isClassDeclaration(node) && isEtsGlobalClass(node)) {
212            return transformEtsGlobalClassMembers(node);
213        } else if (arkts.isCallExpression(node) && isReourceNode(node)) {
214            return transformResource(node, this.projectConfig);
215        } else if (findCanAddMemoFromArrowFunction(node)) {
216            return addMemoAnnotation(node);
217        }
218        return node;
219    }
220}
221