diff --git a/arkui-plugins/collectors/utils/collect-types.ts b/arkui-plugins/collectors/utils/collect-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..73a3319eea820ab1c313aa28cb89b56c036a1a68 --- /dev/null +++ b/arkui-plugins/collectors/utils/collect-types.ts @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; + +export enum TypeRecordTypes { + PRIMITIVE, + FUNCTION, + UNION, + UNDEFINED, + NULL, + TYPE_REFERENCE, + TYPE_PARAMETER, + THIS, + ARRAY, +} + +export interface ThisTypeRecord { + type: TypeRecordTypes.THIS; +} + +export interface ArrayTypeRecord { + type: TypeRecordTypes.ARRAY; + elementType?: TypeRecord; +} + +export interface UndefinedTypeRecord { + type: TypeRecordTypes.UNDEFINED; +} + +export interface NullTypeRecord { + type: TypeRecordTypes.NULL; +} + +export interface TypeReferenceTypeRecord { + type: TypeRecordTypes.TYPE_REFERENCE; + typeName: string | string[]; + annotations: readonly arkts.AnnotationUsage[]; + typeParams?: TypeRecord[]; +} + +export interface PrimitiveTypeRecord { + type: TypeRecordTypes.PRIMITIVE; + typeName: string; +} + +export interface FunctionTypeRecord { + type: TypeRecordTypes.FUNCTION; + params: ParameterRecord[]; + returnType: TypeRecord; + annotations: readonly arkts.AnnotationUsage[]; + typeParams?: TypeParameterTypeRecord[]; +} + +export interface UnionTypeRecord { + type: TypeRecordTypes.UNION; + types: TypeRecord[]; +} + +export interface TypeParameterTypeRecord { + type: TypeRecordTypes.TYPE_PARAMETER; + annotations: readonly arkts.AnnotationUsage[]; + typeName?: string; + constraint?: TypeRecord; + defaultType?: TypeRecord; +} + +export type TypeRecord = + | ArrayTypeRecord + | ThisTypeRecord + | UndefinedTypeRecord + | NullTypeRecord + | TypeReferenceTypeRecord + | PrimitiveTypeRecord + | FunctionTypeRecord + | UnionTypeRecord; + +export interface ParameterRecord { + name: string; + typeRecord: TypeRecord; + annotations: readonly arkts.AnnotationUsage[]; + isOptional: boolean; +} + +export function collectTypeRecordFromTypeParameterInstatiation( + typeParams: arkts.TSTypeParameterInstantiation | undefined +): TypeRecord[] | undefined { + if (!typeParams) { + return undefined; + } + return typeParams.params.map((p) => collectTypeRecordFromType(p)!); +} + +export function collectTypeRecordFromTypeParameterDeclaration( + typeParams: arkts.TSTypeParameterDeclaration | undefined +): TypeParameterTypeRecord[] | undefined { + if (!typeParams) { + return undefined; + } + return typeParams.params.map((p) => collectTypeRecordFromTypeParameter(p)); +} + +export function collectTypeRecordFromTypeParameter(typeParameter: arkts.TSTypeParameter): TypeParameterTypeRecord { + const type = TypeRecordTypes.TYPE_PARAMETER; + const typeName = typeParameter.name?.name; + const annotations = typeParameter.annotations; + const constraint = collectTypeRecordFromType(typeParameter.constraint); + const defaultType = collectTypeRecordFromType(typeParameter.defaultType); + return { type, typeName, annotations, constraint, defaultType }; +} + +export function collectTypeRecordFromUnionType(unionType: arkts.TSUnionType): UnionTypeRecord { + const type = TypeRecordTypes.UNION; + const types = unionType.types.map((t) => collectTypeRecordFromType(t)!); + return { type, types }; +} + +export function collectTypeRecordFromFunctionType(funcType: arkts.ETSFunctionType): FunctionTypeRecord { + const type = TypeRecordTypes.FUNCTION; + const params = funcType.params.map((p) => collectTypeRecordFromParameter(p as arkts.ETSParameterExpression)); + const returnType = collectTypeRecordFromType(funcType.returnType)!; + const annotations = funcType.annotations; + const typeParams = collectTypeRecordFromTypeParameterDeclaration(funcType.typeParams); + return { type, params, returnType, annotations, typeParams }; +} + +export function collectTypeRecordFromParameter(param: arkts.ETSParameterExpression): ParameterRecord { + const name = param.identifier.name; + const typeRecord = collectTypeRecordFromType(param.type)!; + const annotations = param.annotations; + const isOptional = param.optional; + return { name, typeRecord, annotations, isOptional }; +} + +function coerceTypeNameToArray(name: string | string[] | undefined): string[] { + if (Array.isArray(name)) { + return name; + } + if (!!name) { + return [name]; + } + return []; +} + +function getTypeNameFromTypeReferencePartName(name: arkts.Expression | undefined): string | string[] | undefined { + if (!name) { + return undefined; + } + if (arkts.isIdentifier(name)) { + return name.name; + } + if (arkts.isTSQualifiedName(name)) { + const leftName: string | string[] | undefined = getTypeNameFromTypeReferencePartName(name.left); + const rightName: string | string[] | undefined = getTypeNameFromTypeReferencePartName(name.right); + const nameArr: string[] = [...coerceTypeNameToArray(leftName), ...coerceTypeNameToArray(rightName)]; + if (nameArr.length === 0) { + return undefined; + } + return nameArr; + } + return undefined; +} + +export function collectTypeRecordFromTypeReference(node: arkts.ETSTypeReference): TypeReferenceTypeRecord | undefined { + if (!node.part || !arkts.isETSTypeReferencePart(node.part)) { + return undefined; + } + if (!node.part.name) { + return undefined; + } + const typeName = getTypeNameFromTypeReferencePartName(node.part.name); + if (!typeName) { + return undefined; + } + const type = TypeRecordTypes.TYPE_REFERENCE; + const annotations = node.annotations; + const typeParams = collectTypeRecordFromTypeParameterInstatiation(node.part.typeParams); + return { type, typeName, annotations, typeParams }; +} + +export function collectTypeRecordFromUndefinedType(node: arkts.ETSUndefinedType): UndefinedTypeRecord { + const type = TypeRecordTypes.UNDEFINED; + return { type }; +} + +export function collectTypeRecordFromNullType(node: arkts.ETSNullType): NullTypeRecord { + const type = TypeRecordTypes.NULL; + return { type }; +} + +export function collectTypeRecordFromThisType(node: arkts.TSThisType): ThisTypeRecord { + const type = TypeRecordTypes.THIS; + return { type }; +} + +export function collectTypeRecordFromArrayType(node: arkts.TSArrayType): ArrayTypeRecord { + const type = TypeRecordTypes.ARRAY; + const elementType = collectTypeRecordFromType(node.elementType); + return { type, elementType }; +} + +export function collectTypeRecordFromPrimitiveType(node: arkts.ETSPrimitiveType): PrimitiveTypeRecord | undefined { + const type = TypeRecordTypes.PRIMITIVE; + const typeName: string = node.dumpSrc(); + return { type, typeName }; +} + +export function collectTypeRecordFromType(node: arkts.AstNode | undefined): TypeRecord | undefined { + if (!node) { + return undefined; + } + const type = arkts.nodeType(node); + if (collectTypeRecordByType.has(type)) { + return collectTypeRecordByType.get(type)!(node); + } + return undefined; +} + +const collectTypeRecordByType = new Map TypeRecord | undefined>([ + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_TYPE_REFERENCE, collectTypeRecordFromTypeReference], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_FUNCTION_TYPE, collectTypeRecordFromFunctionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_UNDEFINED_TYPE, collectTypeRecordFromUndefinedType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PRIMITIVE_TYPE, collectTypeRecordFromPrimitiveType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_NULL_TYPE, collectTypeRecordFromNullType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_UNION_TYPE, collectTypeRecordFromUnionType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_THIS_TYPE, collectTypeRecordFromThisType], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_ARRAY_TYPE, collectTypeRecordFromArrayType], +]); diff --git a/arkui-plugins/common/safe-types.ts b/arkui-plugins/common/safe-types.ts index 899ef0c39b3dccec8dbbbe1fb68d6243a009dcc2..09ccd909234f3b704687e8653ab5b929eb5bd806 100644 --- a/arkui-plugins/common/safe-types.ts +++ b/arkui-plugins/common/safe-types.ts @@ -34,10 +34,12 @@ export type PartialNested = { type NestedKey = { [P in keyof T]: P extends K ? T[P] : T[P] extends object ? NestedKey : T[P]; - }; - +}; + export type PickNested = { [P in keyof T]: P extends K ? T[P] : T[P] extends object ? NestedKey : T[P]; }; -export type PartialNestedExcept = PartialNested> & PickNested; \ No newline at end of file +export type PartialNestedExcept = PartialNested> & PickNested; + +export type AstNodePointer = arkts.AstNode['peer']; diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/state/state-basic-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/state/state-basic-type.test.ts index 927dc81ce84d56042d0a9868a26106124bc0f058..9db3a8db6c7dc19425d46531e0e55722481ae31c 100644 --- a/arkui-plugins/test/ut/ui-plugins/decorators/state/state-basic-type.test.ts +++ b/arkui-plugins/test/ut/ui-plugins/decorators/state/state-basic-type.test.ts @@ -167,10 +167,6 @@ function main() {} `; function testParsedAndCheckedTransformer(this: PluginTestContext): void { - console.log("[testParsedAndCheckedTransformer] this: ", this); - console.log("[testParsedAndCheckedTransformer] parseDumpSrc(this.scriptSnapshot ?? ''): ", parseDumpSrc(this.scriptSnapshot ?? '')); - console.log("[testParsedAndCheckedTransformer] parseDumpSrc(expectedScript): ", parseDumpSrc(expectedScript)); - expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); } diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts index 55997c0c32272ec3a0b5f7f2af952c2cc6f610f3..d5fe8e76e11495801b60c23b3ece8bf92593babb 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts @@ -43,12 +43,25 @@ import { builderLambdaType, BuilderLambdaSecondLastArgInfo, buildSecondLastArgInfo, + ComponentAttributeCache, + ComponentRecord, + getDeclaredSetAttribtueMethodName, + BuilderLambdaStyleBodyInfo, + checkIsTrailingLambdaInLastParam, + filterParamsExpectTrailingLambda, } from './utils'; import { isDecoratorIntrinsicAnnotation } from '../property-translators/utils'; import { factory as PropertyFactory } from '../property-translators/factory'; +import { factory as TypeFactory } from '../type-translators/factory'; +import { factory as UIFactory } from '../ui-factory'; import { AnimationNames, BindableDecl, DecoratorIntrinsicNames, DecoratorNames } from '../../common/predefines'; import { ImportCollector } from '../../common/import-collector'; -import { addMemoAnnotation, collectMemoableInfoInParameter } from '../../collectors/memo-collectors/utils'; +import { + addMemoAnnotation, + checkIsMemoFromMemoableInfo, + collectMemoableInfoInParameter, +} from '../../collectors/memo-collectors/utils'; +import { StyleInternalsVisitor } from './style-internals-visitor'; export class factory { /** @@ -58,14 +71,12 @@ export class factory { node: arkts.MethodDefinition, prefixArgs: arkts.ETSParameterExpression[], newAnno: arkts.AnnotationUsage[], - newName: string | undefined, - externalSourceName?: string + newName: string | undefined ): arkts.MethodDefinition { const func: arkts.ScriptFunction = node.scriptFunction; - let newParams: arkts.Expression[] = []; - if (func.params.length > 0) { - newParams.push(...prefixArgs, ...func.params); - } + const isFunctionCall: boolean = node.name.name !== BuilderLambdaNames.ORIGIN_METHOD_NAME; + const restParams = isFunctionCall ? filterParamsExpectTrailingLambda(func.params) : func.params; + const newParams: arkts.Expression[] = [...prefixArgs, ...restParams]; const updateFunc = arkts.factory .updateScriptFunction( func, @@ -85,7 +96,7 @@ export class factory { node, node.kind, arkts.factory.updateIdentifier(node.name, newName ?? node.name.name), - node.name.name === BuilderLambdaNames.ORIGIN_METHOD_NAME ? addMemoAnnotation(updateFunc) : updateFunc, + isFunctionCall ? updateFunc : addMemoAnnotation(updateFunc), node.modifiers, false ); @@ -157,6 +168,39 @@ export class factory { } } + /** + * add `instance.()` call as the initial style argument body for components. + * The initial style argument body is `undefined` for custom components. + */ + static createInitLambdaBody(declInfo: BuilderLambdaDeclInfo): BuilderLambdaStyleBodyInfo { + const { name, isFunctionCall, hasReceiver } = declInfo; + const lambdaBodyInfo: BuilderLambdaStyleBodyInfo = { lambdaBody: undefined, initCallPtr: undefined }; + if (!isFunctionCall) { + return lambdaBodyInfo; + } + let lambdaBody: arkts.Identifier | arkts.CallExpression = arkts.factory.createIdentifier( + BuilderLambdaNames.STYLE_ARROW_PARAM_NAME + ); + const methodName = arkts.factory.createIdentifier(getDeclaredSetAttribtueMethodName(name)); + if (!hasReceiver) { + lambdaBodyInfo.lambdaBody = arkts.factory.createCallExpression( + arkts.factory.createMemberExpression( + lambdaBody, + methodName, + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + undefined, + [] + ); + } else { + lambdaBodyInfo.lambdaBody = arkts.factory.createCallExpression(methodName, undefined, [lambdaBody]); + } + lambdaBodyInfo.initCallPtr = lambdaBodyInfo.lambdaBody.peer; + return lambdaBodyInfo; + } + /* * update parameter passing, e.g. `: __backing_`. */ @@ -273,7 +317,7 @@ export class factory { /** * transform options argument in a builder lambda call. */ - static processOptionsArg(arg: T, typeName: string): T { + static processOptionsArg(arg: T): T { let expr: arkts.ObjectExpression | undefined; if (arkts.isTSAsExpression(arg) && !!arg.expr && arkts.isObjectExpression(arg.expr)) { expr = arg.expr; @@ -338,7 +382,6 @@ export class factory { static createOrUpdateArgInBuilderLambda( fallback: arkts.AstNode | undefined, arg: arkts.Expression | undefined, - typeName?: string, canAddMemo?: boolean ): arkts.AstNode | undefined { if (!arg) { @@ -353,7 +396,7 @@ export class factory { } // this is too optimistic to check if this is an options argument... if (arkts.isTSAsExpression(arg) || arkts.isObjectExpression(arg)) { - return this.processOptionsArg(arg, typeName!); + return this.processOptionsArg(arg); } return arg; } @@ -373,16 +416,16 @@ export class factory { */ static generateArgsInBuilderLambda( leaf: arkts.CallExpression, - lambdaBody: arkts.Identifier | arkts.CallExpression, + lambdaBodyInfo: BuilderLambdaStyleBodyInfo, declInfo: BuilderLambdaDeclInfo ): (arkts.AstNode | undefined)[] { const { isFunctionCall, params, returnType, moduleName } = declInfo; const type: arkts.Identifier | undefined = builderLambdaType(leaf); - const args: (arkts.AstNode | undefined)[] = [ - this.createStyleArgInBuilderLambda(lambdaBody, returnType, moduleName), - ]; + const args: (arkts.AstNode | undefined)[] = []; + const modifiedArgs: (arkts.AstNode | undefined)[] = []; const secondLastArgInfo = buildSecondLastArgInfo(type, isFunctionCall); const isTrailingCall = leaf.isTrailingCall; + const hasLastTrailingLambda = checkIsTrailingLambdaInLastParam(params); forEachArgWithParam( leaf.arguments, params, @@ -393,20 +436,67 @@ export class factory { } if (!modifiedArg) { const memoableInfo = collectMemoableInfoInParameter(param); - const canAddMemo = - (!!memoableInfo.hasBuilder || !!memoableInfo.hasMemo) && !!memoableInfo.hasProperType; - modifiedArg = this.createOrUpdateArgInBuilderLambda( - arkts.factory.createUndefinedLiteral(), - arg, - type?.name, - canAddMemo - ); + const canAddMemo = checkIsMemoFromMemoableInfo(memoableInfo, false); + const fallback = arkts.factory.createUndefinedLiteral(); + modifiedArg = this.createOrUpdateArgInBuilderLambda(fallback, arg, canAddMemo); + } + const shouldInsertToArgs = !isFunctionCall || (index === params.length - 1 && hasLastTrailingLambda); + if (shouldInsertToArgs) { + args.push(modifiedArg); + } else { + modifiedArgs.push(modifiedArg); } - args.push(modifiedArg); }, { isTrailingCall } ); - return filterDefined(args); + const lambdaBody = this.addOptionsArgsToLambdaBodyInStyleArg(lambdaBodyInfo, modifiedArgs, leaf.typeArguments); + const typeNode = !isFunctionCall && !!type ? UIFactory.createTypeReferenceFromString(type.name) : returnType; + const styleArg = this.createStyleArgInBuilderLambda(lambdaBody, typeNode, moduleName); + args.unshift(styleArg); + return args; + } + + /** + * add options arguments to set methods in style argument body. + */ + static addOptionsArgsToLambdaBodyInStyleArg( + lambdaBodyInfo: BuilderLambdaStyleBodyInfo, + args: (arkts.AstNode | undefined)[], + typeArguments: readonly arkts.TypeNode[] | undefined + ): arkts.CallExpression | arkts.Identifier | undefined { + const { lambdaBody, initCallPtr } = lambdaBodyInfo; + if (!lambdaBody) { + return undefined; + } + if (!initCallPtr || arkts.isIdentifier(lambdaBody)) { + return this.addApplyAttributesFinishToLambdaBodyEnd(lambdaBody); + } + const styleInternalsVisitor = new StyleInternalsVisitor(); + const newLambdaBody = styleInternalsVisitor + .registerInitCall(initCallPtr) + .registerInitCallArgs(filterDefined(args)) + .registerInitCallTypeArguments(typeArguments) + .visitor(lambdaBody) as arkts.CallExpression | arkts.Identifier; + return this.addApplyAttributesFinishToLambdaBodyEnd(newLambdaBody); + } + + /** + * add `.applyAttributesFinish()` at the end of style argument body. + */ + static addApplyAttributesFinishToLambdaBodyEnd( + lambdaBody: arkts.CallExpression | arkts.Identifier + ): arkts.CallExpression | arkts.Identifier { + return arkts.factory.createCallExpression( + arkts.factory.createMemberExpression( + lambdaBody, + arkts.factory.createIdentifier(BuilderLambdaNames.APPLY_ATTRIBUTES_FINISH_METHOD), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + undefined, + [] + ); } /** @@ -480,23 +570,21 @@ export class factory { /** * transform `@ComponentBuilder` in declared methods. */ - static transformBuilderLambdaMethodDecl( - node: arkts.MethodDefinition, - externalSourceName?: string - ): arkts.MethodDefinition { + static transformBuilderLambdaMethodDecl(node: arkts.MethodDefinition): arkts.MethodDefinition { const func: arkts.ScriptFunction = node.scriptFunction; const isFunctionCall: boolean = isBuilderLambdaFunctionCall(node); + if (isFunctionCall) { + ComponentAttributeCache.getInstance().collect(node); + } const typeNode: arkts.TypeNode | undefined = builderLambdaMethodDeclType(node); const newOverloads: arkts.MethodDefinition[] = node.overloads.map((method) => factory.transformBuilderLambdaMethodDecl(method) ); - const newNode = this.updateBuilderLambdaMethodDecl( node, [this.createStyleArgInBuilderLambdaDecl(typeNode, isFunctionCall)], removeAnnotationByName(func.annotations, BuilderLambdaNames.ANNOTATION_NAME), - replaceBuilderLambdaDeclMethodName(node.name.name), - externalSourceName + replaceBuilderLambdaDeclMethodName(node.name.name) ).setOverloads(newOverloads); arkts.NodeCache.getInstance().collect(newNode); return newNode; @@ -542,7 +630,6 @@ export class factory { static transformBuilderLambda(node: arkts.CallExpression): arkts.AstNode { let instanceCalls: InstanceCallInfo[] = []; let leaf: arkts.CallExpression = node; - while (isStyleChainedCall(leaf) || isStyleWithReceiverCall(leaf)) { if (isStyleChainedCall(leaf)) { instanceCalls.push({ @@ -555,7 +642,6 @@ export class factory { }); leaf = (leaf.expression as arkts.MemberExpression).object as arkts.CallExpression; } - if (isStyleWithReceiverCall(leaf)) { instanceCalls.push({ isReceiver: true, @@ -564,30 +650,26 @@ export class factory { leaf = leaf.arguments[0] as arkts.CallExpression; } } - const decl: arkts.AstNode | undefined = findBuilderLambdaDecl(leaf); if (!decl) { return node; } - const replace: arkts.Identifier | arkts.MemberExpression | undefined = this.builderLambdaReplace(leaf); const declInfo: BuilderLambdaDeclInfo | undefined = findBuilderLambdaDeclInfo(decl); if (!replace || !declInfo) { return node; } - - let lambdaBody: arkts.Identifier | arkts.CallExpression | undefined; + const lambdaBodyInfo = factory.createInitLambdaBody(declInfo); + let lambdaBody: arkts.Identifier | arkts.CallExpression | undefined = lambdaBodyInfo.lambdaBody; if (instanceCalls.length > 0) { instanceCalls = instanceCalls.reverse(); this.updateAnimation(instanceCalls); - lambdaBody = arkts.factory.createIdentifier(BuilderLambdaNames.STYLE_ARROW_PARAM_NAME); - arkts.NodeCache.getInstance().collect(lambdaBody); instanceCalls.forEach((callInfo) => { lambdaBody = this.createStyleLambdaBody(lambdaBody!, callInfo); }); } - - const args: (arkts.AstNode | undefined)[] = this.generateArgsInBuilderLambda(leaf, lambdaBody!, declInfo); + lambdaBodyInfo.lambdaBody = lambdaBody; + const args: (arkts.AstNode | undefined)[] = this.generateArgsInBuilderLambda(leaf, lambdaBodyInfo, declInfo); const newNode = arkts.factory.updateCallExpression(node, replace, leaf.typeArguments, filterDefined(args)); arkts.NodeCache.getInstance().collect(newNode); return newNode; @@ -675,4 +757,89 @@ export class factory { ) ); } + + /** + * add declared set methods in `@ComponentBuilder` Attribute interface + */ + static addDeclaredSetMethodsInAttributeInterface(node: arkts.TSInterfaceDeclaration): arkts.TSInterfaceDeclaration { + if (!node.body) { + return node; + } + const records = ComponentAttributeCache.getInstance().getAllComponentRecords(); + if (records.length === 0) { + return node; + } + let rootMethod = factory.createDeclaredSetMethodFromRecord(records.at(0)!); + const overloads: arkts.MethodDefinition[] = []; + for (let i = 0; i < records.length - 1; i++) { + const newMethod = factory.createDeclaredSetMethodFromRecord(records.at(i + 1)!); + overloads.push(newMethod); + } + rootMethod.setOverloads(overloads); + return arkts.factory.updateInterfaceDeclaration( + node, + node.extends, + node.id, + node.typeParams, + arkts.factory.updateInterfaceBody(node.body, [...node.body.body, rootMethod]), + node.isStatic, + node.isFromExternal + ); + } + + /** + * generate declared set method from `ComponentRecord` + */ + static createDeclaredSetMethodFromRecord(record: ComponentRecord): arkts.MethodDefinition { + const name = getDeclaredSetAttribtueMethodName(record.name); + const hasReceiver = !!record.hasReceiver; + const params = record.attributeRecords.map((record) => TypeFactory.createParameterFromRecord(record)); + const typeParams = record.typeParams?.map((p) => TypeFactory.createTypeParameterFromRecord(p)); + + const key = arkts.factory.createIdentifier(name); + const kind = arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD; + const modifiers = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE; + const funcTypeParams = typeParams + ? arkts.factory.createTypeParameterDeclaration(typeParams, typeParams.length) + : undefined; + const returnTypeAnnotation = arkts.factory.createTSThisType(); + const flags = arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD; + + return UIFactory.createMethodDefinition({ + key, + kind, + function: { + key, + flags, + params, + typeParams: funcTypeParams, + returnTypeAnnotation, + modifiers, + hasReceiver, + }, + modifiers, + }); + } + + /** + * generate `applyAttributesFinish(): void;` in `CommonMethod` interface + */ + static createDeclaredApplyAttributesFinish(): arkts.MethodDefinition { + const key = arkts.factory.createIdentifier(BuilderLambdaNames.APPLY_ATTRIBUTES_FINISH_METHOD); + const kind = arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD; + const modifiers = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE; + const returnTypeAnnotation = arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID); + const flags = arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD; + return UIFactory.createMethodDefinition({ + key, + kind, + function: { + key, + flags, + returnTypeAnnotation, + modifiers + }, + modifiers + }) + } } diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/style-internals-visitor.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/style-internals-visitor.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc46594bfc1fc5671f84bfa1bf6559dfc0c4e659 --- /dev/null +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/style-internals-visitor.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; +import { AbstractVisitor } from '../../common/abstract-visitor'; +import { AstNodePointer } from '../../common/safe-types'; + +export class StyleInternalsVisitor extends AbstractVisitor { + private initCallPtr: AstNodePointer | undefined; + private initCallArgs: arkts.AstNode[] | undefined; + private initCallTypeArguments: readonly arkts.TypeNode[] | undefined; + + registerInitCall(initCallPtr: AstNodePointer): this { + this.initCallPtr = initCallPtr; + return this; + } + + registerInitCallArgs(initCallArgs: arkts.AstNode[]): this { + this.initCallArgs = initCallArgs; + return this; + } + + registerInitCallTypeArguments(initCallTypeArguments: readonly arkts.TypeNode[] | undefined): this { + this.initCallTypeArguments = initCallTypeArguments; + return this; + } + + visitor(node: arkts.CallExpression): arkts.AstNode { + if (!!this.initCallPtr && !!this.initCallArgs && node.peer === this.initCallPtr) { + return arkts.factory.updateCallExpression( + node, + node.expression, + this.initCallTypeArguments ?? node.typeArguments, + this.initCallArgs + ); + } + return this.visitEachChild(node); + } +} diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts index e0118e037174c12773e571e6296cd1264daa674b..151a90580584f254b66624aa939e51ad4838bad6 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts @@ -19,12 +19,27 @@ import { BuilderLambdaNames, isCustomComponentAnnotation } from '../utils'; import { DeclarationCollector } from '../../common/declaration-collector'; import { ARKUI_IMPORT_PREFIX_NAMES, BindableDecl, Dollars, StructDecoratorNames } from '../../common/predefines'; import { ImportCollector } from '../../common/import-collector'; +import { + collectTypeRecordFromParameter, + collectTypeRecordFromTypeParameterDeclaration, + ParameterRecord, + TypeParameterTypeRecord, +} from '../../collectors/utils/collect-types'; +import { hasMemoAnnotation } from '../../collectors/memo-collectors/utils'; +import { AstNodePointer } from 'common/safe-types'; export type BuilderLambdaDeclInfo = { + name: string; isFunctionCall: boolean; // isFunctionCall means it is from $_instantiate. params: readonly arkts.Expression[]; returnType: arkts.TypeNode | undefined; moduleName: string; + hasReceiver?: boolean; +}; + +export type BuilderLambdaStyleBodyInfo = { + lambdaBody: arkts.Identifier | arkts.CallExpression | undefined; + initCallPtr: AstNodePointer | undefined; }; export type BuilderLambdaAstNode = arkts.ScriptFunction | arkts.ETSParameterExpression | arkts.FunctionDeclaration; @@ -43,8 +58,7 @@ export type BuilderLambdaReusableArgInfo = { reuseId?: string; }; -export type BuilderLambdaSecondLastArgInfo = BuilderLambdaArgInfo & - BuilderLambdaReusableArgInfo; +export type BuilderLambdaSecondLastArgInfo = BuilderLambdaArgInfo & BuilderLambdaReusableArgInfo; export function buildSecondLastArgInfo( type: arkts.Identifier | undefined, @@ -334,10 +348,12 @@ export function findBuilderLambdaDeclInfo(decl: arkts.AstNode | undefined): Buil return undefined; } if (arkts.isMethodDefinition(decl)) { + const name = decl.name.name; const params = decl.scriptFunction.params.map((p) => p.clone()); const returnType = decl.scriptFunction.returnTypeAnnotation?.clone(); const isFunctionCall = isBuilderLambdaFunctionCall(decl); - return { isFunctionCall, params, returnType, moduleName }; + const hasReceiver = decl.scriptFunction.hasReceiver; + return { name, isFunctionCall, params, returnType, moduleName, hasReceiver }; } return undefined; @@ -541,3 +557,138 @@ export function collectComponentAttributeImport(type: arkts.TypeNode | undefined ImportCollector.getInstance().collectImport(attributeName); } } + +/** + * check whether the last parameter is trailing lambda in components. + */ +export function checkIsTrailingLambdaInLastParam(params: readonly arkts.Expression[]): boolean { + if (params.length === 0) { + return false; + } + const lastParam = params.at(params.length - 1)! as arkts.ETSParameterExpression; + return hasMemoAnnotation(lastParam) && lastParam.identifier.name === BuilderLambdaNames.COMPONENT_PARAM_ORI; +} + +/** + * remove any parameters except possible last trailing lambda parameter in components. + */ +export function filterParamsExpectTrailingLambda( + params: readonly arkts.Expression[] +): readonly arkts.Expression[] { + if (checkIsTrailingLambdaInLastParam(params)) { + return [params.at(params.length - 1)!]; + } + return []; +} + +/** + * check whether interface is `XXXAttribute` that implies the component's attribute interface. + */ +export function isComponentAttributeInterface(node: arkts.AstNode): node is arkts.TSInterfaceDeclaration { + if (!ComponentAttributeCache.getInstance().isCollected()) { + return false; + } + if (!arkts.isTSInterfaceDeclaration(node) || !node.id) { + return false; + } + return ComponentAttributeCache.getInstance().attributeName === node.id.name; +} + +/** + * get set method name for components. + */ +export function getDeclaredSetAttribtueMethodName(componentName: string): string { + return `set${componentName}Options`; +} + +// CACHE +export interface ComponentRecord { + name: string; + attributeRecords: ParameterRecord[]; + typeParams?: TypeParameterTypeRecord[]; + hasRestParameter?: boolean; + hasReceiver?: boolean; +} + +export class ComponentAttributeCache { + private _cache: Map; + private _attributeName: string | undefined; + private _isCollected: boolean = false; + private static instance: ComponentAttributeCache; + + private constructor() { + this._cache = new Map(); + } + + static getInstance(): ComponentAttributeCache { + if (!this.instance) { + this.instance = new ComponentAttributeCache(); + } + return this.instance; + } + + private collectAttributeName(type: arkts.TypeNode | undefined): string | undefined { + if ( + !type || + !arkts.isETSTypeReference(type) || + !type.part || + !type.part.name || + !arkts.isIdentifier(type.part.name) + ) { + return; + } + this._attributeName = type.part.name.name; + } + + get attributeName(): string | undefined { + return this._attributeName; + } + + reset(): void { + this._cache.clear(); + this._attributeName = undefined; + this._isCollected = false; + } + + isCollected(): boolean { + return this._isCollected; + } + + collect(node: arkts.MethodDefinition): void { + this.collectAttributeName(node.scriptFunction.returnTypeAnnotation); + if (!this._attributeName) { + return; + } + const name: string = node.name.name; + const hasRestParameter = node.scriptFunction.hasRestParameter; + const hasReceiver = node.scriptFunction.hasReceiver; + const typeParams = collectTypeRecordFromTypeParameterDeclaration(node.scriptFunction.typeParams); + const params = node.scriptFunction.params as arkts.ETSParameterExpression[]; + const attributeRecords: ParameterRecord[] = []; + const hasLastTrailingLambda = checkIsTrailingLambdaInLastParam(params); + params.forEach((p, index) => { + if (index === params.length - 1 && hasLastTrailingLambda) { + return; + } + const record = collectTypeRecordFromParameter(p); + attributeRecords.push(record); + }); + const componentRecord: ComponentRecord = { + name, + attributeRecords, + typeParams, + hasRestParameter, + hasReceiver, + }; + this._cache.set(name, componentRecord); + this._isCollected = true; + } + + getComponentRecord(name: string): ComponentRecord | undefined { + return this._cache.get(name); + } + + getAllComponentRecords(): ComponentRecord[] { + return Array.from(this._cache.values()); + } +} diff --git a/arkui-plugins/ui-plugins/checked-transformer.ts b/arkui-plugins/ui-plugins/checked-transformer.ts index 0dbc8b4458dfb19a07dc4ca473510db86443c33a..80b686edb4667e5a9d36f39bdf2b89b113d2b297 100644 --- a/arkui-plugins/ui-plugins/checked-transformer.ts +++ b/arkui-plugins/ui-plugins/checked-transformer.ts @@ -19,7 +19,11 @@ import { factory as structFactory } from './struct-translators/factory'; import { factory as builderLambdaFactory } from './builder-lambda-translators/factory'; import { factory as entryFactory } from './entry-translators/factory'; import { AbstractVisitor } from '../common/abstract-visitor'; -import { isBuilderLambda, isBuilderLambdaMethodDecl } from './builder-lambda-translators/utils'; +import { + ComponentAttributeCache, + isBuilderLambda, + isBuilderLambdaMethodDecl, +} from './builder-lambda-translators/utils'; import { isEntryWrapperClass } from './entry-translators/utils'; import { ImportCollector } from '../common/import-collector'; import { DeclarationCollector } from '../common/declaration-collector'; @@ -33,7 +37,7 @@ import { LoaderJson, ResourceInfo, ScopeInfoCollection, - isForEachDecl + isForEachDecl, } from './struct-translators/utils'; import { collectCustomComponentScopeInfo, CustomComponentNames, isCustomComponentClass } from './utils'; import { findAndCollectMemoableNode } from '../collectors/memo-collectors/factory'; @@ -56,6 +60,7 @@ export class CheckedTransformer extends AbstractVisitor { super.reset(); this.scope = { customComponents: [] }; PropertyCache.getInstance().reset(); + ComponentAttributeCache.getInstance().reset(); ImportCollector.getInstance().reset(); DeclarationCollector.getInstance().reset(); LogCollector.getInstance().reset(); @@ -98,8 +103,12 @@ export class CheckedTransformer extends AbstractVisitor { if (arkts.isEtsScript(node)) { insertImportDeclaration(this.program); } - if (arkts.isClassDeclaration(node) && node.definition && node.definition.ident && - node.definition.ident.name === 'CustomDialogController') { + if ( + arkts.isClassDeclaration(node) && + node.definition && + node.definition.ident && + node.definition.ident.name === 'CustomDialogController' + ) { return transformDeclaration(node as arkts.ClassDeclaration); } return node; @@ -111,10 +120,7 @@ export class CheckedTransformer extends AbstractVisitor { const lambda = builderLambdaFactory.transformBuilderLambda(beforeChildren); return this.visitEachChild(lambda); } else if (arkts.isMethodDefinition(beforeChildren) && isBuilderLambdaMethodDecl(beforeChildren)) { - const lambda = builderLambdaFactory.transformBuilderLambdaMethodDecl( - beforeChildren, - this.externalSourceName - ); + const lambda = builderLambdaFactory.transformBuilderLambdaMethodDecl(beforeChildren); return this.visitEachChild(lambda); } const node = this.visitEachChild(beforeChildren); diff --git a/arkui-plugins/ui-plugins/struct-translators/factory.ts b/arkui-plugins/ui-plugins/struct-translators/factory.ts index c450bf11ee2ede2d78346f0346b0c250e13e5171..9cad2c674990eb4d519eeeb552487c810f35b80f 100644 --- a/arkui-plugins/ui-plugins/struct-translators/factory.ts +++ b/arkui-plugins/ui-plugins/struct-translators/factory.ts @@ -25,6 +25,7 @@ import { } from '../utils'; import { factory as uiFactory } from '../ui-factory'; import { factory as propertyFactory } from '../property-translators/factory'; +import { factory as builderLambdaFactory } from '../builder-lambda-translators/factory'; import { collect, filterDefined } from '../../common/arkts-utils'; import { classifyObservedTrack, @@ -49,6 +50,7 @@ import { isForEachCall, } from './utils'; import { collectStateManagementTypeImport, hasDecorator, PropertyCache } from '../property-translators/utils'; +import { ComponentAttributeCache, isComponentAttributeInterface } from '../builder-lambda-translators/utils'; import { ProjectConfig } from '../../common/plugin-context'; import { ImportCollector } from '../../common/import-collector'; import { @@ -252,13 +254,18 @@ export class factory { } /** - * add headers for animation & @AnimatableExtend in CommonMethod + * add following declared methods in `CommonMethod` interface: + * - `animationStart` and `animationStop` for `Animation` component; + * - `__createOrSetAnimatableProperty` for `@AnimatableExtend` function with receiver; + * - `applyAttributesFinish` for component's style set options method. */ static modifyExternalComponentCommon(node: arkts.TSInterfaceDeclaration): arkts.TSInterfaceDeclaration { const animationStart = factory.createAnimationMethod(AnimationNames.ANIMATION_START); const animationStop = factory.createAnimationMethod(AnimationNames.ANIMATION_STOP); const createOrSetAniProperty = factory.createOrSetAniProperty(); + const applyAttributesFinish = builderLambdaFactory.createDeclaredApplyAttributesFinish(); const updatedBody = arkts.factory.updateInterfaceBody(node.body!, [ + applyAttributesFinish, animationStart, animationStop, createOrSetAniProperty, @@ -604,13 +611,17 @@ export class factory { if (!node.id || !node.body) { return node; } - if (externalSourceName === ARKUI_COMPONENT_COMMON_SOURCE_NAME && node.id.name === 'CommonMethod') { - return factory.modifyExternalComponentCommon(node); + const newNode = factory.tranformInterfaceBuildMember(node); + if (externalSourceName === ARKUI_COMPONENT_COMMON_SOURCE_NAME && newNode.id!.name === 'CommonMethod') { + return factory.modifyExternalComponentCommon(newNode); } - if (isCustomComponentInterface(node)) { - return factory.tranformCustomComponentInterfaceMembers(node); + if (isCustomComponentInterface(newNode)) { + return factory.tranformCustomComponentInterfaceMembers(newNode); } - return factory.tranformInterfaceBuildMember(node); + if (isComponentAttributeInterface(newNode)) { + return builderLambdaFactory.addDeclaredSetMethodsInAttributeInterface(newNode); + } + return newNode; } static tranformInterfaceBuildMember(node: arkts.TSInterfaceDeclaration): arkts.TSInterfaceDeclaration { diff --git a/arkui-plugins/ui-plugins/type-translators/factory.ts b/arkui-plugins/ui-plugins/type-translators/factory.ts new file mode 100644 index 0000000000000000000000000000000000000000..14e2650429732fef51864d6ddd6d0565b35ea1ae --- /dev/null +++ b/arkui-plugins/ui-plugins/type-translators/factory.ts @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; +import { + ArrayTypeRecord, + FunctionTypeRecord, + NullTypeRecord, + PrimitiveTypeRecord, + ThisTypeRecord, + TypeRecord, + TypeRecordTypes, + TypeReferenceTypeRecord, + UndefinedTypeRecord, + UnionTypeRecord, + ParameterRecord, + TypeParameterTypeRecord, +} from '../../collectors/utils/collect-types'; + +export class factory { + /** + * generate type node from `TypeRecord` + */ + static createTypeNodeFromRecord(record: TypeRecord): arkts.TypeNode { + const type = record?.type; + switch (type) { + case TypeRecordTypes.ARRAY: + return factory.createArrayTypeFromRecord(record); + case TypeRecordTypes.FUNCTION: + return factory.createFunctionTypeFromRecord(record); + case TypeRecordTypes.NULL: + return factory.createNullTypeFromRecord(record); + case TypeRecordTypes.UNDEFINED: + return factory.createUndefinedTypeFromRecord(record); + case TypeRecordTypes.THIS: + return factory.createThisTypeFromRecord(record); + case TypeRecordTypes.UNION: + return factory.createUnionTypeFromRecord(record); + case TypeRecordTypes.TYPE_REFERENCE: + return factory.createTypeReferenceFromRecord(record); + case TypeRecordTypes.PRIMITIVE: + return factory.createPrimitiveTypeFromRecord(record); + default: + throw new Error(`Unknown type node's type: ${type}`); + } + } + + /** + * generate `arkts.ETSPrimitiveType` node from `PrimitiveTypeRecord` + */ + static createPrimitiveTypeFromRecord(record: PrimitiveTypeRecord): arkts.ETSPrimitiveType { + const typeName = record?.typeName; + switch (typeName) { + case 'boolean': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BOOLEAN); + case 'byte': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BYTE); + case 'char': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_CHAR); + case 'double': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_DOUBLE); + case 'float': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_FLOAT); + case 'int': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_INT); + case 'long': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_LONG); + case 'short': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_SHORT); + case 'void': + return arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID); + default: + throw new Error(`Cannot create primitive type because of name: ${typeName}`); + } + } + + /** + * generate `arkts.ETSParameterExpression` node from `ParameterRecord` + */ + static createParameterFromRecord(record: ParameterRecord): arkts.ETSParameterExpression { + const name = record.name; + const annotations = record.annotations.map((a) => a.clone()); + const isOptional = record.isOptional; + const typeAnnotation = factory.createTypeNodeFromRecord(record.typeRecord); + const parameter = arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier(name, typeAnnotation), + undefined + ); + parameter.annotations = annotations; + if (isOptional) { + parameter.setOptional(true); + } + return parameter; + } + + /** + * generate `arkts.TSTypeParameter` node from `TypeParameterTypeRecord` + */ + static createTypeParameterFromRecord(record: TypeParameterTypeRecord): arkts.TSTypeParameter { + const annotations = record.annotations.map((a) => a.clone()); + const typeName = record.typeName ? arkts.factory.createIdentifier(record.typeName) : undefined; + const defaultType = record.defaultType ? factory.createTypeNodeFromRecord(record.defaultType) : undefined; + const constraint = record.constraint ? factory.createTypeNodeFromRecord(record.constraint) : undefined; + const typeParameter = arkts.factory.createTypeParameter(typeName, constraint, defaultType); + typeParameter.setAnnotations(annotations); + return typeParameter; + } + + /** + * generate `arkts.TSArrayType` node from `ArrayTypeRecord` + */ + static createArrayTypeFromRecord(record: ArrayTypeRecord): arkts.TSArrayType { + const elementType = record.elementType ? factory.createTypeNodeFromRecord(record.elementType) : undefined; + return arkts.factory.createTSArrayType(elementType); + } + + /** + * generate `arkts.ETSNullType` node from `NullTypeRecord` + */ + static createNullTypeFromRecord(record: NullTypeRecord): arkts.ETSNullType { + return arkts.factory.createETSNullType(); + } + + /** + * generate `arkts.ETSUndefinedType` node from `UndefinedTypeRecord` + */ + static createUndefinedTypeFromRecord(record: UndefinedTypeRecord): arkts.ETSUndefinedType { + return arkts.factory.createETSUndefinedType(); + } + + /** + * generate `arkts.TSThisType` node from `ThisTypeRecord` + */ + static createThisTypeFromRecord(record: ThisTypeRecord): arkts.TSThisType { + return arkts.factory.createTSThisType(); + } + + /** + * generate `arkts.ETSFunctionType` node from `FunctionTypeRecord` + */ + static createFunctionTypeFromRecord(record: FunctionTypeRecord): arkts.ETSFunctionType { + const annotations = record.annotations.map((a) => a.clone()); + const returnType = factory.createTypeNodeFromRecord(record.returnType); + const params = record.params.map((p) => factory.createParameterFromRecord(p)); + const typeParams = record.typeParams?.map((p) => factory.createTypeParameterFromRecord(p)); + const funcType = arkts.factory.createFunctionType( + arkts.factory.createFunctionSignature( + typeParams ? arkts.factory.createTypeParameterDeclaration(typeParams, typeParams.length) : undefined, + params, + returnType, + false + ), + arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW + ); + funcType.setAnnotations(annotations); + return funcType; + } + + /** + * generate `arkts.ETSUnionType` node from `UnionTypeRecord` + */ + static createUnionTypeFromRecord(record: UnionTypeRecord): arkts.ETSUnionType { + const types = record.types.map((t) => factory.createTypeNodeFromRecord(t)); + return arkts.factory.createUnionType(types); + } + + /** + * @internal + */ + static createTypeNameForTypeReferencePart(name: string | string[]): arkts.Expression { + if (!Array.isArray(name)) { + return arkts.factory.createIdentifier(name); + } + const names = name.map((n) => arkts.factory.createIdentifier(n)); + if (names.length === 1) { + return names.at(0)!; + } + const leftName = names.shift(); + const rightName = names.shift(); + let nameNode: arkts.TSQualifiedName = arkts.factory.createTSQualifiedName(leftName, rightName); + while (names.length > 0) { + const currName = names.shift(); + nameNode = arkts.factory.updateTSQualifiedName(nameNode, nameNode, currName); + } + return nameNode; + } + + /** + * generate `arkts.ETSTypeReference` node from `TypeReferenceTypeRecord` + */ + static createTypeReferenceFromRecord(record: TypeReferenceTypeRecord): arkts.ETSTypeReference { + const name = record.typeName; + const annotations = record.annotations; + const typeParams = record.typeParams?.map((p) => factory.createTypeNodeFromRecord(p)); + const typeRef = arkts.factory.createTypeReference( + arkts.factory.createTypeReferencePart( + factory.createTypeNameForTypeReferencePart(name), + typeParams ? arkts.factory.createTSTypeParameterInstantiation(typeParams) : undefined + ) + ); + typeRef.setAnnotations(annotations); + return typeRef; + } +} diff --git a/arkui-plugins/ui-plugins/utils.ts b/arkui-plugins/ui-plugins/utils.ts index 562c74f4ed6619f53fcd07cf76251a9d9a615122..cb6ae080e45d2756c222b4659d907bbf1e56b385 100644 --- a/arkui-plugins/ui-plugins/utils.ts +++ b/arkui-plugins/ui-plugins/utils.ts @@ -43,7 +43,9 @@ export enum BuilderLambdaNames { TRANSFORM_METHOD_NAME = '_instantiateImpl', STYLE_PARAM_NAME = 'style', STYLE_ARROW_PARAM_NAME = 'instance', - CONTENT_PARAM_NAME = 'content' + CONTENT_PARAM_NAME = 'content', + COMPONENT_PARAM_ORI = 'content_', + APPLY_ATTRIBUTES_FINISH_METHOD = 'applyAttributesFinish' } // IMPORT diff --git a/koala-wrapper/src/arkts-api/factory/nodeFactory.ts b/koala-wrapper/src/arkts-api/factory/nodeFactory.ts index c780fdbf9e9b760ce14b9536ad5b386725d3772d..cabad86df388a5d31543bd73be742afbab273d70 100644 --- a/koala-wrapper/src/arkts-api/factory/nodeFactory.ts +++ b/koala-wrapper/src/arkts-api/factory/nodeFactory.ts @@ -81,6 +81,10 @@ import { ForUpdateStatement, ForInStatement, ForOfStatement, + TSArrayType, + ETSNullType, + TSThisType, + TSQualifiedName, } from '../../generated'; import { Es2pandaModifierFlags } from '../../generated/Es2pandaEnums'; import { classPropertySetOptional, hasModifierFlag } from '../utilities/public'; @@ -142,6 +146,10 @@ import { updateTSClassImplements } from '../node-utilities/TSClassImplements'; import { updateForUpdateStatement } from '../node-utilities/ForUpdateStatement'; import { updateForInStatement } from '../node-utilities/ForInStatement'; import { updateForOfStatement } from '../node-utilities/ForOfStatement'; +import { updateTSArrayType } from '../node-utilities/TSArrayType'; +import { updateETSNullType } from '../node-utilities/ETSNullType'; +import { updateTSThisType } from '../node-utilities/TSThisType'; +import { updateTSQualifiedName } from '../node-utilities/TSQualifiedName'; export const factory = { get createIdentifier(): (...args: Parameters) => Identifier { @@ -601,6 +609,30 @@ export const factory = { get updateForOfStatement(): (...args: Parameters) => ForOfStatement { return updateForOfStatement; }, + get createTSArrayType(): (...args: Parameters) => TSArrayType { + return TSArrayType.createTSArrayType; + }, + get updateTSArrayType(): (...args: Parameters) => TSArrayType { + return updateTSArrayType; + }, + get createETSNullType(): (...args: Parameters) => ETSNullType { + return ETSNullType.createETSNullType; + }, + get updateETSNullType(): (...args: Parameters) => ETSNullType { + return updateETSNullType; + }, + get createTSThisType(): (...args: Parameters) => TSThisType { + return TSThisType.createTSThisType; + }, + get updateTSThisType(): (...args: Parameters) => TSThisType { + return updateTSThisType; + }, + get createTSQualifiedName(): (...args: Parameters) => TSQualifiedName { + return TSQualifiedName.createTSQualifiedName; + }, + get updateTSQualifiedName(): (...args: Parameters) => TSQualifiedName { + return updateTSQualifiedName; + }, /** @deprecated */ createTypeParameter1_(name: Identifier, constraint?: TypeNode, defaultType?: TypeNode) { return TSTypeParameter.createTSTypeParameter(Identifier.create1Identifier(name.name), constraint, defaultType); diff --git a/koala-wrapper/src/arkts-api/index.ts b/koala-wrapper/src/arkts-api/index.ts index eef8304a59881a93eb1c6b9e7167d5b73492062e..e76f056e47297107a129e2c928abb95456c48de1 100644 --- a/koala-wrapper/src/arkts-api/index.ts +++ b/koala-wrapper/src/arkts-api/index.ts @@ -63,6 +63,7 @@ export * from '../generated/peers/TSArrayType'; export * from '../generated/peers/ArrayExpression'; export * from '../generated/peers/TryStatement'; export * from '../generated/peers/ETSNullType'; +export * from '../generated/peers/TSQualifiedName'; export * from './types'; export * from './utilities/private'; diff --git a/koala-wrapper/src/arkts-api/node-utilities/ETSNullType.ts b/koala-wrapper/src/arkts-api/node-utilities/ETSNullType.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e1b10940f0bf7207120c7bf3dca258758b652d7 --- /dev/null +++ b/koala-wrapper/src/arkts-api/node-utilities/ETSNullType.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ETSNullType } from '../../generated'; +import { attachModifiers, updateThenAttach } from '../utilities/private'; + +export function updateETSNullType( + original: ETSNullType +): ETSNullType { + /* TODO: no getter provided yet */ + + const update = updateThenAttach(ETSNullType.updateETSNullType, attachModifiers); + return update(original); +} diff --git a/koala-wrapper/src/arkts-api/node-utilities/TSArrayType.ts b/koala-wrapper/src/arkts-api/node-utilities/TSArrayType.ts new file mode 100644 index 0000000000000000000000000000000000000000..fcbfdb5da3a47a7a51ee5694e85a0764287c07af --- /dev/null +++ b/koala-wrapper/src/arkts-api/node-utilities/TSArrayType.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TSArrayType, TypeNode } from '../../generated'; +import { isSameNativeObject } from '../peers/ArktsObject'; +import { attachModifiers, updateThenAttach } from '../utilities/private'; + +export function updateTSArrayType( + original: TSArrayType, + elementType?: TypeNode +): TSArrayType { + if ( + isSameNativeObject(elementType, original.elementType) + ) { + return original; + } + + const update = updateThenAttach(TSArrayType.updateTSArrayType, attachModifiers); + return update(original, elementType); +} diff --git a/koala-wrapper/src/arkts-api/node-utilities/TSQualifiedName.ts b/koala-wrapper/src/arkts-api/node-utilities/TSQualifiedName.ts new file mode 100644 index 0000000000000000000000000000000000000000..655c3239c64dd850214b046b519c6f8299ba2665 --- /dev/null +++ b/koala-wrapper/src/arkts-api/node-utilities/TSQualifiedName.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Expression, Identifier, TSQualifiedName } from '../../generated'; +import { isSameNativeObject } from '../peers/ArktsObject'; +import { attachModifiers, updateThenAttach } from '../utilities/private'; + +export function updateTSQualifiedName( + original: TSQualifiedName, + left?: Expression, + right?: Identifier +): TSQualifiedName { + if (isSameNativeObject(left, original.left) && isSameNativeObject(right, original.right)) { + return original; + } + + const update = updateThenAttach(TSQualifiedName.updateTSQualifiedName, attachModifiers); + return update(original); +} diff --git a/koala-wrapper/src/arkts-api/node-utilities/TSThisType.ts b/koala-wrapper/src/arkts-api/node-utilities/TSThisType.ts new file mode 100644 index 0000000000000000000000000000000000000000..77b1c0e60daa4232f10d2f244febcf7eff6fa7db --- /dev/null +++ b/koala-wrapper/src/arkts-api/node-utilities/TSThisType.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TSThisType } from '../../generated'; +import { attachModifiers, updateThenAttach } from '../utilities/private'; + +export function updateTSThisType( + original: TSThisType +): TSThisType { + /* TODO: no getter provided yet */ + + const update = updateThenAttach(TSThisType.updateTSThisType, attachModifiers); + return update(original); +} diff --git a/koala-wrapper/src/arkts-api/types.ts b/koala-wrapper/src/arkts-api/types.ts index d73f539d0750207a7aacaa766b37e2d78a15a0fa..9241a12d1a6dc2e26928ebfc8d346085bbe3c757 100644 --- a/koala-wrapper/src/arkts-api/types.ts +++ b/koala-wrapper/src/arkts-api/types.ts @@ -572,7 +572,7 @@ export class ETSParameterExpression extends Expression { global.generatedEs2panda._ETSParameterExpressionSetTypeAnnotation(global.context, this.peer, t.peer); } - get optional(): Boolean { + get optional(): boolean { return global.generatedEs2panda._ETSParameterExpressionIsOptionalConst(global.context, this.peer); }