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 { ProjectConfig } from '../common/plugin-context'; 18import { factory as structFactory } from './struct-translators/factory'; 19import { factory as builderLambdaFactory } from './builder-lambda-translators/factory'; 20import { factory as uiFactory } from './ui-factory'; 21import { factory as entryFactory } from './entry-translators/factory'; 22import { AbstractVisitor } from '../common/abstract-visitor'; 23import { annotation, collect, filterDefined } from '../common/arkts-utils'; 24import { 25 CustomComponentNames, 26 getCustomComponentOptionsName, 27 getTypeNameFromTypeParameter, 28 getTypeParamsFromClassDecl, 29 getGettersFromClassDecl, 30 addMemoAnnotation, 31} from './utils'; 32import { hasDecorator, DecoratorNames } from './property-translators/utils'; 33import { 34 isCustomComponentClass, 35 isKnownMethodDefinition, 36 isEtsGlobalClass, 37 isReourceNode, 38 ScopeInfoCollection, 39 CustomComponentScopeInfo, 40 isMemoCall, 41 findCanAddMemoFromArrowFunction, 42} from './struct-translators/utils'; 43import { isBuilderLambda, isBuilderLambdaMethodDecl } from './builder-lambda-translators/utils'; 44import { isEntryWrapperClass } from './entry-translators/utils'; 45import { classifyObservedTrack, classifyProperty, PropertyTranslator } from './property-translators'; 46import { ObservedTrackTranslator } from './property-translators/observedTrack'; 47import { nodeByType } from '@koalaui/libarkts/build/src/reexport-for-generated'; 48import { isArkUICompatible, updateArkUICompatible } from './interop'; 49 50export class CheckedTransformer extends AbstractVisitor { 51 private scopeInfoCollection: ScopeInfoCollection; 52 projectConfig: ProjectConfig | undefined; 53 54 constructor(projectConfig: ProjectConfig | undefined) { 55 super(); 56 this.projectConfig = projectConfig; 57 this.scopeInfoCollection = { customComponents: [] }; 58 } 59 60 reset(): void { 61 super.reset(); 62 this.scopeInfoCollection = { customComponents: [] }; 63 } 64 65 enter(node: arkts.AstNode): void { 66 if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) { 67 this.scopeInfoCollection.customComponents.push({ name: node.definition!.ident!.name }); 68 } 69 if (arkts.isMethodDefinition(node) && this.scopeInfoCollection.customComponents.length > 0) { 70 const name = node.name.name; 71 const scopeInfo = this.scopeInfoCollection.customComponents.pop()!; 72 scopeInfo.hasInitializeStruct ||= name === CustomComponentNames.COMPONENT_INITIALIZE_STRUCT; 73 scopeInfo.hasUpdateStruct ||= name === CustomComponentNames.COMPONENT_UPDATE_STRUCT; 74 scopeInfo.hasReusableRebind ||= name === CustomComponentNames.REUSABLE_COMPONENT_REBIND_STATE; 75 this.scopeInfoCollection.customComponents.push(scopeInfo); 76 } 77 } 78 79 exit(node: arkts.AstNode): void { 80 if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) { 81 this.scopeInfoCollection.customComponents.pop(); 82 } 83 } 84 85 visitor(beforeChildren: arkts.AstNode): arkts.AstNode { 86 this.enter(beforeChildren); 87 if (arkts.isCallExpression(beforeChildren) && isBuilderLambda(beforeChildren)) { 88 const lambda = builderLambdaFactory.transformBuilderLambda(beforeChildren, this.projectConfig); 89 return this.visitEachChild(lambda); 90 } else if (arkts.isMethodDefinition(beforeChildren) && isBuilderLambdaMethodDecl(beforeChildren)) { 91 const lambda = builderLambdaFactory.transformBuilderLambdaMethodDecl(beforeChildren); 92 return this.visitEachChild(lambda); 93 } 94 const node = this.visitEachChild(beforeChildren); 95 if (arkts.isClassDeclaration(node) && isCustomComponentClass(node)) { 96 let scope: CustomComponentScopeInfo | undefined; 97 const scopeInfos: CustomComponentScopeInfo[] = this.scopeInfoCollection.customComponents; 98 if (scopeInfos.length > 0) { 99 scope = scopeInfos[scopeInfos.length - 1]; 100 } 101 const newClass: arkts.ClassDeclaration = tranformClassMembers( 102 node, 103 arkts.hasModifierFlag(node, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE), 104 scope 105 ); 106 this.exit(beforeChildren); 107 return newClass; 108 } else if (isEntryWrapperClass(node)) { 109 entryFactory.addMemoToEntryWrapperClassMethods(node); 110 return node; 111 } else if (arkts.isClassDeclaration(node) && isEtsGlobalClass(node)) { 112 return transformEtsGlobalClassMembers(node); 113 } else if (arkts.isCallExpression(node) && isReourceNode(node)) { 114 return transformResource(node, this.projectConfig); 115 } else if (findCanAddMemoFromArrowFunction(node)) { 116 return addMemoAnnotation(node); 117 } else if (arkts.isClassDeclaration(node)) { 118 return transformObservedTracked(node); 119 } else if (isArkUICompatible(node)) { 120 return updateArkUICompatible(node as arkts.CallExpression); 121 } else if (this.externalSourceName) { 122 return structFactory.transformExternalSource(this.externalSourceName, node); 123 } 124 return node; 125 } 126} 127 128export type ClassScopeInfo = { 129 isObserved: boolean; 130 classHasTrack: boolean; 131 getters: arkts.MethodDefinition[]; 132}; 133 134function transformObservedTracked(node: arkts.ClassDeclaration): arkts.ClassDeclaration { 135 if (!node.definition) { 136 return node; 137 } 138 const isObserved: boolean = hasDecorator(node.definition, DecoratorNames.OBSERVED); 139 const classHasTrack: boolean = node.definition.body.some( 140 (member) => arkts.isClassProperty(member) && hasDecorator(member, DecoratorNames.TRACK) 141 ); 142 if (!isObserved && !classHasTrack) { 143 return node; 144 } 145 146 const updateClassDef: arkts.ClassDefinition = arkts.factory.updateClassDefinition( 147 node.definition, 148 node.definition.ident, 149 node.definition.typeParams, 150 node.definition.superTypeParams, 151 [ 152 ...node.definition.implements, 153 arkts.TSClassImplements.createTSClassImplements( 154 arkts.factory.createTypeReference( 155 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('IObservedObject')) 156 ) 157 ), 158 ], 159 undefined, 160 node.definition.super, 161 observedTrackPropertyMembers(classHasTrack, node.definition, isObserved), 162 node.definition.modifiers, 163 arkts.classDefinitionFlags(node.definition) 164 ); 165 return arkts.factory.updateClassDeclaration(node, updateClassDef); 166} 167 168function observedTrackPropertyMembers( 169 classHasTrack: boolean, 170 definition: arkts.ClassDefinition, 171 isObserved: boolean 172): arkts.AstNode[] { 173 const watchMembers: arkts.AstNode[] = createWatchMembers(); 174 const permissibleAddRefDepth: arkts.ClassProperty = arkts.factory.createClassProperty( 175 arkts.factory.createIdentifier('_permissibleAddRefDepth'), 176 arkts.factory.createNumericLiteral(0), 177 arkts.factory.createTypeReference( 178 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('int32')) 179 ), 180 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, 181 false 182 ); 183 184 const meta: arkts.ClassProperty = arkts.factory.createClassProperty( 185 arkts.factory.createIdentifier('__meta'), 186 arkts.factory.createETSNewClassInstanceExpression( 187 arkts.factory.createTypeReference( 188 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('MutableStateMeta')) 189 ), 190 [arkts.factory.createStringLiteral('@Observe properties (no @Track)')] 191 ), 192 arkts.factory.createTypeReference( 193 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('MutableStateMeta')) 194 ), 195 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, 196 false 197 ); 198 199 const getters: arkts.MethodDefinition[] = getGettersFromClassDecl(definition); 200 201 const classScopeInfo: ClassScopeInfo = { 202 isObserved: isObserved, 203 classHasTrack: classHasTrack, 204 getters: getters, 205 }; 206 207 const propertyTranslators: ObservedTrackTranslator[] = filterDefined( 208 definition.body.map((it) => classifyObservedTrack(it, classScopeInfo)) 209 ); 210 211 const propertyMembers = propertyTranslators.map((translator) => translator.translateMember()); 212 213 const nonClassPropertyOrGetter: arkts.AstNode[] = definition.body.filter( 214 (member) => 215 !arkts.isClassProperty(member) && 216 !( 217 arkts.isMethodDefinition(member) && 218 arkts.hasModifierFlag(member, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_GETTER) 219 ) 220 ); 221 222 return [ 223 ...watchMembers, 224 ...(classHasTrack ? [permissibleAddRefDepth] : [permissibleAddRefDepth, meta]), 225 ...collect(...propertyMembers), 226 ...nonClassPropertyOrGetter, 227 ...classScopeInfo.getters, 228 ]; 229} 230 231function createWatchMethod( 232 methodName: string, 233 returnType: arkts.Es2pandaPrimitiveType, 234 paramName: string, 235 paramType: string, 236 isReturnStatement: boolean 237): arkts.MethodDefinition { 238 return arkts.factory.createMethodDefinition( 239 arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, 240 arkts.factory.createIdentifier(methodName), 241 arkts.factory.createScriptFunction( 242 arkts.factory.createBlock([ 243 isReturnStatement 244 ? arkts.factory.createReturnStatement( 245 arkts.factory.createCallExpression(thisSubscribedWatchesMember(methodName), undefined, [ 246 arkts.factory.createIdentifier(paramName), 247 ]) 248 ) 249 : arkts.factory.createExpressionStatement( 250 arkts.factory.createCallExpression(thisSubscribedWatchesMember(methodName), undefined, [ 251 arkts.factory.createIdentifier(paramName), 252 ]) 253 ), 254 ]), 255 arkts.factory.createFunctionSignature( 256 undefined, 257 [ 258 arkts.factory.createParameterDeclaration( 259 arkts.factory.createIdentifier( 260 paramName, 261 arkts.factory.createTypeReference( 262 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier(paramType)) 263 ) 264 ), 265 undefined 266 ), 267 ], 268 arkts.factory.createPrimitiveType(returnType), 269 false 270 ), 271 arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, 272 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC 273 ), 274 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, 275 false 276 ); 277} 278 279function createWatchMembers(): arkts.AstNode[] { 280 const subscribedWatches: arkts.ClassProperty = arkts.factory.createClassProperty( 281 arkts.factory.createIdentifier('subscribedWatches'), 282 arkts.factory.createETSNewClassInstanceExpression( 283 arkts.factory.createTypeReference( 284 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('SubscribedWatches')) 285 ), 286 [] 287 ), 288 arkts.factory.createTypeReference( 289 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier('SubscribedWatches')) 290 ), 291 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, 292 false 293 ); 294 295 const addWatchSubscriber = createWatchMethod( 296 'addWatchSubscriber', 297 arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID, 298 'watchId', 299 'WatchIdType', 300 false 301 ); 302 303 const removeWatchSubscriber = createWatchMethod( 304 'removeWatchSubscriber', 305 arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BOOLEAN, 306 'watchId', 307 'WatchIdType', 308 true 309 ); 310 311 const executeOnSubscribingWatches = createWatchMethod( 312 'executeOnSubscribingWatches', 313 arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID, 314 'propertyName', 315 'string', 316 false 317 ); 318 319 return [subscribedWatches, addWatchSubscriber, removeWatchSubscriber, executeOnSubscribingWatches]; 320} 321 322function thisSubscribedWatchesMember(member: string): arkts.MemberExpression { 323 return arkts.factory.createMemberExpression( 324 arkts.factory.createMemberExpression( 325 arkts.factory.createThisExpression(), 326 arkts.factory.createIdentifier('subscribedWatches'), 327 arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, 328 false, 329 false 330 ), 331 arkts.factory.createIdentifier(member), 332 arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, 333 false, 334 false 335 ); 336} 337 338/** 339 * @deprecated 340 */ 341function tranformClassMembers( 342 node: arkts.ClassDeclaration, 343 isDecl?: boolean, 344 scope?: CustomComponentScopeInfo 345): arkts.ClassDeclaration { 346 if (!node.definition) { 347 return node; 348 } 349 350 let classTypeName: string | undefined; 351 let classOptionsName: string | undefined; 352 if (isDecl) { 353 const [classType, classOptions] = getTypeParamsFromClassDecl(node); 354 classTypeName = getTypeNameFromTypeParameter(classType); 355 classOptionsName = getTypeNameFromTypeParameter(classOptions); 356 } 357 const definition: arkts.ClassDefinition = node.definition; 358 const className: string | undefined = node.definition.ident?.name; 359 if (!className) { 360 throw new Error('Non Empty className expected for Component'); 361 } 362 363 const propertyTranslators: PropertyTranslator[] = filterDefined( 364 definition.body.map((it) => classifyProperty(it, className)) 365 ); 366 const translatedMembers: arkts.AstNode[] = tranformPropertyMembers( 367 className, 368 propertyTranslators, 369 classOptionsName ?? getCustomComponentOptionsName(className), 370 isDecl, 371 scope 372 ); 373 const updateMembers: arkts.AstNode[] = definition.body 374 .filter((member) => !arkts.isClassProperty(member)) 375 .map((member: arkts.AstNode) => 376 transformOtherMembersInClass(member, classTypeName, classOptionsName, className, isDecl) 377 ); 378 379 const updateClassDef: arkts.ClassDefinition = structFactory.updateCustomComponentClass(definition, [ 380 ...translatedMembers, 381 ...updateMembers, 382 ]); 383 return arkts.factory.updateClassDeclaration(node, updateClassDef); 384} 385 386/** 387 * @deprecated 388 */ 389function transformOtherMembersInClass( 390 member: arkts.AstNode, 391 classTypeName: string | undefined, 392 classOptionsName: string | undefined, 393 className: string, 394 isDecl?: boolean 395): arkts.AstNode { 396 if (arkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.BUILDER)) { 397 member.scriptFunction.setAnnotations([annotation('memo')]); 398 return member; 399 } 400 if ( 401 arkts.isMethodDefinition(member) && 402 isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI) && 403 !isDecl 404 ) { 405 return uiFactory.createConstructorMethod(member); 406 } 407 if (arkts.isMethodDefinition(member) && isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_BUILD_ORI)) { 408 return structFactory.transformBuildMethodWithOriginBuild( 409 member, 410 classTypeName ?? className, 411 classOptionsName ?? getCustomComponentOptionsName(className), 412 isDecl 413 ); 414 } 415 return member; 416} 417 418/** 419 * @deprecated 420 */ 421function tranformPropertyMembers( 422 className: string, 423 propertyTranslators: PropertyTranslator[], 424 optionsTypeName: string, 425 isDecl?: boolean, 426 scope?: CustomComponentScopeInfo 427): arkts.AstNode[] { 428 const propertyMembers = propertyTranslators.map((translator) => translator.translateMember()); 429 const currentStructInfo: arkts.StructInfo = arkts.GlobalInfo.getInfoInstance().getStructInfo(className); 430 const collections = []; 431 if (!scope?.hasInitializeStruct) { 432 collections.push(structFactory.createInitializeStruct(currentStructInfo, optionsTypeName, isDecl)); 433 } 434 if (!scope?.hasUpdateStruct) { 435 collections.push(structFactory.createUpdateStruct(currentStructInfo, optionsTypeName, isDecl)); 436 } 437 if (currentStructInfo.isReusable) { 438 collections.push(structFactory.toRecord(optionsTypeName, currentStructInfo.toRecordBody)); 439 } 440 return collect(...collections, ...propertyMembers); 441} 442 443/** 444 * @deprecated 445 */ 446function transformEtsGlobalClassMembers(node: arkts.ClassDeclaration): arkts.ClassDeclaration { 447 if (!node.definition) { 448 return node; 449 } 450 node.definition.body.map((member: arkts.AstNode) => { 451 if (arkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.BUILDER)) { 452 member.scriptFunction.setAnnotations([annotation('memo')]); 453 } 454 return member; 455 }); 456 return node; 457} 458 459/** 460 * @deprecated 461 */ 462function transformResource( 463 resourceNode: arkts.CallExpression, 464 projectConfig: ProjectConfig | undefined 465): arkts.CallExpression { 466 const newArgs: arkts.AstNode[] = [ 467 arkts.factory.create1StringLiteral(projectConfig?.bundleName ? projectConfig.bundleName : ''), 468 arkts.factory.create1StringLiteral(projectConfig?.moduleName ? projectConfig.moduleName : ''), 469 ...resourceNode.arguments, 470 ]; 471 return structFactory.generateTransformedResource(resourceNode, newArgs); 472} 473