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'; 17 18import { 19 createGetter, 20 DecoratorNames, 21 generateGetOrSetCall, 22 generateThisBacking, 23 generateToRecord, 24 hasDecorator, 25 judgeIfAddWatchFunc, 26} from './utils'; 27import { PropertyTranslator } from './base'; 28import { GetterSetter, InitializerConstructor } from './types'; 29import { backingField, expectName } from '../../common/arkts-utils'; 30import { factory } from './factory'; 31import { createOptionalClassProperty } from '../utils'; 32 33export class ObjectLinkTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { 34 translateMember(): arkts.AstNode[] { 35 const originalName: string = expectName(this.property.key); 36 const newName: string = backingField(originalName); 37 if (!this.ifObservedDecoratedClass()) { 38 throw new Error('@ObjectLink decorated property only accepts @Observed decorated class instance'); // TODO: replace this with proper error message. 39 } 40 41 this.cacheTranslatedInitializer(newName, originalName); // TODO: need to release cache after some point... 42 return this.translateWithoutInitializer(newName, originalName); 43 } 44 45 cacheTranslatedInitializer(newName: string, originalName: string): void { 46 const currentStructInfo: arkts.StructInfo = arkts.GlobalInfo.getInfoInstance().getStructInfo(this.structName); 47 const initializeStruct: arkts.AstNode = this.generateInitializeStruct(newName, originalName); 48 const updateStruct: arkts.AstNode = this.generateUpdateStruct(newName, originalName); 49 currentStructInfo.initializeBody.push(initializeStruct); 50 currentStructInfo.updateBody.push(updateStruct); 51 52 if (currentStructInfo.isReusable) { 53 const toRecord = generateToRecord(newName, originalName); 54 currentStructInfo.toRecordBody.push(toRecord); 55 } 56 57 arkts.GlobalInfo.getInfoInstance().setStructInfo(this.structName, currentStructInfo); 58 } 59 60 generateInitializeStruct(newName: string, originalName: string): arkts.AstNode { 61 const initializers = arkts.factory.createTSNonNullExpression( 62 factory.createBlockStatementForOptionalExpression( 63 arkts.factory.createIdentifier('initializers'), 64 originalName 65 ) 66 ); 67 68 const args: arkts.Expression[] = [arkts.factory.create1StringLiteral(originalName), initializers]; 69 judgeIfAddWatchFunc(args, this.property); 70 71 const newClass = arkts.factory.createETSNewClassInstanceExpression( 72 arkts.factory.createTypeReference( 73 arkts.factory.createTypeReferencePart( 74 arkts.factory.createIdentifier('ObjectLinkDecoratedVariable'), 75 arkts.factory.createTSTypeParameterInstantiation( 76 this.property.typeAnnotation ? [this.property.typeAnnotation] : [] 77 ) 78 ) 79 ), 80 args 81 ); 82 83 return arkts.factory.createAssignmentExpression( 84 generateThisBacking(newName), 85 arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, 86 newClass 87 ); 88 } 89 90 generateUpdateStruct(newName: string, originalName: string): arkts.AstNode { 91 const binaryItem = arkts.factory.createBinaryExpression( 92 factory.createBlockStatementForOptionalExpression( 93 arkts.factory.createIdentifier('initializers'), 94 originalName 95 ), 96 arkts.factory.createUndefinedLiteral(), 97 arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL 98 ); 99 const member: arkts.MemberExpression = arkts.factory.createMemberExpression( 100 generateThisBacking(newName, false, true), 101 arkts.factory.createIdentifier('update'), 102 arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, 103 false, 104 false 105 ); 106 const nonNullItem = arkts.factory.createTSNonNullExpression( 107 factory.createNonNullOrOptionalMemberExpression('initializers', originalName, false, true) 108 ); 109 return arkts.factory.createIfStatement( 110 binaryItem, 111 arkts.factory.createBlock([ 112 arkts.factory.createExpressionStatement( 113 arkts.factory.createCallExpression(member, undefined, [nonNullItem]) 114 ), 115 ]) 116 ); 117 } 118 119 translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { 120 const field: arkts.ClassProperty = createOptionalClassProperty( 121 newName, 122 this.property, 123 'ObjectLinkDecoratedVariable', 124 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE 125 ); 126 const thisValue: arkts.Expression = generateThisBacking(newName, false, true); 127 const thisGet: arkts.CallExpression = generateGetOrSetCall(thisValue, 'get'); 128 const getter: arkts.MethodDefinition = this.translateGetter( 129 originalName, 130 this.property.typeAnnotation, 131 thisGet 132 ); 133 return [field, getter]; 134 } 135 136 translateGetter( 137 originalName: string, 138 typeAnnotation: arkts.TypeNode | undefined, 139 returnValue: arkts.Expression 140 ): arkts.MethodDefinition { 141 return createGetter(originalName, typeAnnotation, returnValue); 142 } 143 144 ifObservedDecoratedClass(): boolean { 145 if (this.property.typeAnnotation && arkts.isETSTypeReference(this.property.typeAnnotation)) { 146 const decl = arkts.getDecl(this.property.typeAnnotation.part?.name!); 147 if (arkts.isClassDefinition(decl!) && hasDecorator(decl, DecoratorNames.OBSERVED)) { 148 return true; 149 } 150 } 151 return false; 152 } 153} 154