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