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 { getInteropPath } from '../path'; 18const interop = require(getInteropPath()); 19const nullptr = interop.nullptr; 20import { AbstractVisitor, VisitorOptions } from '../common/abstract-visitor'; 21import { 22 CustomComponentNames, 23 getCustomComponentOptionsName, 24 createOptionalClassProperty, 25 findLocalImport, 26} from './utils'; 27import { isAnnotation, updateStructMetadata, backingField, expectName, annotation } from '../common/arkts-utils'; 28import { EntryWrapperNames, findEntryWithStorageInClassAnnotations } from './entry-translators/utils'; 29import { factory as entryFactory } from './entry-translators/factory'; 30import { 31 hasDecorator, 32 DecoratorNames, 33 getStateManagementType, 34 collectPropertyDecorators, 35} from './property-translators/utils'; 36import { factory } from './ui-factory'; 37import { StructMap } from '../common/program-visitor'; 38import { generateTempCallFunction } from './interop'; 39import { stringify } from 'querystring'; 40 41export interface ComponentTransformerOptions extends VisitorOptions { 42 arkui?: string; 43} 44 45type ScopeInfo = { 46 name: string; 47 isEntry?: boolean; 48 isComponent?: boolean; 49 isReusable?: boolean; 50}; 51 52interface ComponentContext { 53 structMembers: Map<string, arkts.AstNode[]>; 54} 55 56export interface InteropContext { 57 className: string; 58 path: string; 59 line?: number; 60 col?: number; 61 arguments?: arkts.ObjectExpression; 62} 63 64export class ComponentTransformer extends AbstractVisitor { 65 private scopeInfos: ScopeInfo[] = []; 66 private componentInterfaceCollection: arkts.TSInterfaceDeclaration[] = []; 67 private entryNames: string[] = []; 68 private reusableNames: string[] = []; 69 private readonly arkui?: string; 70 private context: ComponentContext = { structMembers: new Map() }; 71 private isCustomComponentImported: boolean = false; 72 private isEntryPointImported: boolean = false; 73 private hasLegacy = false; 74 private legacyStructMap: Map<string, StructMap> = new Map(); 75 private legacyCallMap: Map<string, string> = new Map(); 76 77 constructor(options?: ComponentTransformerOptions) { 78 const _options: ComponentTransformerOptions = options ?? {}; 79 super(_options); 80 this.arkui = _options.arkui; 81 } 82 83 reset(): void { 84 super.reset(); 85 this.scopeInfos = []; 86 this.componentInterfaceCollection = []; 87 this.entryNames = []; 88 this.reusableNames = []; 89 this.context = { structMembers: new Map() }; 90 this.isCustomComponentImported = false; 91 this.isEntryPointImported = false; 92 this.hasLegacy = false; 93 this.legacyStructMap = new Map(); 94 this.legacyCallMap = new Map(); 95 } 96 97 enter(node: arkts.AstNode) { 98 if (arkts.isStructDeclaration(node) && !!node.definition.ident) { 99 const scopeInfo: ScopeInfo = { name: node.definition.ident.name }; 100 node.definition.annotations.forEach((anno) => { 101 scopeInfo.isEntry ||= isAnnotation(anno, CustomComponentNames.ENTRY_ANNOTATION_NAME); 102 scopeInfo.isComponent ||= isAnnotation(anno, CustomComponentNames.COMPONENT_ANNOTATION_NAME); 103 scopeInfo.isReusable ||= isAnnotation(anno, CustomComponentNames.RESUABLE_ANNOTATION_NAME); 104 }); 105 this.scopeInfos.push(scopeInfo); 106 } 107 if (arkts.isETSImportDeclaration(node) && !this.isCustomComponentImported) { 108 this.isCustomComponentImported = !!findLocalImport( 109 node, 110 CustomComponentNames.COMPONENT_DEFAULT_IMPORT, 111 CustomComponentNames.COMPONENT_CLASS_NAME 112 ); 113 } 114 if (arkts.isETSImportDeclaration(node) && !this.isEntryPointImported) { 115 this.isEntryPointImported = !!findLocalImport( 116 node, 117 EntryWrapperNames.ENTRY_DEFAULT_IMPORT, 118 EntryWrapperNames.ENTRY_POINT_CLASS_NAME 119 ); 120 } 121 } 122 123 exit(node: arkts.AstNode) { 124 if (arkts.isStructDeclaration(node) || arkts.isClassDeclaration(node)) { 125 if (!node.definition || !node.definition.ident || this.scopeInfos.length === 0) return; 126 if (this.scopeInfos[this.scopeInfos.length - 1]?.name === node.definition.ident.name) { 127 this.scopeInfos.pop(); 128 } 129 } 130 } 131 132 isComponentStruct(): boolean { 133 if (this.scopeInfos.length === 0) return false; 134 const scopeInfo: ScopeInfo = this.scopeInfos[this.scopeInfos.length - 1]; 135 return !!scopeInfo.isComponent; 136 } 137 138 createImportDeclaration(): void { 139 const source: arkts.StringLiteral = arkts.factory.create1StringLiteral( 140 this.arkui ?? CustomComponentNames.COMPONENT_DEFAULT_IMPORT 141 ); 142 const imported: arkts.Identifier = arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_CLASS_NAME); 143 // Insert this import at the top of the script's statements. 144 if (!this.program) { 145 throw Error('Failed to insert import: Transformer has no program'); 146 } 147 factory.createAndInsertImportDeclaration( 148 source, 149 imported, 150 imported, 151 arkts.Es2pandaImportKinds.IMPORT_KINDS_VALUE, 152 this.program 153 ); 154 } 155 156 processEtsScript(node: arkts.EtsScript): arkts.EtsScript { 157 if (this.isExternal && this.componentInterfaceCollection.length === 0 && this.entryNames.length === 0) { 158 return node; 159 } 160 let updateStatements: arkts.AstNode[] = []; 161 if (this.componentInterfaceCollection.length > 0) { 162 if (!this.isCustomComponentImported) this.createImportDeclaration(); 163 updateStatements.push(...this.componentInterfaceCollection); 164 } 165 166 if (this.entryNames.length > 0) { 167 if (!this.isEntryPointImported) entryFactory.createAndInsertEntryPointImport(this.program); 168 // TODO: normally, we should only have at most one @Entry component in a single file. 169 // probably need to handle error message here. 170 updateStatements.push(...this.entryNames.map(entryFactory.generateEntryWrapper)); 171 } 172 if (updateStatements.length > 0) { 173 return arkts.factory.updateEtsScript(node, [...node.statements, ...updateStatements]); 174 } 175 return node; 176 } 177 178 createStaticMethod(definition: arkts.ClassDefinition): arkts.MethodDefinition { 179 const param: arkts.ETSParameterExpression = arkts.factory.createParameterDeclaration( 180 arkts.factory.createIdentifier( 181 CustomComponentNames.OPTIONS, 182 arkts.factory.createTypeReference( 183 arkts.factory.createTypeReferencePart( 184 arkts.factory.createIdentifier(getCustomComponentOptionsName(definition.ident!.name)) 185 ) 186 ) 187 ), 188 undefined 189 ); 190 191 const script = arkts.factory.createScriptFunction( 192 arkts.factory.createBlock([arkts.factory.createReturnStatement()]), 193 arkts.FunctionSignature.createFunctionSignature( 194 undefined, 195 [param], 196 arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), 197 false 198 ), 199 arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, 200 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC | arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC 201 ); 202 203 return arkts.factory.createMethodDefinition( 204 arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, 205 arkts.factory.createIdentifier(CustomComponentNames.BUILDCOMPATIBLENODE), 206 script, 207 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC | arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, 208 false 209 ); 210 } 211 212 processComponent( 213 node: arkts.ClassDeclaration | arkts.StructDeclaration 214 ): arkts.ClassDeclaration | arkts.StructDeclaration { 215 const scopeInfo = this.scopeInfos[this.scopeInfos.length - 1]; 216 const className = node.definition?.ident?.name; 217 if (!className || scopeInfo?.name !== className) { 218 return node; 219 } 220 221 arkts.GlobalInfo.getInfoInstance().add(className); 222 223 if (arkts.isStructDeclaration(node)) { 224 this.collectComponentMembers(node, className); 225 } 226 227 if (scopeInfo.isReusable) { 228 const currentStructInfo: arkts.StructInfo = arkts.GlobalInfo.getInfoInstance().getStructInfo(className); 229 currentStructInfo.isReusable = true; 230 arkts.GlobalInfo.getInfoInstance().setStructInfo(className, currentStructInfo); 231 } 232 233 this.componentInterfaceCollection.push(this.generateComponentInterface(className, node.modifiers)); 234 235 const definition: arkts.ClassDefinition = node.definition!; 236 const newDefinitionBody: arkts.AstNode[] = []; 237 if (scopeInfo.isEntry) { 238 this.entryNames.push(className); 239 const entryWithStorage: arkts.ClassProperty | undefined = 240 findEntryWithStorageInClassAnnotations(definition); 241 if (!!entryWithStorage) { 242 newDefinitionBody.push(entryFactory.createEntryLocalStorageInClass(entryWithStorage)); 243 } 244 } 245 const newDefinition: arkts.ClassDefinition = this.createNewDefinition( 246 node, 247 className, 248 definition, 249 newDefinitionBody 250 ); 251 252 if (arkts.isStructDeclaration(node)) { 253 const _node = arkts.factory.createClassDeclaration(newDefinition); 254 _node.modifiers = node.modifiers; 255 return _node; 256 } else { 257 return arkts.factory.updateClassDeclaration(node, newDefinition); 258 } 259 } 260 261 createNewDefinition( 262 node: arkts.ClassDeclaration | arkts.StructDeclaration, 263 className: string, 264 definition: arkts.ClassDefinition, 265 newDefinitionBody: arkts.AstNode[] 266 ): arkts.ClassDefinition { 267 const staticMethodBody: arkts.AstNode[] = []; 268 const hasExportFlag = 269 (node.modifiers & arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT) === 270 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT; 271 if (hasExportFlag) { 272 const buildCompatibleNode: arkts.MethodDefinition = this.createStaticMethod(definition); 273 if (!!buildCompatibleNode) { 274 staticMethodBody.push(buildCompatibleNode); 275 } 276 } 277 return arkts.factory.updateClassDefinition( 278 definition, 279 definition.ident, 280 undefined, 281 undefined, // superTypeParams doen't work 282 definition.implements, 283 undefined, 284 arkts.factory.createTypeReference( 285 arkts.factory.createTypeReferencePart( 286 arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_CLASS_NAME), 287 arkts.factory.createTSTypeParameterInstantiation([ 288 arkts.factory.createTypeReference( 289 arkts.factory.createTypeReferencePart(arkts.factory.createIdentifier(className)) 290 ), 291 arkts.factory.createTypeReference( 292 arkts.factory.createTypeReferencePart( 293 arkts.factory.createIdentifier( 294 `${CustomComponentNames.COMPONENT_INTERFACE_PREFIX}${className}` 295 ) 296 ) 297 ), 298 ]) 299 ) 300 ), 301 [ 302 ...newDefinitionBody, 303 ...definition.body.map((st: arkts.AstNode) => factory.PreprocessClassPropertyModifier(st)), 304 ...staticMethodBody, 305 ], 306 definition.modifiers, 307 arkts.classDefinitionFlags(definition) | arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_FINAL 308 ); 309 } 310 311 generateComponentInterface(name: string, modifiers: number): arkts.TSInterfaceDeclaration { 312 const interfaceNode = arkts.factory.createInterfaceDeclaration( 313 [], 314 arkts.factory.createIdentifier(getCustomComponentOptionsName(name)), 315 nullptr, // TODO: wtf 316 arkts.factory.createInterfaceBody([...(this.context.structMembers.get(name) || [])]), 317 false, 318 false 319 ); 320 interfaceNode.modifiers = modifiers; 321 return interfaceNode; 322 } 323 324 collectComponentMembers(node: arkts.StructDeclaration, className: string): void { 325 const structInfo: arkts.StructInfo = arkts.GlobalInfo.getInfoInstance().getStructInfo(className); 326 if (!this.context.structMembers.has(className)) { 327 this.context.structMembers.set(className, []); 328 } 329 node.definition.body.map((it) => { 330 if (arkts.isClassProperty(it)) { 331 if (hasDecorator(it, DecoratorNames.PROVIDE)) { 332 factory.processNoAliasProvideVariable(it); 333 } 334 this.context.structMembers.get(className)!.push(...this.createInterfaceInnerMember(it, structInfo)); 335 } 336 }); 337 arkts.GlobalInfo.getInfoInstance().setStructInfo(className, structInfo); 338 } 339 340 createInterfaceInnerMember(member: arkts.ClassProperty, structInfo: arkts.StructInfo): arkts.ClassProperty[] { 341 const originalName: string = expectName(member.key); 342 const newName: string = backingField(originalName); 343 344 const properties = collectPropertyDecorators(member); 345 const hasStateManagementType = properties.length > 0; 346 updateStructMetadata(structInfo, originalName, properties, member.modifiers, hasStateManagementType); 347 348 const originMember: arkts.ClassProperty = createOptionalClassProperty( 349 originalName, 350 member, 351 '', 352 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC 353 ); 354 if (member.annotations.length > 0 && !hasDecorator(member, DecoratorNames.BUILDER_PARAM)) { 355 const newMember: arkts.ClassProperty = createOptionalClassProperty( 356 newName, 357 member, 358 getStateManagementType(member), 359 arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC 360 ); 361 return [originMember, newMember]; 362 } 363 if (hasDecorator(member, DecoratorNames.BUILDER_PARAM) && !!originMember.typeAnnotation) { 364 originMember.typeAnnotation.setAnnotations([annotation('memo')]); 365 } 366 return [originMember]; 367 } 368 369 registerMap(map: Map<string, StructMap>): void { 370 this.legacyStructMap = map; 371 this.hasLegacy = true; 372 } 373 374 processImport(node: arkts.ETSImportDeclaration): void { 375 const source = node.source?.str!; 376 const specifiers = node.specifiers; 377 if (this.legacyStructMap.has(source)) { 378 const structMap = this.legacyStructMap.get(source); 379 if (!structMap) { 380 return; 381 } 382 for (const specifier of specifiers) { 383 const name = specifier.local.name; 384 if (structMap[name]) { 385 this.legacyCallMap.set(name, structMap[name]); 386 } 387 } 388 } 389 } 390 391 visitor(node: arkts.AstNode): arkts.AstNode { 392 this.enter(node); 393 const newNode = this.visitEachChild(node); 394 if (arkts.isEtsScript(newNode)) { 395 return this.processEtsScript(newNode); 396 } 397 if (arkts.isStructDeclaration(newNode) && this.isComponentStruct()) { 398 const updateNode = this.processComponent(newNode); 399 this.exit(newNode); 400 return updateNode; 401 } 402 if (!this.hasLegacy) { 403 return newNode; 404 } 405 if (arkts.isETSImportDeclaration(newNode)) { 406 this.processImport(newNode); 407 } 408 if (arkts.isCallExpression(newNode)) { 409 const ident = newNode.expression; 410 if (!(ident instanceof arkts.Identifier)) { 411 return newNode; 412 } 413 const className = ident.name; 414 if (this.legacyCallMap.has(className)) { 415 const path = this.legacyCallMap.get(className)!; 416 // const pathName = 'path/har1'; 417 const args = newNode.arguments; 418 const context: InteropContext = { 419 className: className, 420 path: path, 421 arguments: args && args.length === 1 && args[0] instanceof arkts.ObjectExpression 422 ? args[0] 423 : undefined 424 }; 425 return generateTempCallFunction(context); 426 } 427 } 428 return newNode; 429 } 430} 431