diff --git a/arkui-plugins/collectors/memo-collectors/factory.ts b/arkui-plugins/collectors/memo-collectors/factory.ts index 2c4931e8cb23cb3f7ecc1fa5e9102c964c97a7df..66d1dcede9919fb9535cd2139a26d9fc240589f3 100644 --- a/arkui-plugins/collectors/memo-collectors/factory.ts +++ b/arkui-plugins/collectors/memo-collectors/factory.ts @@ -25,72 +25,186 @@ import { findCanAddMemoFromProperty, findCanAddMemoFromTypeAlias, } from './utils'; +import { coerceToAstNode } from '../../common/arkts-utils'; -export function findAndCollectMemoableNode(node: arkts.AstNode): arkts.AstNode { +export type RewriteAfterFoundFn = ( + node: T, + nodeType: arkts.Es2pandaAstNodeType +) => T; + +export function findAndCollectMemoableNode(node: arkts.AstNode, rewriteFn?: RewriteAfterFoundFn): arkts.AstNode { const type = arkts.nodeType(node); if (collectByType.has(type)) { - return collectByType.get(type)!(node); + return collectByType.get(type)!(node, rewriteFn); } return node; } export class factory { - static findAndCollectMemoableProperty(node: arkts.Property): arkts.Property { + /** + * Find and collect possible `@memo` property with arrow function value. + * + * @param node `arkts.Property` node + * @param rewriteFn function callback to rewrite node when it is `@memo` property + * @returns `arkts.Property` node + */ + static findAndCollectMemoableProperty( + node: T, + rewriteFn?: RewriteAfterFoundFn + ): T { + let found: boolean = false; if (findCanAddMemoFromProperty(node)) { + found = true; addMemoAnnotation(node.value! as arkts.ArrowFunctionExpression); } + if (found && !!rewriteFn) { + return rewriteFn(node, arkts.Es2pandaAstNodeType.AST_NODE_TYPE_PROPERTY); + } return node; } - static findAndCollectMemoableClassProperty(node: arkts.ClassProperty): arkts.ClassProperty { + /** + * Find and collect possible `@memo` class property with arrow function value. + * + * @param node `arkts.ClassProperty` node + * @param rewriteFn function callback to rewrite node when it is `@memo` class property + * @returns `arkts.ClassProperty` node + */ + static findAndCollectMemoableClassProperty( + node: T, + rewriteFn?: RewriteAfterFoundFn + ): T { + let found: boolean = false; if (findCanAddMemoFromClassProperty(node)) { + found = true; addMemoAnnotation(node); } + if (found && !!rewriteFn) { + return rewriteFn(node, arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_PROPERTY); + } return node; } - static findAndCollectMemoableTypeAlias(node: arkts.TSTypeAliasDeclaration): arkts.TSTypeAliasDeclaration { + /** + * Find and collect possible `@memo` type alias with function type. + * + * @param node `arkts.TSTypeAliasDeclaration` node + * @param rewriteFn function callback to rewrite node when it is `@memo` type alias + * @returns `arkts.TSTypeAliasDeclaration` node + */ + static findAndCollectMemoableTypeAlias( + node: T, + rewriteFn?: RewriteAfterFoundFn + ): T { + let found: boolean = false; if (findCanAddMemoFromTypeAlias(node)) { + found = true; addMemoAnnotation(node); } + if (found && !!rewriteFn) { + return rewriteFn(node, arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_ALIAS_DECLARATION); + } return node; } - static findAndCollectMemoableParameter(node: arkts.ETSParameterExpression): arkts.ETSParameterExpression { + /** + * Find and collect possible `@memo` parameter with function type. + * + * @param node `arkts.ETSParameterExpression` node + * @param rewriteFn function callback to rewrite node when it is `@memo` parameter + * @returns `arkts.ETSParameterExpression` node + */ + static findAndCollectMemoableParameter( + node: T, + rewriteFn?: RewriteAfterFoundFn + ): T { + let found: boolean = false; if (findCanAddMemoFromParameter(node)) { + found = true; addMemoAnnotation(node); } + if (found && !!rewriteFn) { + return rewriteFn(node, arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PARAMETER_EXPRESSION); + } return node; } - static findAndCollectMemoableMethod(node: arkts.MethodDefinition): arkts.MethodDefinition { + /** + * Find and collect possible `@memo` method. + * + * @param node `arkts.MethodDefinition` node + * @param rewriteFn function callback to rewrite node when it is `@memo` method + * @returns `arkts.MethodDefinition` node + */ + static findAndCollectMemoableMethod( + node: T, + rewriteFn?: RewriteAfterFoundFn + ): T { + let found: boolean = false; if (findCanAddMemoFromMethod(node)) { + found = true; addMemoAnnotation(node.scriptFunction); } + if (found && !!rewriteFn) { + return rewriteFn(node, arkts.Es2pandaAstNodeType.AST_NODE_TYPE_METHOD_DEFINITION); + } return node; } - static findAndCollectMemoableArrowFunction(node: arkts.ArrowFunctionExpression): arkts.ArrowFunctionExpression { + /** + * Find and collect possible `@memo` arrow function. + * + * @param node `arkts.ArrowFunctionExpression` node + * @param rewriteFn function callback to rewrite node when it is `@memo` arrow function + * @returns `arkts.ArrowFunctionExpression` node + */ + static findAndCollectMemoableArrowFunction( + node: T, + rewriteFn?: RewriteAfterFoundFn + ): T { + let found: boolean = false; if (findCanAddMemoFromArrowFunction(node)) { + found = true; addMemoAnnotation(node.scriptFunction); } + if (found && !!rewriteFn) { + return rewriteFn(node, arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ARROW_FUNCTION_EXPRESSION); + } return node; } - static findAndCollectMemoableCallExpression(node: arkts.CallExpression): arkts.CallExpression { - collectMemoFromCallExpression(node); + /** + * Find and collect possible `@memo` function call. + * + * @param node `arkts.CallExpression` node + * @returns `arkts.CallExpression` node + */ + static findAndCollectMemoableCallExpression( + node: T, + rewriteFn?: RewriteAfterFoundFn + ): T { + const _node = coerceToAstNode(node); + collectMemoFromCallExpression(_node); return node; } - static findAndCollectMemoableNewClass( - node: arkts.ETSNewClassInstanceExpression - ): arkts.ETSNewClassInstanceExpression { - collectMemoFromNewClass(node); + /** + * Find and collect new class instance with possible `@memo` type parameters. + * + * @param node `arkts.ETSNewClassInstanceExpression` node + * @returns `arkts.ETSNewClassInstanceExpression` node + */ + static findAndCollectMemoableNewClass( + node: T, + rewriteFn?: RewriteAfterFoundFn + ): T { + const _node = coerceToAstNode(node); + collectMemoFromNewClass(_node); return node; } } -type CollectFactoryFn = (node: any) => arkts.AstNode; +type CollectFactoryFn = (node: T, rewriteFn?: RewriteAfterFoundFn) => T; const collectByType = new Map([ [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_PROPERTY, factory.findAndCollectMemoableProperty], diff --git a/arkui-plugins/collectors/memo-collectors/utils.ts b/arkui-plugins/collectors/memo-collectors/utils.ts index 61fa700cba5f7060397b1db2a520f07b0912ae74..61eaeda417de846e730b8aa6f7d610dac530fa4e 100644 --- a/arkui-plugins/collectors/memo-collectors/utils.ts +++ b/arkui-plugins/collectors/memo-collectors/utils.ts @@ -308,25 +308,19 @@ export function collectMemoableInfoInProperty(node: arkts.AstNode, info?: Memoab return currInfo; } const decl = arkts.getDecl(node.key); - if (!decl || !arkts.isMethodDefinition(decl)) { + if (!decl) { return currInfo; } - const hasReceiver = decl.scriptFunction.hasReceiver; - const isSetter = decl.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; - const isGetter = decl.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; - let newInfo: MemoableInfo = {}; - if (isSetter && decl.scriptFunction.params.length > 0) { - if (hasReceiver && decl.scriptFunction.params.length === 2) { - newInfo = collectMemoableInfoInParameter(decl.scriptFunction.params.at(1)!); - } else { - newInfo = collectMemoableInfoInParameter(decl.scriptFunction.params.at(0)!); - } - } else if (isGetter) { - newInfo = collectMemoableInfoInFunctionReturnType(decl.scriptFunction); + if (arkts.isMethodDefinition(decl)) { + const newInfo = collectMemoableInfoInMethod(decl); + currInfo = { ...currInfo, ...newInfo }; + } else if (arkts.isClassProperty(decl)) { + const newInfo = collectMemoableInfoInClassProperty(decl); + currInfo = { ...currInfo, ...newInfo }; } - currInfo = { ...currInfo, ...collectMemoableInfoInScriptFunction(decl.scriptFunction), ...newInfo }; currInfo.hasProperType = false; if (!!node.value && arkts.isArrowFunctionExpression(node.value)) { + currInfo.hasProperType = true; currInfo = { ...currInfo, ...collectMemoableInfoInScriptFunction(node.value.scriptFunction), @@ -578,7 +572,8 @@ export function findCanAddMemoFromProperty(property: arkts.AstNode): property is if (!!memoableInfo.hasMemo && !!memoableInfo.hasProperType) { arkts.NodeCache.getInstance().collect(property); } - return !!memoableInfo.hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; + const hasBuilder = !!memoableInfo.hasBuilder || !!memoableInfo.hasBuilderParam; + return hasBuilder && !memoableInfo.hasMemo && !!memoableInfo.hasProperType; } /** diff --git a/arkui-plugins/common/arkts-utils.ts b/arkui-plugins/common/arkts-utils.ts index 1b01d924856e72a453897a32d3fbf7dbefd5098d..ff2e195a86da1239072344f8e94926b48c75c1dd 100644 --- a/arkui-plugins/common/arkts-utils.ts +++ b/arkui-plugins/common/arkts-utils.ts @@ -17,6 +17,10 @@ import * as arkts from '@koalaui/libarkts'; import { DeclarationCollector } from './declaration-collector'; import { ARKUI_IMPORT_PREFIX_NAMES, DecoratorNames } from './predefines'; +export function coerceToAstNode(node: arkts.AstNode): T { + return node as T; +} + /** * create and insert `import { as } from ` to the top of script's statements. */ diff --git a/arkui-plugins/common/safe-types.ts b/arkui-plugins/common/safe-types.ts index 899ef0c39b3dccec8dbbbe1fb68d6243a009dcc2..87345cf196a2952101a3b802924e95b6d3991503 100644 --- a/arkui-plugins/common/safe-types.ts +++ b/arkui-plugins/common/safe-types.ts @@ -40,4 +40,6 @@ 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']; \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/builder-lambda/condition-scope/with-builder.ets b/arkui-plugins/test/demo/mock/builder-lambda/condition-scope/with-builder.ets new file mode 100644 index 0000000000000000000000000000000000000000..c2a8eaab837dcb47aafa6cf935d2971177b2e63a --- /dev/null +++ b/arkui-plugins/test/demo/mock/builder-lambda/condition-scope/with-builder.ets @@ -0,0 +1,76 @@ +/* + * 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 { Text, Column, Component, Builder, BuilderParam, WrappedBuilder, wrapBuilder } from "@ohos.arkui.component" + +@Builder +function MyBuilder(): void { + if (true) { + Text('within Builder function'); + } +} + +@Builder +function ParamBuilder(@Builder param: (() => void) = () => { + if (true) { + Text('within Builder parameter') + } +}): void { + param(); +} + +const wBuilder = wrapBuilder(ParamBuilder); + +@Component +struct MyStruct { + @Builder + public myBuilderMethod() { + if (true) { + Text('within Builder method'); + } + } + + build() { + Column() { + wBuilder.builder(@Builder () => { + if (true) { + Text('with Builder lambda'); + } + }); + Child({ + myBuilderParam: () => { + if (true) { + Text('within Builder property') + } + this.myBuilderMethod(); + } + }) + } + } +} + +@Component +struct Child { + @BuilderParam myBuilderParam: () => void = () => { + if (true) { + Text('within BuilderParam property'); + } + } + build() { + if (true) { + Text('within struct build'); + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/ut/ui-plugins/builder-lambda/condition-scope/with-builder.test.ts b/arkui-plugins/test/ut/ui-plugins/builder-lambda/condition-scope/with-builder.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9118dc865d253ca53d279b8a22ba80a198be2ad6 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/builder-lambda/condition-scope/with-builder.test.ts @@ -0,0 +1,496 @@ +/* + * 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 BUILDER_LAMBDA_DIR_PATH: string = 'builder-lambda/condition-scope'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, BUILDER_LAMBDA_DIR_PATH, 'with-builder.ets'), +]; + +const pluginTester = new PluginTester('test conditionScope within @Builder or @BuilderParam', buildConfig); + +const parsedTransform: Plugins = { + name: 'with-builder', + parsed: uiTransform().parsed, +}; + +const expectedUIScript: string = ` +import { ConditionScope as ConditionScope } from \"arkui.component.builder\"; +import { ConditionBranch as ConditionBranch } from \"arkui.component.builder\"; +import { memo as memo } from \"arkui.stateManagement.runtime\"; +import { CustomComponent as CustomComponent } from \"arkui.component.customComponent\"; +import { Text as Text, Column as Column, Component as Component, Builder as Builder, BuilderParam as BuilderParam, WrappedBuilder as WrappedBuilder, wrapBuilder as wrapBuilder } from \"@ohos.arkui.component\"; +const wBuilder = wrapBuilder(ParamBuilder); +function main() {} +@memo() function MyBuilder(): void { + ConditionScope(@memo() (() => { + if (true) { + ConditionBranch(@memo() (() => { + Text(undefined, \"within Builder function\", undefined, undefined); + })); + } + })); +} +@memo() function ParamBuilder(@Builder() @memo() gensym%%_?: (()=> void)): void { + let param: (()=> void) = (((gensym%%_) !== (undefined)) ? gensym%%_ : ((() => { + ConditionScope(@memo() (() => { + if (true) { + ConditionBranch(@memo() (() => { + Text(undefined, \"within Builder parameter\", undefined, undefined); + })); + } + })); + }) as (()=> void))); + param(); +} +@Component() final struct MyStruct extends CustomComponent { + public __initializeStruct(initializers: (__Options_MyStruct | undefined), @memo() content: ((()=> void) | undefined)): void {} + public __updateStruct(initializers: (__Options_MyStruct | undefined)): void {} + @memo() public myBuilderMethod() { + ConditionScope(@memo() (() => { + if (true) { + ConditionBranch(@memo() (() => { + Text(undefined, \"within Builder method\", undefined, undefined); + })); + } + })); + } + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + wBuilder.builder(@Builder() (() => { + ConditionScope(@memo() (() => { + if (true) { + ConditionBranch(@memo() (() => { + Text(undefined, \"with Builder lambda\", undefined, undefined); + })); + } + })); + })); + Child._instantiateImpl(undefined, (() => { + return new Child(); + }), { + myBuilderParam: @memo() (() => { + ConditionScope(@memo() (() => { + if (true) { + ConditionBranch(@memo() (() => { + Text(undefined, \"within Builder property\", undefined, undefined); + })); + } + })); + this.myBuilderMethod(); + }), + }, undefined, undefined); + })); + } + private constructor() {} +} +@Component() final struct Child extends CustomComponent { + public __initializeStruct(initializers: (__Options_Child | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_myBuilderParam = ((((({let gensym___ = initializers; + (((gensym___) == (null)) ? undefined : gensym___.myBuilderParam)})) ?? (content))) ?? ((() => { + ConditionScope(@memo() (() => { + if (true) { + ConditionBranch(@memo() (() => { + Text(undefined, \"within BuilderParam property\", undefined, undefined); + })); + } + })); + }))) + } + public __updateStruct(initializers: (__Options_Child | undefined)): void {} + private __backing_myBuilderParam?: @memo() (()=> void); + public get myBuilderParam(): @memo() (()=> void) { + return this.__backing_myBuilderParam!; + } + public set myBuilderParam(value: @memo() (()=> void)) { + this.__backing_myBuilderParam = value; + } + @memo() public build() { + ConditionScope(@memo() (() => { + if (true) { + ConditionBranch(@memo() (() => { + Text(undefined, "within struct build", undefined, undefined); + })); + } + })); + } + private constructor() {} +} +@Component() export interface __Options_MyStruct { +} +@Component() export interface __Options_Child { + set myBuilderParam(myBuilderParam: (@memo() (()=> void) | undefined)) + get myBuilderParam(): (@memo() (()=> void) | undefined) +} +`; + +function testUITransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedUIScript)); +} + +const expectedMemoScript: string = ` +import { __memo_context_type as __memo_context_type, __memo_id_type as __memo_id_type } from \"arkui.stateManagement.runtime\"; +import { ConditionScope as ConditionScope } from \"arkui.component.builder\"; +import { ConditionBranch as ConditionBranch } from \"arkui.component.builder\"; +import { memo as memo } from \"arkui.stateManagement.runtime\"; +import { CustomComponent as CustomComponent } from \"arkui.component.customComponent\"; +import { Text as Text, Column as Column, Component as Component, Builder as Builder, BuilderParam as BuilderParam, WrappedBuilder as WrappedBuilder, wrapBuilder as wrapBuilder } from \"@ohos.arkui.component\"; +const wBuilder = wrapBuilder(ParamBuilder); +function main() {} +@memo() function MyBuilder(__memo_context: __memo_context_type, __memo_id: __memo_id_type): void { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + ConditionScope(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + if (true) { + ConditionBranch(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + Text(__memo_context, ((__memo_id) + ()), undefined, \"within Builder function\", undefined, undefined); + { + __memo_scope.recache(); + return; + } + })); + } + { + __memo_scope.recache(); + return; + } + })); + { + __memo_scope.recache(); + return; + } +} +@memo() function ParamBuilder(__memo_context: __memo_context_type, __memo_id: __memo_id_type, @Builder() @memo() gensym%%_?: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)): void { + let param: ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) = (((gensym%%_) !== (undefined)) ? gensym%%_ : (((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + ConditionScope(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + if (true) { + ConditionBranch(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + Text(__memo_context, ((__memo_id) + ()), undefined, \"within Builder parameter\", undefined, undefined); + { + __memo_scope.recache(); + return; + } + })); + } + { + __memo_scope.recache(); + return; + } + })); + { + __memo_scope.recache(); + return; + } + }) as ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void))); + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 1); + const __memo_parameter_param = __memo_scope.param(0, param); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + __memo_parameter_param.value(__memo_context, ((__memo_id) + ())); + { + __memo_scope.recache(); + return; + } +} +@Component() final struct MyStruct extends CustomComponent { + public __initializeStruct(initializers: (__Options_MyStruct | undefined), @memo() content: (((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined)): void {} + public __updateStruct(initializers: (__Options_MyStruct | undefined)): void {} + @memo() public myBuilderMethod(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + ConditionScope(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + if (true) { + ConditionBranch(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + Text(__memo_context, ((__memo_id) + ()), undefined, \"within Builder method\", undefined, undefined); + { + __memo_scope.recache(); + return; + } + })); + } + { + __memo_scope.recache(); + return; + } + })); + { + __memo_scope.recache(); + return; + } + } + @memo() public build(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + Column(__memo_context, ((__memo_id) + ()), undefined, undefined, @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + wBuilder.builder(__memo_context, ((__memo_id) + ()), @Builder() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + ConditionScope(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + if (true) { + ConditionBranch(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + Text(__memo_context, ((__memo_id) + ()), undefined, \"with Builder lambda\", undefined, undefined); + { + __memo_scope.recache(); + return; + } + })); + } + { + __memo_scope.recache(); + return; + } + })); + { + __memo_scope.recache(); + return; + } + })); + Child._instantiateImpl(__memo_context, ((__memo_id) + ()), undefined, (() => { + return new Child(); + }), { + myBuilderParam: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + ConditionScope(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + if (true) { + ConditionBranch(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + Text(__memo_context, ((__memo_id) + ()), undefined, \"within Builder property\", undefined, undefined); + { + __memo_scope.recache(); + return; + } + })); + } + { + __memo_scope.recache(); + return; + } + })); + this.myBuilderMethod(__memo_context, ((__memo_id) + ())); + { + __memo_scope.recache(); + return; + } + }), + }, undefined, undefined); + { + __memo_scope.recache(); + return; + } + })); + { + __memo_scope.recache(); + return; + } + } + private constructor() {} +} +@Component() final struct Child extends CustomComponent { + public __initializeStruct(initializers: (__Options_Child | undefined), @memo() content: (((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined)): void { + this.__backing_myBuilderParam = ((((({let gensym___ = initializers; + (((gensym___) == (null)) ? undefined : gensym___.myBuilderParam)})) ?? (content))) ?? (((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + ConditionScope(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + if (true) { + ConditionBranch(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + Text(__memo_context, ((__memo_id) + ()), undefined, \"within BuilderParam property\", undefined, undefined); + { + __memo_scope.recache(); + return; + } + })); + } + { + __memo_scope.recache(); + return; + } + })); + { + __memo_scope.recache(); + return; + } + }))) + } + public __updateStruct(initializers: (__Options_Child | undefined)): void {} + private __backing_myBuilderParam?: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void); + public get myBuilderParam(): @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) { + return this.__backing_myBuilderParam!; + } + public set myBuilderParam(value: @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void)) { + this.__backing_myBuilderParam = value; + } + @memo() public build(__memo_context: __memo_context_type, __memo_id: __memo_id_type) { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + ConditionScope(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + if (true) { + ConditionBranch(__memo_context, ((__memo_id) + ()), @memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type) => { + const __memo_scope = __memo_context.scope(((__memo_id) + ()), 0); + if (__memo_scope.unchanged) { + __memo_scope.cached; + return; + } + Text(__memo_context, ((__memo_id) + ()), undefined, "within struct build", undefined, undefined); + { + __memo_scope.recache(); + return; + } + })); + } + { + __memo_scope.recache(); + return; + } + })); + { + __memo_scope.recache(); + return; + } + } + private constructor() {} +} +@Component() export interface __Options_MyStruct { +} +@Component() export interface __Options_Child { + set myBuilderParam(myBuilderParam: (@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined)) + get myBuilderParam(): (@memo() ((__memo_context: __memo_context_type, __memo_id: __memo_id_type)=> void) | undefined) +} +`; + +function testMemoTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedMemoScript)); +} + +pluginTester.run( + 'test conditionScope within @Builder or @BuilderParam', + [parsedTransform, uiNoRecheck, memoNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testUITransformer], + 'checked:memo-no-recheck': [testMemoTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/builder-factory.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/builder-factory.ts new file mode 100644 index 0000000000000000000000000000000000000000..a30518a7e35dce2c6e43d84a5b9c1b214fb34eb7 --- /dev/null +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/builder-factory.ts @@ -0,0 +1,118 @@ +/* + * 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 { ConditionScopeVisitor } from './condition-scope-visitor'; +import { coerceToAstNode } from '../../common/arkts-utils'; +import { factory as UIFactory } from '../ui-factory'; + +export class BuilderFactory { + /** + * rewrite `@Builder` function body with `ConditionScopeVisitor`. + * + * @internal + */ + static rewriteBuilderScriptFunction(node: T): arkts.ScriptFunction { + const _node = coerceToAstNode(node); + let funcBody: arkts.AstNode | undefined = _node.body; + if (!funcBody || !arkts.isBlockStatement(funcBody)) { + return _node; + } + const conditionScopeVisitor = ConditionScopeVisitor.getInstance(); + funcBody = arkts.factory.updateBlock( + funcBody, + funcBody.statements.map((st) => conditionScopeVisitor.visitor(st)) + ); + return UIFactory.updateScriptFunction(_node, { body: funcBody }); + } + + /** + * rewrite `@Builder` method. + */ + static rewriteBuilderMethod(node: T): arkts.MethodDefinition { + const _node = coerceToAstNode(node); + const newFunc = BuilderFactory.rewriteBuilderScriptFunction(_node.scriptFunction); + return arkts.factory.updateMethodDefinition(_node, _node.kind, _node.name, newFunc, _node.modifiers, false); + } + + /** + * rewrite `@Builder` class property with arrow function value. + */ + static rewirteBuilderClassProperty(node: T): arkts.ClassProperty { + const _node = coerceToAstNode(node); + const value = _node.value; + if (!value || !arkts.isArrowFunctionExpression(value)) { + return _node; + } + const newValue = BuilderFactory.rewriteBuilderArrowFunction(value); + return arkts.factory.updateClassProperty( + _node, + _node.key, + newValue, + _node.typeAnnotation, + _node.modifiers, + false + ); + } + + /** + * rewrite `@Builder` property with arrow function value. + */ + static rewriteBuilderProperty(node: T): arkts.Property { + const _node = coerceToAstNode(node); + const value = _node.value; + if (!value || !arkts.isArrowFunctionExpression(value)) { + return _node; + } + const newValue = BuilderFactory.rewriteBuilderArrowFunction(value); + return arkts.factory.updateProperty(_node, _node.key, newValue); + } + + /** + * rewrite `@Builder` arrow function. + */ + static rewriteBuilderArrowFunction( + node: T + ): arkts.ArrowFunctionExpression { + const _node = coerceToAstNode(node); + const newFunc = BuilderFactory.rewriteBuilderScriptFunction(_node.scriptFunction); + return arkts.factory.updateArrowFunction(_node, newFunc); + } + + /** + * rewrite `@Builder` parameter with arrow function value. + */ + static rewriteBuilderParameter( + node: T + ): arkts.ETSParameterExpression { + const _node = coerceToAstNode(node); + const initializer = _node.initializer; + if (!initializer || !arkts.isArrowFunctionExpression(initializer)) { + return _node; + } + const newInitializer = BuilderFactory.rewriteBuilderArrowFunction(initializer); + return arkts.factory.updateParameterDeclaration(_node, _node.identifier, newInitializer); + } +} + +export type BuilderRewriteFn = (node: T) => T; + +export const builderRewriteByType = new Map>([ + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_METHOD_DEFINITION, BuilderFactory.rewriteBuilderMethod], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_CLASS_PROPERTY, BuilderFactory.rewirteBuilderClassProperty], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_PROPERTY, BuilderFactory.rewriteBuilderProperty], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ARROW_FUNCTION_EXPRESSION, BuilderFactory.rewriteBuilderArrowFunction], + [arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_PARAMETER_EXPRESSION, BuilderFactory.rewriteBuilderParameter], +]); diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/condition-scope-visitor.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/condition-scope-visitor.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8d9937c0fbe913966cef155af9bfe15ee6c9437 --- /dev/null +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/condition-scope-visitor.ts @@ -0,0 +1,55 @@ +/* + * 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 { factory as BuilderLambdaFactory } from './factory'; + +/** + * `ConditionScopeVisitor` is used to visit `@Builder` function body to wrap `ConditionScope`/`ConditionBranch` + * to if-else or switch-case statements. + * + * @internal + */ +export class ConditionScopeVisitor extends AbstractVisitor { + private static instance: ConditionScopeVisitor; + + private constructor() { + super(); + } + + static getInstance(): ConditionScopeVisitor { + if (!this.instance) { + this.instance = new ConditionScopeVisitor(); + } + return this.instance; + } + + visitor(node: arkts.AstNode): arkts.AstNode { + if (arkts.isIfStatement(node)) { + return BuilderLambdaFactory.updateIfElseContentBodyInBuilderLambda(node, true, true); + } + if (arkts.isSwitchStatement(node)) { + return BuilderLambdaFactory.updateSwitchCaseContentBodyInBuilderLambda(node, true, true); + } + if (arkts.isBlockStatement(node)) { + const newStatements = node.statements.map((st) => + BuilderLambdaFactory.updateContentBodyInBuilderLambda(st, true, true) + ); + return arkts.factory.updateBlock(node, newStatements); + } + return this.visitEachChild(node); + } +} diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts index 9be67b1ddf165a1a545f048ca39f4c462c333a4c..a09b461082df0997d4aba0115c5a222dc2775ac3 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts @@ -62,6 +62,7 @@ import { import { ImportCollector } from '../../common/import-collector'; import { addMemoAnnotation, collectMemoableInfoInParameter } from '../../collectors/memo-collectors/utils'; import { factory as MemoCollectFactory } from '../../collectors/memo-collectors/factory'; +import { BuilderFactory } from './builder-factory'; export class factory { /** @@ -310,8 +311,12 @@ export class factory { return arg; } const properties = (expr.properties as arkts.Property[]).map((p) => { - MemoCollectFactory.findAndCollectMemoableProperty(p); - return factory.updatePropertiesInOptions(p); + let property = p; + MemoCollectFactory.findAndCollectMemoableProperty(p, (currNode: arkts.Property) => { + property = BuilderFactory.rewriteBuilderProperty(currNode); + return property; + }); + return factory.updatePropertiesInOptions(property); }); const updatedExpr: arkts.ObjectExpression = arkts.ObjectExpression.updateObjectExpression( expr, @@ -439,19 +444,31 @@ export class factory { /** * update if-else in a builder lambda call's arguments. */ - static updateIfElseContentBodyInBuilderLambda(statement: arkts.AstNode, shouldWrap?: boolean): arkts.AstNode { + static updateIfElseContentBodyInBuilderLambda( + statement: arkts.AstNode, + shouldWrap?: boolean, + stopAtBuilderLambda?: boolean + ): arkts.AstNode { if (arkts.isIfStatement(statement)) { const alternate = !!statement.alternate - ? this.updateIfElseContentBodyInBuilderLambda(statement.alternate, shouldWrap) + ? this.updateIfElseContentBodyInBuilderLambda(statement.alternate, shouldWrap, stopAtBuilderLambda) : statement.alternate; - const consequence = this.updateIfElseContentBodyInBuilderLambda(statement.consequent, shouldWrap); + const consequence = this.updateIfElseContentBodyInBuilderLambda( + statement.consequent, + shouldWrap, + stopAtBuilderLambda + ); const newStatement = arkts.factory.updateIfStatement(statement, statement.test, consequence!, alternate); return !shouldWrap || checkIsWithInIfConditionScope(statement) ? newStatement : this.wrapConditionToBlock([newStatement], ConditionNames.CONDITION_SCOPE); } if (arkts.isBlockStatement(statement)) { - let { statements, breakIndex } = this.updateConditionBranchInScope(statement.statements, shouldWrap); + let { statements, breakIndex } = this.updateConditionBranchInScope( + statement.statements, + shouldWrap, + stopAtBuilderLambda + ); if (!!shouldWrap && checkIsWithInIfConditionScope(statement)) { const beforeBreak = this.wrapConditionToBlock( breakIndex > 0 ? statements.slice(0, breakIndex) : statements, @@ -470,17 +487,24 @@ export class factory { */ static updateSwitchCaseContentBodyInBuilderLambda( statement: T, - shouldWrap?: boolean + shouldWrap?: boolean, + stopAtBuilderLambda?: boolean ): T { if (arkts.isSwitchStatement(statement)) { - const cases = statement.cases.map((c) => this.updateSwitchCaseContentBodyInBuilderLambda(c, shouldWrap)); + const cases = statement.cases.map((c) => + this.updateSwitchCaseContentBodyInBuilderLambda(c, shouldWrap, stopAtBuilderLambda) + ); const newStatement = arkts.factory.updateSwitchStatement(statement, statement.discriminant, cases); return (!shouldWrap ? newStatement : this.wrapConditionToBlock([newStatement], ConditionNames.CONDITION_SCOPE)) as arkts.AstNode as T; } if (arkts.isSwitchCaseStatement(statement)) { - let { statements, breakIndex } = this.updateConditionBranchInScope(statement.consequent, shouldWrap); + let { statements, breakIndex } = this.updateConditionBranchInScope( + statement.consequent, + shouldWrap, + stopAtBuilderLambda + ); if (shouldWrap) { const beforeBreak = this.wrapConditionToBlock( breakIndex > 0 ? statements.slice(0, breakIndex) : statements, @@ -500,14 +524,15 @@ export class factory { */ static updateConditionBranchInScope( statements: readonly arkts.Statement[], - shouldWrap?: boolean + shouldWrap?: boolean, + stopAtBuilderLambda?: boolean ): BuilderLambdaConditionBranchInfo { let breakIndex = statements.length; const newStatements = statements.map((st, index) => { if (checkShouldBreakFromStatement(st)) { breakIndex = index; } - return this.updateContentBodyInBuilderLambda(st, shouldWrap); + return this.updateContentBodyInBuilderLambda(st, shouldWrap, stopAtBuilderLambda); }); return { statements: newStatements, breakIndex }; } @@ -536,25 +561,34 @@ export class factory { /** * update trailing lambda contents in a builder lambda call. */ - static updateContentBodyInBuilderLambda(statement: arkts.Statement, hasBuilder?: boolean): arkts.Statement { + static updateContentBodyInBuilderLambda( + statement: arkts.Statement, + hasBuilder?: boolean, + stopAtBuilderLambda?: boolean + ): arkts.Statement { if ( arkts.isExpressionStatement(statement) && arkts.isCallExpression(statement.expression) && isBuilderLambda(statement.expression) ) { + if (!!stopAtBuilderLambda) { + return statement; + } return arkts.factory.updateExpressionStatement( statement, this.transformBuilderLambda(statement.expression) ); } if (arkts.isIfStatement(statement)) { - return this.updateIfElseContentBodyInBuilderLambda(statement, hasBuilder); + return this.updateIfElseContentBodyInBuilderLambda(statement, hasBuilder, stopAtBuilderLambda); } if (arkts.isSwitchStatement(statement)) { - return this.updateSwitchCaseContentBodyInBuilderLambda(statement, hasBuilder); + return this.updateSwitchCaseContentBodyInBuilderLambda(statement, hasBuilder, stopAtBuilderLambda); } if (arkts.isBlockStatement(statement)) { - const newStatements = statement.statements.map((st) => this.updateContentBodyInBuilderLambda(st, hasBuilder)); + const newStatements = statement.statements.map((st) => + this.updateContentBodyInBuilderLambda(st, hasBuilder, stopAtBuilderLambda) + ); return arkts.factory.updateBlock(statement, newStatements); } return statement; diff --git a/arkui-plugins/ui-plugins/checked-transformer.ts b/arkui-plugins/ui-plugins/checked-transformer.ts index 8fe41f30c05dbeb4d2cc58ef523cc2f0673d8243..016dbd524589838a90512847d141c2bef8563d9e 100644 --- a/arkui-plugins/ui-plugins/checked-transformer.ts +++ b/arkui-plugins/ui-plugins/checked-transformer.ts @@ -32,7 +32,7 @@ import { LoaderJson, ResourceInfo, ScopeInfoCollection, - isForEachDecl + isForEachDecl, } from './struct-translators/utils'; import { collectCustomComponentScopeInfo, @@ -44,6 +44,7 @@ import { import { findAndCollectMemoableNode } from '../collectors/memo-collectors/factory'; import { InteroperAbilityNames } from './interop/predefines'; import { generateBuilderCompatible } from './interop/builder-interop'; +import { builderRewriteByType } from './builder-lambda-translators/builder-factory'; export class CheckedTransformer extends AbstractVisitor { private scope: ScopeInfoCollection; @@ -127,10 +128,10 @@ export class CheckedTransformer extends AbstractVisitor { let isFrom1_1 = false; if (arkts.isMethodDefinition(decl)) { const annotations = decl.scriptFunction.annotations; - const decorators: string[] = annotations.map(annotation => { + const decorators: string[] = annotations.map((annotation) => { return (annotation.expr as arkts.Identifier).name; }); - decorators.forEach(element => { + decorators.forEach((element) => { if (element === 'Builder') { isFrom1_1 = true; return; @@ -164,8 +165,13 @@ export class CheckedTransformer extends AbstractVisitor { ); return this.visitEachChild(lambda); } - const node = this.visitEachChild(beforeChildren); - findAndCollectMemoableNode(node); + let node = this.visitEachChild(beforeChildren); + findAndCollectMemoableNode(node, (currNode: arkts.AstNode, nodeType: arkts.Es2pandaAstNodeType) => { + if (builderRewriteByType.has(nodeType)) { + node = builderRewriteByType.get(nodeType)!(currNode); + } + return currNode; + }); if ( arkts.isClassDeclaration(node) && this.scope.customComponents.length > 0 && @@ -192,7 +198,10 @@ export class CheckedTransformer extends AbstractVisitor { return structFactory.AddArrowTypeForParameter(node); } else if (arkts.isTSInterfaceDeclaration(node)) { return structFactory.tranformInterfaceMembers(node, this.externalSourceName); - } else if (arkts.isETSNewClassInstanceExpression(node) && isSpecificNewClass(node, CustomDialogNames.CUSTOM_DIALOG_CONTROLLER)) { + } else if ( + arkts.isETSNewClassInstanceExpression(node) && + isSpecificNewClass(node, CustomDialogNames.CUSTOM_DIALOG_CONTROLLER) + ) { return structFactory.transformCustomDialogController(node); } diff --git a/arkui-plugins/ui-plugins/property-translators/builderParam.ts b/arkui-plugins/ui-plugins/property-translators/builderParam.ts index 41c616f607c7c69f403d5c3db8862a4f7a5e67b8..242ffbd64d7702e5f6982d070e455549c12e4f0c 100644 --- a/arkui-plugins/ui-plugins/property-translators/builderParam.ts +++ b/arkui-plugins/ui-plugins/property-translators/builderParam.ts @@ -83,6 +83,10 @@ export class BuilderParamTranslator extends PropertyTranslator implements Initia } generateInitializeStruct(mutableThis: arkts.Expression, originalName: string): arkts.AstNode { + const value = this.property.value; + if (!!value && arkts.isArrowFunctionExpression(value)) { + arkts.NodeCache.getInstance().collect(value); + } return arkts.factory.createAssignmentExpression( mutableThis, arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, @@ -95,7 +99,7 @@ export class BuilderParamTranslator extends PropertyTranslator implements Initia arkts.factory.createIdentifier('content'), arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING ), - this.property.value ?? arkts.factory.createUndefinedLiteral(), + value ?? arkts.factory.createUndefinedLiteral(), arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING ) ); diff --git a/arkui-plugins/ui-plugins/struct-translators/factory.ts b/arkui-plugins/ui-plugins/struct-translators/factory.ts index d4fd4e5d1d88d7b00caed14fdf65a0e4da916e03..201db5c27fd677c7463339f84dc843123b677d9b 100644 --- a/arkui-plugins/ui-plugins/struct-translators/factory.ts +++ b/arkui-plugins/ui-plugins/struct-translators/factory.ts @@ -29,6 +29,7 @@ import { 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 { BuilderFactory } from '../builder-lambda-translators/builder-factory'; import { backingField, collect, filterDefined } from '../../common/arkts-utils'; import { classifyInObservedClass, @@ -479,6 +480,7 @@ export class factory { } if (isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_BUILD_ORI)) { addMemoAnnotation(member.scriptFunction); + return BuilderFactory.rewriteBuilderMethod(member); } return member; }