• 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 { backingField, expectName } from '../../common/arkts-utils';
18import { DecoratorNames, hasDecorator } from './utils';
19import { ClassScopeInfo } from 'ui-plugins/checked-transformer';
20
21export class ObservedTrackTranslator {
22    constructor(protected property: arkts.ClassProperty, protected classScopeInfo: ClassScopeInfo) {}
23
24    private hasImplement: boolean = expectName(this.property.key).startsWith('<property>');
25    private isTracked: boolean = hasDecorator(this.property, DecoratorNames.TRACK);
26
27    translateMember(): arkts.AstNode[] {
28        if (!this.isTracked && (this.classScopeInfo.classHasTrack || !this.classScopeInfo.isObserved)) {
29            return [this.property];
30        }
31        const originalName: string = this.hasImplement
32            ? this.removeImplementProperty(expectName(this.property.key))
33            : expectName(this.property.key);
34        const newName: string = backingField(originalName);
35        let properyIsClass = false;
36
37        if (this.property.typeAnnotation && arkts.isETSTypeReference(this.property.typeAnnotation)) {
38            const decl = arkts.getDecl(this.property.typeAnnotation.part?.name!);
39            if (arkts.isClassDefinition(decl!)) {
40                properyIsClass = true;
41            }
42        }
43        const field = this.createField(originalName, newName, properyIsClass);
44
45        this.transformGetterSetter(originalName, newName, properyIsClass);
46
47        return [...field];
48    }
49
50    createField(originalName: string, newName: string, properyIsClass: boolean): arkts.ClassProperty[] {
51        const backingField = properyIsClass
52            ? this.propertyIsClassField(newName)
53            : arkts.factory.createClassProperty(
54                  arkts.factory.createIdentifier(newName),
55                  this.property.value,
56                  this.property.typeAnnotation,
57                  arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE,
58                  false
59              );
60        if (!this.isTracked) {
61            return [backingField];
62        }
63        const metaField = this.metaField(originalName);
64        return [backingField, metaField];
65    }
66
67    createGetter(originalName: string, newName: string, properyIsClass: boolean): arkts.MethodDefinition {
68        const ifRefDepth: arkts.IfStatement = this.getterIfRefDepth(originalName);
69        const returnMember: arkts.ReturnStatement = this.getterReturnMember(properyIsClass, newName);
70        const setObservationDepth = this.getterSetObservationDepth(newName);
71
72        const body = arkts.factory.createBlock([
73            ifRefDepth,
74            ...(properyIsClass ? [setObservationDepth] : []),
75            returnMember,
76        ]);
77
78        const scriptFunction = arkts.factory.createScriptFunction(
79            body,
80            arkts.FunctionSignature.createFunctionSignature(undefined, [], this.property.typeAnnotation, false),
81            arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER,
82            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC
83        );
84
85        return arkts.factory.createMethodDefinition(
86            arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET,
87            arkts.factory.createIdentifier(originalName),
88            scriptFunction,
89            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC,
90            false
91        );
92    }
93
94    createSetter(originalName: string, newName: string, properyIsClass: boolean): arkts.MethodDefinition {
95        const ifEqualsNewValue: arkts.IfStatement = this.setterIfEqualsNewValue(properyIsClass, originalName, newName);
96        const body = arkts.factory.createBlock([ifEqualsNewValue]);
97        const param = arkts.factory.createParameterDeclaration(
98            arkts.factory.createIdentifier('newValue', this.property.typeAnnotation),
99            undefined
100        );
101
102        const scriptFunction = arkts.factory.createScriptFunction(
103            body,
104            arkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false),
105            arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER,
106            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC
107        );
108
109        return arkts.factory.createMethodDefinition(
110            arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET,
111            arkts.factory.createIdentifier(originalName),
112            scriptFunction,
113            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC,
114            false
115        );
116    }
117
118    genThisBacking(newName: string): arkts.MemberExpression {
119        return arkts.factory.createMemberExpression(
120            arkts.factory.createThisExpression(),
121            arkts.factory.createIdentifier(newName),
122            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
123            false,
124            false
125        );
126    }
127
128    genThisBackingValue(newName: string): arkts.MemberExpression {
129        return arkts.factory.createMemberExpression(
130            this.genThisBacking(newName),
131            arkts.factory.createIdentifier('value'),
132            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
133            false,
134            false
135        );
136    }
137
138    metaIdentifier(originalName: string): arkts.Identifier {
139        return this.isTracked
140            ? arkts.factory.createIdentifier(`__meta_${originalName}`)
141            : arkts.factory.createIdentifier('__meta');
142    }
143
144    removeImplementProperty(originalName: string): string {
145        const prefix = '<property>';
146        return originalName.substring(prefix.length);
147    }
148
149    transformGetterSetter(originalName: string, newName: string, properyIsClass: boolean): void {
150        const newGetter = this.createGetter(originalName, newName, properyIsClass);
151        const newSetter = this.createSetter(originalName, newName, properyIsClass);
152        if (this.hasImplement) {
153            {
154                const idx: number = this.classScopeInfo.getters.findIndex(
155                    (getter) => getter.name.name === originalName
156                );
157                const originGetter: arkts.MethodDefinition = this.classScopeInfo.getters[idx];
158                const originSetter: arkts.MethodDefinition = originGetter.overloads[0];
159                const updateGetter: arkts.MethodDefinition = arkts.factory.updateMethodDefinition(
160                    originGetter,
161                    originGetter.kind,
162                    newGetter.name,
163                    newGetter.scriptFunction.addFlag(arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD),
164                    originGetter.modifiers,
165                    false
166                );
167                arkts.factory.updateMethodDefinition(
168                    originSetter,
169                    originSetter.kind,
170                    newSetter.name,
171                    newSetter.scriptFunction
172                        .addFlag(arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_OVERLOAD)
173                        .addFlag(arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD),
174                    originSetter.modifiers,
175                    false
176                );
177                this.classScopeInfo.getters[idx] = updateGetter;
178            }
179        } else {
180            this.classScopeInfo.getters.push(...[newGetter, newSetter]);
181        }
182    }
183
184    propertyIsClassField(newName: string): arkts.ClassProperty {
185        return arkts.factory.createClassProperty(
186            arkts.factory.createIdentifier(newName),
187            this.property.value
188                ? arkts.factory.createETSNewClassInstanceExpression(
189                      arkts.factory.createTypeReference(
190                          arkts.factory.createTypeReferencePart(
191                              arkts.factory.createIdentifier('BackingValue'),
192                              arkts.factory.createTSTypeParameterInstantiation(
193                                  this.property.typeAnnotation ? [this.property.typeAnnotation] : []
194                              )
195                          )
196                      ),
197                      [this.property.value]
198                  )
199                : undefined,
200            arkts.factory.createTypeReference(
201                arkts.factory.createTypeReferencePart(
202                    arkts.factory.createIdentifier('BackingValue'),
203                    arkts.factory.createTSTypeParameterInstantiation(
204                        this.property.typeAnnotation ? [this.property.typeAnnotation] : []
205                    )
206                )
207            ),
208            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE,
209            false
210        );
211    }
212
213    metaField(originalName: string): arkts.ClassProperty {
214        return arkts.factory.createClassProperty(
215            arkts.factory.createIdentifier(`__meta_${originalName}`),
216            arkts.factory.createETSNewClassInstanceExpression(
217                arkts.factory.createTypeReference(
218                    arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('MutableStateMeta'))
219                ),
220                [arkts.factory.createStringLiteral('@Track')]
221            ),
222            arkts.factory.createTypeReference(
223                arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('MutableStateMeta'))
224            ),
225            arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE,
226            false
227        );
228    }
229
230    getterIfRefDepth(originalName: string): arkts.IfStatement {
231        return arkts.factory.createIfStatement(
232            arkts.factory.createBinaryExpression(
233                arkts.factory.createMemberExpression(
234                    arkts.factory.createThisExpression(),
235                    arkts.factory.createIdentifier('_permissibleAddRefDepth'),
236                    arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
237                    false,
238                    false
239                ),
240                arkts.factory.createNumericLiteral(0),
241                arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_GREATER_THAN
242            ),
243            arkts.factory.createBlock([
244                arkts.factory.createExpressionStatement(
245                    arkts.factory.createCallExpression(
246                        arkts.factory.createMemberExpression(
247                            arkts.factory.createMemberExpression(
248                                arkts.factory.createThisExpression(),
249                                this.metaIdentifier(originalName),
250                                arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
251                                false,
252                                false
253                            ),
254                            arkts.factory.createIdentifier('addRef'),
255                            arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
256                            false,
257                            false
258                        ),
259                        undefined,
260                        undefined,
261                        false,
262                        false
263                    )
264                ),
265            ])
266        );
267    }
268
269    getterSetObservationDepth(newName: string): arkts.ExpressionStatement {
270        return arkts.factory.createExpressionStatement(
271            arkts.factory.createCallExpression(arkts.factory.createIdentifier('setObservationDepth'), undefined, [
272                this.genThisBackingValue(newName),
273                arkts.factory.createBinaryExpression(
274                    arkts.factory.createMemberExpression(
275                        arkts.factory.createThisExpression(),
276                        arkts.factory.createIdentifier('_permissibleAddRefDepth'),
277                        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
278                        false,
279                        false
280                    ),
281                    arkts.factory.createNumericLiteral(1),
282                    arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_MINUS
283                ),
284            ])
285        );
286    }
287
288    getterReturnMember(properyIsClass: boolean, newName: string): arkts.ReturnStatement {
289        return arkts.factory.createReturnStatement(
290            properyIsClass ? this.genThisBackingValue(newName) : this.genThisBacking(newName)
291        );
292    }
293
294    setterIfEqualsNewValue(properyIsClass: boolean, originalName: string, newName: string): arkts.IfStatement {
295        const backingValue = properyIsClass ? this.genThisBackingValue(newName) : this.genThisBacking(newName);
296
297        const setNewValue = arkts.factory.createExpressionStatement(
298            arkts.factory.createAssignmentExpression(
299                backingValue,
300                arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION,
301                arkts.factory.createIdentifier('newValue')
302            )
303        );
304
305        const fireChange = arkts.factory.createExpressionStatement(
306            arkts.factory.createCallExpression(
307                arkts.factory.createMemberExpression(
308                    arkts.factory.createMemberExpression(
309                        arkts.factory.createThisExpression(),
310                        this.metaIdentifier(originalName),
311                        arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
312                        false,
313                        false
314                    ),
315                    arkts.factory.createIdentifier('fireChange'),
316                    arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
317                    false,
318                    false
319                ),
320                undefined,
321                undefined
322            )
323        );
324
325        const subscribingWatches = arkts.factory.createExpressionStatement(
326            arkts.factory.createCallExpression(
327                arkts.factory.createMemberExpression(
328                    arkts.factory.createThisExpression(),
329                    arkts.factory.createIdentifier('executeOnSubscribingWatches'),
330                    arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS,
331                    false,
332                    false
333                ),
334                undefined,
335                [arkts.factory.createStringLiteral(originalName)]
336            )
337        );
338
339        return arkts.factory.createIfStatement(
340            arkts.factory.createBinaryExpression(
341                backingValue,
342                arkts.factory.createIdentifier('newValue'),
343                arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL
344            ),
345            arkts.factory.createBlock([setNewValue, fireChange, subscribingWatches])
346        );
347    }
348}
349