diff --git a/arkui-plugins/memo-plugins/utils.ts b/arkui-plugins/memo-plugins/utils.ts index fea2bffadc1824364f79f6df9754a83f58acb9e7..1d2aa79a1417c82b6fdcadd1f25fb124b9c8ee9a 100644 --- a/arkui-plugins/memo-plugins/utils.ts +++ b/arkui-plugins/memo-plugins/utils.ts @@ -21,6 +21,7 @@ export enum RuntimeNames { __CONTEXT = '__context', __ID = '__id', __KEY = '__key', + ANNOTATION_BUILDER = 'Builder', ANNOTATION = 'memo', ANNOTATION_ENTRY = 'memo_entry', ANNOTATION_INTRINSIC = 'memo_intrinsic', @@ -116,7 +117,7 @@ export type MemoAstNode = | arkts.VariableDeclaration; export function hasMemoAnnotation(node: T): boolean { - return node.annotations.some((it) => isMemoAnnotation(it, RuntimeNames.ANNOTATION)); + return node.annotations.some((it) => isMemoAnnotation(it, RuntimeNames.ANNOTATION) || isMemoAnnotation(it, RuntimeNames.ANNOTATION_BUILDER)); } export function hasMemoIntrinsicAnnotation(node: T): boolean { diff --git a/arkui-plugins/test/demo/mock/wrap-builder/init-with-builder.ets b/arkui-plugins/test/demo/mock/wrap-builder/init-with-builder.ets new file mode 100644 index 0000000000000000000000000000000000000000..8ec8bd8a67acc1b77e2066f459f0113395dafde2 --- /dev/null +++ b/arkui-plugins/test/demo/mock/wrap-builder/init-with-builder.ets @@ -0,0 +1,33 @@ +/* + * 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 { Component, Text, WrappedBuilder, wrapBuilder, Builder, Column } from "@kit.ArkUI" + +@Builder +function myBuilder(value: string, size: number) { + Text(value).fontSize(size) +} + +type MyBuilderFuncType = @Builder (value: string, size: number) => void; +let globalBuilder: WrappedBuilder = wrapBuilder(myBuilder); + +@Component +struct ImportStruct { + build() { + Column() { + globalBuilder.builder('hello', 50) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/wrap-builder/wrap-builder-in-ui.ets b/arkui-plugins/test/demo/mock/wrap-builder/wrap-builder-in-ui.ets new file mode 100644 index 0000000000000000000000000000000000000000..f92db0bc0ef961bc7bdb8719a1f8925186a14a27 --- /dev/null +++ b/arkui-plugins/test/demo/mock/wrap-builder/wrap-builder-in-ui.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 { Component, Text, WrappedBuilder, wrapBuilder, Builder, Column, ForEach } from "@kit.ArkUI" + +@Builder +function myBuilder(value: string, size: number) { + Text(value).fontSize(size) +} + +@Builder +function yourBuilder(value: string, size: number) { + Text(value).fontSize(size) +} + +type MyBuilderFuncType = @Builder (value: string, size: number) => void; +const globalBuilderArr: WrappedBuilder[] = [wrapBuilder(myBuilder), wrapBuilder(yourBuilder)]; + +@Component +struct ImportStruct { + @Builder testBuilder() { + ForEach(globalBuilderArr, (item: WrappedBuilder) => { + item.builder('hello world', 39) + }) + } + + build() { + Column() { + this.testBuilder() + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/ut/ui-plugins/wrap-builder/init-with-builder.test.ts b/arkui-plugins/test/ut/ui-plugins/wrap-builder/init-with-builder.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..946db1fa1945b0256dc9429a2826e0ec372fc5fd --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/wrap-builder/init-with-builder.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { uiNoRecheck, recheck } from '../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; +import { uiTransform } from '../../../../ui-plugins'; +import { Plugins } from '../../../../common/plugin-context'; + +const WRAP_BUILDER_DIR_PATH: string = 'wrap-builder'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, WRAP_BUILDER_DIR_PATH, 'init-with-builder.ets'), +]; + +const pluginTester = new PluginTester('test wrap builder init with @Builder function', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsed transform', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; +import { TextAttribute as TextAttribute } from "arkui.component.text"; +import { LayoutCallback as LayoutCallback } from "arkui.component.customComponent"; +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; +import { Component as Component, Text as Text, WrappedBuilder as WrappedBuilder, wrapBuilder as wrapBuilder, Builder as Builder, Column as Column } from "@kit.ArkUI"; + +let globalBuilder: WrappedBuilder; + +function main() {} + +globalBuilder = wrapBuilder(myBuilder); +@memo() function myBuilder(value: string, size: number) { + Text(((instance: TextAttribute): void => { + instance.fontSize(size); + return; + }), value); +} + + +type MyBuilderFuncType = @memo() ((value: string, size: number)=> void); + +@Component() final struct ImportStruct extends CustomComponent { + public __initializeStruct(initializers: __Options_ImportStruct | undefined, @memo() content: (()=> void) | undefined): void {} + + public __updateStruct(initializers: __Options_ImportStruct | undefined): void {} + + @memo() public build() { + Column(undefined, (() => { + globalBuilder.builder("hello", 50); + })); + } + + private constructor() {} + +} + +@Component() export interface __Options_ImportStruct { + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test wrap builder init with @Builder function', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/wrap-builder/wrap-builder-in-ui.test.ts b/arkui-plugins/test/ut/ui-plugins/wrap-builder/wrap-builder-in-ui.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..33bd5355c3d4d6c136bef236e50cfd058253a90b --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/wrap-builder/wrap-builder-in-ui.test.ts @@ -0,0 +1,114 @@ +/* + * 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 { uiNoRecheck, recheck } from '../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../utils/shared-types'; +import { uiTransform } from '../../../../ui-plugins'; +import { Plugins } from '../../../../common/plugin-context'; + +const WRAP_BUILDER_DIR_PATH: string = 'wrap-builder'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, WRAP_BUILDER_DIR_PATH, 'wrap-builder-in-ui.ets'), +]; + +const pluginTester = new PluginTester('test wrap builder used in UI', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsed transform', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { TextAttribute as TextAttribute } from "arkui.component.text"; + +import { LayoutCallback as LayoutCallback } from "arkui.component.customComponent"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Text as Text, WrappedBuilder as WrappedBuilder, wrapBuilder as wrapBuilder, Builder as Builder, Column as Column, ForEach as ForEach } from "@kit.ArkUI"; + +const globalBuilderArr: Array> = [wrapBuilder(myBuilder), wrapBuilder(yourBuilder)]; + +function main() {} + + +@memo() function myBuilder(value: string, size: number) { + Text(((instance: TextAttribute): void => { + instance.fontSize(size); + return; + }), value); +} + +@memo() function yourBuilder(value: string, size: number) { + Text(((instance: TextAttribute): void => { + instance.fontSize(size); + return; + }), value); +} + + +type MyBuilderFuncType = @memo() ((value: string, size: number)=> void); + +@Component() final struct ImportStruct extends CustomComponent { + public __initializeStruct(initializers: __Options_ImportStruct | undefined, @memo() content: (()=> void) | undefined): void {} + + public __updateStruct(initializers: __Options_ImportStruct | undefined): void {} + + @memo() public testBuilder() { + ForEach(globalBuilderArr, ((item: WrappedBuilder) => { + item.builder("hello world", 39); + })); + } + + @memo() public build() { + Column(undefined, @memo() (() => { + this.testBuilder(); + })); + } + + private constructor() {} + +} + +@Component() export interface __Options_ImportStruct { + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test wrap builder init with @Builder function', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts index 9c303f7659da18e823426653cff0299052e4a535..5ba42556d026949ab64e8e1c6056a5f84972e17d 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts @@ -51,6 +51,13 @@ export class factory { newName: string | undefined ): arkts.MethodDefinition { const func: arkts.ScriptFunction = node.scriptFunction; + if (func.params.length > 0) { + newParams.push(...func.params.slice(0, func.params.length - 1)); + if (externalSourceName === 'arkui.component.xcomponent' && node.name.name === 'XComponent') { + newParams.push(this.createPackageInfoArgForXComponent()); + } + newParams.push(func.params.at(func.params.length - 1)!); + } const updateFunc = arkts.factory .updateScriptFunction( func,