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 { annotation } from '../../common/arkts-utils'; 18import { factory } from './factory'; 19 20export enum DecoratorNames { 21 STATE = 'State', 22 STORAGE_LINK = 'StorageLink', 23 STORAGE_PROP = 'StorageProp', 24 LINK = 'Link', 25 PROP = 'Prop', 26 PROVIDE = 'Provide', 27 CONSUME = 'Consume', 28 OBJECT_LINK = 'ObjectLink', 29 OBSERVED = 'Observed', 30 WATCH = 'Watch', 31 BUILDER_PARAM = 'BuilderParam', 32 BUILDER = 'Builder', 33 CUSTOM_DIALOG = 'CustomDialog', 34 LOCAL_STORAGE_PROP = 'LocalStorageProp', 35 LOCAL_STORAGE_LINK = 'LocalStorageLink', 36 REUSABLE = 'Reusable', 37 TRACK = 'Track', 38} 39 40export function collectPropertyDecorators(property: arkts.ClassProperty): string[] { 41 const properties: string[] = []; 42 property.annotations.forEach((anno) => { 43 if (!!anno.expr && arkts.isIdentifier(anno.expr)) { 44 properties.push(anno.expr.name); 45 } 46 }); 47 return properties; 48} 49 50export function isDecoratorAnnotation(anno: arkts.AnnotationUsage, decoratorName: DecoratorNames): boolean { 51 return !!anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; 52} 53 54export function hasDecorator( 55 property: arkts.ClassProperty | arkts.ClassDefinition | arkts.MethodDefinition, 56 decoratorName: DecoratorNames 57): boolean { 58 if (arkts.isMethodDefinition(property)) { 59 return property.scriptFunction.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName)); 60 } 61 return property.annotations.some((anno) => isDecoratorAnnotation(anno, decoratorName)); 62} 63 64/** 65 * Determine whether the node `<st>` is decorated by decorators that need initializing without assignment. 66 * 67 * @param st class property node 68 */ 69export function needDefiniteOrOptionalModifier(st: arkts.ClassProperty): boolean { 70 return ( 71 hasDecorator(st, DecoratorNames.LINK) || 72 hasDecorator(st, DecoratorNames.CONSUME) || 73 hasDecorator(st, DecoratorNames.OBJECT_LINK) || 74 (hasDecorator(st, DecoratorNames.PROP) && !st.value) 75 ); 76} 77 78export function getStateManagementType(node: arkts.ClassProperty): string { 79 if (hasDecorator(node, DecoratorNames.STATE)) { 80 return 'StateDecoratedVariable'; 81 } else if (hasDecorator(node, DecoratorNames.LINK)) { 82 return 'DecoratedV1VariableBase'; 83 } else if (hasDecorator(node, DecoratorNames.PROP)) { 84 return 'PropDecoratedVariable'; 85 } else if (hasDecorator(node, DecoratorNames.STORAGE_LINK)) { 86 return 'StorageLinkDecoratedVariable'; 87 } else if (hasDecorator(node, DecoratorNames.STORAGE_PROP)) { 88 return 'StoragePropDecoratedVariable'; 89 } else if (hasDecorator(node, DecoratorNames.PROVIDE)) { 90 return 'ProvideDecoratedVariable'; 91 } else if (hasDecorator(node, DecoratorNames.CONSUME)) { 92 return 'ConsumeDecoratedVariable'; 93 } else if (hasDecorator(node, DecoratorNames.OBJECT_LINK)) { 94 return 'ObjectLinkDecoratedVariable'; 95 } else if (hasDecorator(node, DecoratorNames.LOCAL_STORAGE_PROP)) { 96 return 'SyncedProperty'; 97 } 98 return 'MutableState'; 99} 100 101export function createGetter( 102 name: string, 103 type: arkts.TypeNode | undefined, 104 returns: arkts.Expression, 105 needMemo: boolean = false, 106): arkts.MethodDefinition { 107 const returnType: arkts.TypeNode | undefined = type?.clone(); 108 if (needMemo) { 109 returnType?.setAnnotations([annotation('memo')]); 110 } 111 const body = arkts.factory.createBlock([arkts.factory.createReturnStatement(returns)]); 112 const scriptFunction = arkts.factory.createScriptFunction( 113 body, 114 arkts.FunctionSignature.createFunctionSignature(undefined, [], returnType, false), 115 arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER, 116 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC 117 ); 118 return arkts.factory.createMethodDefinition( 119 arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, 120 arkts.factory.createIdentifier(name), 121 scriptFunction, 122 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, 123 false 124 ); 125} 126 127export function createSetter( 128 name: string, 129 type: arkts.TypeNode | undefined, 130 left: arkts.Expression, 131 right: arkts.AstNode, 132 needMemo: boolean = false 133): arkts.MethodDefinition { 134 const body = arkts.factory.createBlock([ 135 arkts.factory.createExpressionStatement( 136 arkts.factory.createAssignmentExpression( 137 left, 138 arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, 139 right 140 ) 141 ), 142 ]); 143 const param: arkts.ETSParameterExpression = arkts.factory.createParameterDeclaration( 144 arkts.factory.createIdentifier('value', type?.clone()), 145 undefined 146 ); 147 if (needMemo) { 148 param.annotations = [annotation('memo')]; 149 } 150 const scriptFunction = arkts.factory.createScriptFunction( 151 body, 152 arkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false), 153 arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, 154 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC 155 ); 156 157 return arkts.factory.createMethodDefinition( 158 arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, 159 arkts.factory.createIdentifier(name), 160 scriptFunction, 161 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, 162 false 163 ); 164} 165 166export function createSetter2( 167 name: string, 168 type: arkts.TypeNode | undefined, 169 statement: arkts.AstNode 170): arkts.MethodDefinition { 171 const body = arkts.factory.createBlock([statement]); 172 const param: arkts.ETSParameterExpression = arkts.factory.createParameterDeclaration( 173 arkts.factory.createIdentifier('value', type?.clone()), 174 undefined 175 ); 176 const scriptFunction = arkts.factory.createScriptFunction( 177 body, 178 arkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false), 179 arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, 180 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC 181 ); 182 183 return arkts.factory.createMethodDefinition( 184 arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, 185 arkts.factory.createIdentifier(name), 186 scriptFunction, 187 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, 188 false 189 ); 190} 191 192export function generateThisBackingValue( 193 name: string, 194 optional: boolean = false, 195 nonNull: boolean = false 196): arkts.MemberExpression { 197 const member: arkts.Expression = generateThisBacking(name, optional, nonNull); 198 return arkts.factory.createMemberExpression( 199 member, 200 arkts.factory.createIdentifier('value'), 201 arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, 202 false, 203 false 204 ); 205} 206 207export function generateThisBacking( 208 name: string, 209 optional: boolean = false, 210 nonNull: boolean = false 211): arkts.Expression { 212 const member: arkts.Expression = arkts.factory.createMemberExpression( 213 arkts.factory.createThisExpression(), 214 arkts.factory.createIdentifier(`${name}`), 215 arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, 216 false, 217 optional 218 ); 219 return nonNull ? arkts.factory.createTSNonNullExpression(member) : member; 220} 221 222function getValueStr(node: arkts.AstNode): string | undefined { 223 if (!arkts.isClassProperty(node) || !node.value) return undefined; 224 return arkts.isStringLiteral(node.value) ? node.value.str : undefined; 225} 226 227function getAnnotationValue(anno: arkts.AnnotationUsage, decoratorName: DecoratorNames): string | undefined { 228 const isSuitableAnnotation: boolean = 229 !!anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; 230 if (isSuitableAnnotation && anno.properties.length === 1) { 231 return getValueStr(anno.properties.at(0)!); 232 } 233 return undefined; 234} 235 236export function getValueInAnnotation(node: arkts.ClassProperty, decoratorName: DecoratorNames): string | undefined { 237 const annotations: readonly arkts.AnnotationUsage[] = node.annotations; 238 for (let i = 0; i < annotations.length; i++) { 239 const anno: arkts.AnnotationUsage = annotations[i]; 240 const str: string | undefined = getAnnotationValue(anno, decoratorName); 241 if (!!str) { 242 return str; 243 } 244 } 245 return undefined; 246} 247 248export interface ProvideOptions { 249 alias: string; 250 allowOverride: boolean; 251} 252 253export function getValueInProvideAnnotation(node: arkts.ClassProperty): ProvideOptions | undefined { 254 const annotations: readonly arkts.AnnotationUsage[] = node.annotations; 255 for (let i = 0; i < annotations.length; i++) { 256 const anno: arkts.AnnotationUsage = annotations[i]; 257 if (anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.PROVIDE) { 258 const alias: string = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'alias'); 259 const allowOverride: boolean = getValueInObjectAnnotation(anno, DecoratorNames.PROVIDE, 'allowOverride') 260 ? true 261 : false; 262 return { alias, allowOverride }; 263 } 264 } 265 return undefined; 266} 267 268function getValueInObjectAnnotation(anno: arkts.AnnotationUsage, decoratorName: DecoratorNames, key: string): any { 269 const isSuitableAnnotation: boolean = 270 !!anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === decoratorName; 271 if (!isSuitableAnnotation) { 272 return undefined; 273 } 274 const keyItem: arkts.AstNode | undefined = anno.properties.find( 275 (annoProp: arkts.AstNode) => 276 arkts.isClassProperty(annoProp) && 277 annoProp.key && 278 arkts.isIdentifier(annoProp.key) && 279 annoProp.key.name === key 280 ); 281 if (keyItem && arkts.isClassProperty(keyItem) && keyItem.value) { 282 return getDifferentAnnoTypeValue(keyItem.value); 283 } 284 return undefined; 285} 286 287function getDifferentAnnoTypeValue(value: arkts.Expression): string | boolean { 288 if (arkts.isBooleanLiteral(value)) { 289 return value.value; 290 } else if (arkts.isStringLiteral(value)) { 291 return value.str; 292 } 293 return value.dumpSrc(); 294} 295 296export function judgeIfAddWatchFunc(args: arkts.Expression[], property: arkts.ClassProperty): void { 297 if (hasDecorator(property, DecoratorNames.WATCH)) { 298 const watchStr: string | undefined = getValueInAnnotation(property, DecoratorNames.WATCH); 299 if (watchStr) { 300 args.push(factory.createWatchCallback(watchStr)); 301 } 302 } 303} 304 305export function generateGetOrSetCall(beforCall: arkts.AstNode, type: string) { 306 return arkts.factory.createCallExpression( 307 arkts.factory.createMemberExpression( 308 beforCall, 309 arkts.factory.createIdentifier(type), 310 arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, 311 false, 312 false 313 ), 314 undefined, 315 type === 'set' ? [arkts.factory.createIdentifier('value')] : undefined, 316 undefined 317 ); 318} 319 320export function generateToRecord(newName: string, originalName: string): arkts.Property { 321 return arkts.Property.createProperty( 322 arkts.factory.createStringLiteral(originalName), 323 arkts.factory.createBinaryExpression( 324 arkts.factory.createMemberExpression( 325 arkts.factory.createIdentifier('paramsCasted'), 326 arkts.factory.createIdentifier(originalName), 327 arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, 328 false, 329 false 330 ), 331 arkts.ETSNewClassInstanceExpression.createETSNewClassInstanceExpression( 332 arkts.factory.createTypeReference( 333 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('Object')) 334 ), 335 [] 336 ), 337 arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING 338 ) 339 ); 340} 341 342export function getStageManagementIdent(property: arkts.ClassProperty): string { 343 const useMutableState: boolean = 344 hasDecorator(property, DecoratorNames.STATE) || 345 hasDecorator(property, DecoratorNames.STORAGE_LINK) || 346 hasDecorator(property, DecoratorNames.PROVIDE) || 347 hasDecorator(property, DecoratorNames.CONSUME) || 348 hasDecorator(property, DecoratorNames.LINK) || 349 hasDecorator(property, DecoratorNames.LOCAL_STORAGE_LINK) || 350 hasDecorator(property, DecoratorNames.LINK); 351 const useSyncedProperty: boolean = 352 hasDecorator(property, DecoratorNames.PROP) || 353 hasDecorator(property, DecoratorNames.STORAGE_PROP) || 354 hasDecorator(property, DecoratorNames.LOCAL_STORAGE_PROP) || 355 hasDecorator(property, DecoratorNames.OBJECT_LINK); 356 if (useMutableState) { 357 return 'MutableState'; 358 } else if (useSyncedProperty) { 359 return 'SyncedProperty'; 360 } else { 361 return ''; 362 } 363} 364