diff --git a/arkui-plugins/test/demo/mock/decorators/custom-dialog/declare-custom-dialog.ets b/arkui-plugins/test/demo/mock/decorators/custom-dialog/declare-custom-dialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..72cc017a539e4492813d6a860ad6ad8ee381a1d9 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/custom-dialog/declare-custom-dialog.ets @@ -0,0 +1,44 @@ +/* + * 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 { State } from "@ohos.arkui.stateManagement" +import { CustomDialog, CustomDialogController, ComponentV2, Component, Builder } from "@ohos.arkui.component" + +@CustomDialog +export declare struct CustomDialogExample { + aaController?: CustomDialogController; + @State text: string; + hh: string + + @Builder build(): void; +} + +@Component +struct CustomDialogUserV1 { + dialogController: CustomDialogController | null = new CustomDialogController({ + builder: CustomDialogExample(), + }) + + build() {} +} + +@ComponentV2 +struct CustomDialogUserV2 { + dialogController: CustomDialogController | null = new CustomDialogController({ + builder: CustomDialogExample(), + }) + + build() {} +} \ No newline at end of file diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/base-custom-dialog.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/base-custom-dialog.test.ts index 7547b4bfe17a101048e2c8d8eac4c05f11dd47a2..28d4b35b1c37e7ac03bf450b771dc283a180bb86 100644 --- a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/base-custom-dialog.test.ts +++ b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/base-custom-dialog.test.ts @@ -131,10 +131,6 @@ function main() {} this.__backing_hh!.set(value); } - public __setDialogController__(controller: CustomDialogController): void { - this.__backing_aaController = controller; - } - @memo() public build() { Column(undefined, undefined, @memo() (() => { Text(@memo() ((instance: TextAttribute): void => { @@ -154,6 +150,9 @@ function main() {} private constructor() {} + public __setDialogController__(controller: CustomDialogController): void { + this.__backing_aaController = controller; + } } @Component() final struct CustomDialogUser extends CustomComponent { diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/controller-in-build.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/controller-in-build.test.ts index 9ad581f09cae75e99d9b61ba4101a9b9f5eae658..55568990b957c32a9c087bd6bdf2aea283fce15e 100644 --- a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/controller-in-build.test.ts +++ b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/controller-in-build.test.ts @@ -107,10 +107,6 @@ function main() {} this.__backing_hh!.set(value); } - public __setDialogController__(controller: CustomDialogController): void { - this.__backing_aaController = controller; - } - @memo() public build() { Column(undefined, undefined, @memo() (() => { Text(undefined, "CustomDialog One", undefined, undefined); @@ -119,6 +115,9 @@ function main() {} private constructor() {} + public __setDialogController__(controller: CustomDialogController): void { + this.__backing_aaController = controller; + } } @Component() final struct CustomDialogUser extends CustomComponent { diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/controller-in-method.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/controller-in-method.test.ts index ea1b773addc3dc8845ccd7c6e7a63be604ac2650..61084a04929771ca97d369cfc1445bbaf2ab1783 100644 --- a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/controller-in-method.test.ts +++ b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/controller-in-method.test.ts @@ -67,14 +67,13 @@ function main() {} this.__backing_aaController = value; } - public __setDialogController__(controller: CustomDialogController): void { - this.__backing_aaController = controller; - } - @memo() public build() {} private constructor() {} - + + public __setDialogController__(controller: CustomDialogController): void { + this.__backing_aaController = controller; + } } @Component() final struct CustomDialogUser extends CustomComponent { diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/declare-custom-dialog.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/declare-custom-dialog.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8fb2bb4950ef1808f6d61802865e69a8965e092 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/declare-custom-dialog.test.ts @@ -0,0 +1,180 @@ +/* + * 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 path from 'path'; +import { PluginTester } from '../../../../utils/plugin-tester'; +import { mockBuildConfig } from '../../../../utils/artkts-config'; +import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../../utils/path-config'; +import { parseDumpSrc } from '../../../../utils/parse-string'; +import { memoNoRecheck, recheck, uiNoRecheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const FUNCTION_DIR_PATH: string = 'decorators/custom-dialog'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, FUNCTION_DIR_PATH, 'declare-custom-dialog.ets'), +]; + +const pluginTester = new PluginTester('test declared struct @CustomDialog transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +}; + +const expectedCheckedScript: string = ` +import { IStateDecoratedVariable as IStateDecoratedVariable } from "arkui.stateManagement.decorator"; +import { memo as memo } from "arkui.stateManagement.runtime"; +import { BaseCustomDialog as BaseCustomDialog } from "arkui.component.customComponent"; +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; +import { State as State } from "@ohos.arkui.stateManagement"; +import { CustomDialog as CustomDialog, CustomDialogController as CustomDialogController, ComponentV2 as ComponentV2, Component as Component, Builder as Builder } from "@ohos.arkui.component"; + +function main() {} + +@CustomDialog() export declare final struct CustomDialogExample extends BaseCustomDialog { + public aaController?: (CustomDialogController | undefined); + + @State() public text: string; + + public hh: string; + + @Builder() @memo() public build(): void + + public constructor() {} + + public static _buildCompatibleNode(options: __Options_CustomDialogExample): void + +} + +@Component() final struct CustomDialogUserV1 extends CustomComponent { + public __initializeStruct(initializers: (__Options_CustomDialogUserV1 | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_dialogController = ((({let gensym___51459619 = initializers; + (((gensym___51459619) == (null)) ? undefined : gensym___51459619.dialogController)})) ?? (({let gensym___203542966: Any; + gensym___203542966 = new CustomDialogController({ + builder: @memo() (() => { + CustomDialogExample._instantiateImpl(undefined, (() => { + const instance = new CustomDialogExample(); + instance.__setDialogController__((gensym___203542966 as CustomDialogController)); + return instance; + }), undefined, undefined); + }), + baseComponent: this, + }) + (gensym___203542966 as CustomDialogController)}))); + } + + public __updateStruct(initializers: (__Options_CustomDialogUserV1 | undefined)): void {} + + private __backing_dialogController?: (CustomDialogController | null); + + public get dialogController(): (CustomDialogController | null) { + return (this.__backing_dialogController as (CustomDialogController | null)); + } + + public set dialogController(value: (CustomDialogController | null)) { + this.__backing_dialogController = value; + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() final struct CustomDialogUserV2 extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_CustomDialogUserV2 | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_dialogController = ((({let gensym___176924847 = initializers; + (((gensym___176924847) == (null)) ? undefined : gensym___176924847.dialogController)})) ?? (({let gensym___46528967: Any; + gensym___46528967 = new CustomDialogController({ + builder: @memo() (() => { + CustomDialogExample._instantiateImpl(undefined, (() => { + const instance = new CustomDialogExample(); + instance.__setDialogController__((gensym___46528967 as CustomDialogController)); + return instance; + }), undefined, undefined); + }), + baseComponent: this, + }) + (gensym___46528967 as CustomDialogController)}))); + } + + public __updateStruct(initializers: (__Options_CustomDialogUserV2 | undefined)): void {} + + private __backing_dialogController?: (CustomDialogController | null); + + public get dialogController(): (CustomDialogController | null) { + return (this.__backing_dialogController as (CustomDialogController | null)); + } + + public set dialogController(value: (CustomDialogController | null)) { + this.__backing_dialogController = value; + } + + @memo() public build() {} + + private constructor() {} + +} + +@CustomDialog() export declare interface __Options_CustomDialogExample { + set aaController(aaController: ((CustomDialogController | undefined) | undefined)) + + get aaController(): ((CustomDialogController | undefined) | undefined) + set text(text: (string | undefined)) + + get text(): (string | undefined) + set __backing_text(__backing_text: (IStateDecoratedVariable | undefined)) + + get __backing_text(): (IStateDecoratedVariable | undefined) + set hh(hh: (string | undefined)) + + get hh(): (string | undefined) + +} + +@Component() export interface __Options_CustomDialogUserV1 { + set dialogController(dialogController: ((CustomDialogController | null) | undefined)) + + get dialogController(): ((CustomDialogController | null) | undefined) + +} + +@ComponentV2() export interface __Options_CustomDialogUserV2 { + set dialogController(dialogController: ((CustomDialogController | null) | undefined)) + + get dialogController(): ((CustomDialogController | null) | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test declared struct @CustomDialog transformation', + [parsedTransform, uiNoRecheck, memoNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); \ No newline at end of file diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/extends-dialog-controller.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/extends-dialog-controller.test.ts index 1785866b1612271345d6d1533d4f195af72fc34f..e83754c45b64208421d5792898d064556455550b 100644 --- a/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/extends-dialog-controller.test.ts +++ b/arkui-plugins/test/ut/ui-plugins/decorators/custom-dialog/extends-dialog-controller.test.ts @@ -73,16 +73,15 @@ function main() {} this.__backing_aaController = value; } - public __setDialogController__(controller: CustomDialogController): void { - this.__backing_aaController = controller; - } - @memo() public build() { Column(undefined, undefined, @memo() (() => {})); } private constructor() {} + public __setDialogController__(controller: CustomDialogController): void { + this.__backing_aaController = controller; + } } class DialogControllerV2 extends CustomDialogController { diff --git a/arkui-plugins/ui-plugins/struct-translators/factory.ts b/arkui-plugins/ui-plugins/struct-translators/factory.ts index 2e41cd8d30f0db3ac89b7c67b775c6fc90f6fe41..433902743d2f4cbbc77c3f34248ab653260b6b7e 100644 --- a/arkui-plugins/ui-plugins/struct-translators/factory.ts +++ b/arkui-plugins/ui-plugins/struct-translators/factory.ts @@ -504,25 +504,15 @@ export class factory { if (!className) { throw new Error('Non Empty className expected for Component'); } - + const body: readonly arkts.AstNode[] = definition.body; const propertyTranslators: (PropertyTranslator | MethodTranslator)[] = filterDefined( - definition.body.map((it) => classifyStructMembers(it, scope)) + body.map((member) => classifyStructMembers(member, scope)) ); const translatedMembers: arkts.AstNode[] = this.tranformPropertyMembers( propertyTranslators, classOptionsName ?? getCustomComponentOptionsName(className), scope ); - if (hasDecorator(node.definition, DecoratorNames.CUSTOM_DIALOG)) { - const dialogControllerProperty: arkts.ClassProperty | undefined = definition.body.find( - (item: arkts.AstNode) => arkts.isClassProperty(item) && getCustomDialogController(item).length > 0 - ) as arkts.ClassProperty | undefined; - if (!!dialogControllerProperty) { - translatedMembers.push( - this.createCustomDialogMethod(getCustomDialogController(dialogControllerProperty)) - ); - } - } const updateMembers: arkts.AstNode[] = definition.body .filter( (member) => @@ -531,13 +521,35 @@ export class factory { ) .map((member: arkts.AstNode) => factory.transformNonPropertyMembersInClass(member, scope.isDecl)); - const updateClassDef: arkts.ClassDefinition = this.updateCustomComponentClass(definition, [ + let updateClassDef: arkts.ClassDefinition = this.updateCustomComponentClass(definition, [ ...translatedMembers, ...updateMembers, ]); + if ( + !!scope.annotations.customdialog || + (scope.isDecl && scope.name === CustomComponentNames.BASE_CUSTOM_DIALOG_NAME) + ) { + updateClassDef.addProperties(factory.addControllerSetMethod(scope.isDecl, body)); + } return arkts.factory.updateClassDeclaration(node, updateClassDef); } + /** + * add `__setDialogController__` method in `@CustomDialog` component. + */ + static addControllerSetMethod(isDecl: boolean, body: readonly arkts.AstNode[]): arkts.MethodDefinition[] { + if (isDecl) { + return [this.createCustomDialogMethod(isDecl)]; + } + const dialogControllerProperty: arkts.ClassProperty | undefined = body.find( + (item: arkts.AstNode) => arkts.isClassProperty(item) && getCustomDialogController(item).length > 0 + ) as arkts.ClassProperty | undefined; + if (!!dialogControllerProperty) { + return [this.createCustomDialogMethod(isDecl, getCustomDialogController(dialogControllerProperty))]; + } + return []; + } + /** * transform `$r` and `$rawfile` function calls. */ @@ -614,7 +626,11 @@ export class factory { } } - static createCustomDialogMethod(controller: string): arkts.MethodDefinition { + static createCustomDialogMethod(isDecl: boolean, controller?: string): arkts.MethodDefinition { + let block: arkts.BlockStatement | undefined = undefined; + if (!!controller) { + block = arkts.factory.createBlock(this.createSetControllerElements(controller)); + } const param: arkts.ETSParameterExpression = arkts.factory.createParameterDeclaration( arkts.factory.createIdentifier( CustomDialogNames.CONTROLLER, @@ -622,38 +638,41 @@ export class factory { ), undefined ); - const block = arkts.factory.createBlock( - controller.length !== 0 - ? [ - arkts.factory.createExpressionStatement( - arkts.factory.createAssignmentExpression( - generateThisBacking(backingField(controller)), - arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, - arkts.factory.createIdentifier(CustomDialogNames.CONTROLLER) - ) - ), - ] - : [] - ); - const script = arkts.factory.createScriptFunction( - block, - arkts.FunctionSignature.createFunctionSignature( - undefined, - [param], - arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), - false - ), - arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC - ); + const modifiers = isDecl + ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC | arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE + : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC; + return UIFactory.createMethodDefinition({ + key: arkts.factory.createIdentifier(CustomDialogNames.SET_DIALOG_CONTROLLER_METHOD), + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + function: { + body: block, + params: [param], + returnTypeAnnotation: arkts.factory.createPrimitiveType( + arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID + ), + hasReceiver: false, + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + modifiers: modifiers, + }, + modifiers: modifiers, + }); + } - return arkts.factory.createMethodDefinition( - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, - arkts.factory.createIdentifier(CustomDialogNames.SET_DIALOG_CONTROLLER_METHOD), - script, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - false - ); + /* + * create assignment expression `this.__backing = controller`. + */ + static createSetControllerElements(controller: string): arkts.AstNode[] { + return controller.length !== 0 + ? [ + arkts.factory.createExpressionStatement( + arkts.factory.createAssignmentExpression( + generateThisBacking(backingField(controller)), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + arkts.factory.createIdentifier(CustomDialogNames.CONTROLLER) + ) + ), + ] + : []; } /* @@ -919,8 +938,7 @@ export class factory { ...restMembers, ...classScopeInfo.getters, ]; - const isDecl: boolean = arkts.hasModifierFlag(definition, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); - return ObservedAnno.isObservedV2 && !isDecl + return ObservedAnno.isObservedV2 ? returnNodes.concat(this.transformObservedV2Constuctor(definition, classScopeInfo.className)) : returnNodes; } @@ -932,12 +950,13 @@ export class factory { arkts.isMethodDefinition(it) && isKnownMethodDefinition(it, CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI) ) as arkts.MethodDefinition | undefined; + const isDecl: boolean = arkts.hasModifierFlag(definition, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); if (!originConstructorMethod) { return UIFactory.createMethodDefinition({ key: arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI), function: { key: arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI), - body: arkts.factory.createBlock(addConstructorNodes), + body: isDecl ? undefined : arkts.factory.createBlock(addConstructorNodes), flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_CONSTRUCTOR, modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_CONSTRUCTOR, }, @@ -945,7 +964,9 @@ export class factory { modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_CONSTRUCTOR, }); } - + if (isDecl) { + return originConstructorMethod; + } const originBody = originConstructorMethod.scriptFunction.body as arkts.BlockStatement | undefined; return UIFactory.updateMethodDefinition(originConstructorMethod, { function: { diff --git a/arkui-plugins/ui-plugins/utils.ts b/arkui-plugins/ui-plugins/utils.ts index 51de584470146558f23caf845c66c3777a881f35..210a2aeabfcb7278c9972cb004ae289e2e3334ae 100644 --- a/arkui-plugins/ui-plugins/utils.ts +++ b/arkui-plugins/ui-plugins/utils.ts @@ -240,7 +240,7 @@ export function collectCustomComponentScopeInfo( ...(isReusable && !annotations?.reusable && { reusable: anno }), ...(isReusableV2 && !annotations?.reusableV2 && { reusableV2: anno }), ...(isCustomLayout && !annotations?.customLayout && { customLayout: anno }), - ...(isCustomDialog && !annotations?.reusable && { customdialog: anno }), + ...(isCustomDialog && !annotations?.customdialog && { customdialog: anno }), }; } if (!isCustomComponent) {