diff --git a/arkui-plugins/test/demo/mock/builder-lambda/condition-scope/non-builder-within-builder.ets b/arkui-plugins/test/demo/mock/builder-lambda/condition-scope/non-builder-within-builder.ets new file mode 100644 index 0000000000000000000000000000000000000000..2579f1ba9e259be8438bf050f30ae3071aabfa7f --- /dev/null +++ b/arkui-plugins/test/demo/mock/builder-lambda/condition-scope/non-builder-within-builder.ets @@ -0,0 +1,43 @@ +/* + * 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, Builder } from "@ohos.arkui.component" + +@Builder +function TestComponent( + init: TestInitCallback, + update: TestUpdateCallback, +): void {} + +type TestInitCallback = () => void; +type TestUpdateCallback = () => void; + +@Component +struct MyStateSample { + build() { + TestComponent( + () => { + if (true) { + + } + }, + () => { + if (false) { + + } + } + ); + } +} diff --git a/arkui-plugins/test/ut/ui-plugins/builder-lambda/condition-scope/non-builder-within-builder.test.ts b/arkui-plugins/test/ut/ui-plugins/builder-lambda/condition-scope/non-builder-within-builder.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..7bfebe64eb48474a8c925462a3d4fbce25ef49a7 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/builder-lambda/condition-scope/non-builder-within-builder.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { 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, 'non-builder-within-builder.ets'), +]; + +const pluginTester = new PluginTester( + 'test no conditionScope in non-@Builder function within @Builder function', + buildConfig +); + +const parsedTransform: Plugins = { + name: 'with-no-condition', + parsed: uiTransform().parsed, +}; + +const expectedUIScript: string = ` +import { memo as memo } from \"arkui.stateManagement.runtime\"; +import { CustomComponent as CustomComponent } from \"arkui.component.customComponent\"; +import { Component as Component, Builder as Builder } from \"@ohos.arkui.component\"; +function main() {} +@memo() function TestComponent(init: TestInitCallback, update: TestUpdateCallback): void {} +type TestInitCallback = (()=> void); +type TestUpdateCallback = (()=> void); +@Component() final struct MyStateSample extends CustomComponent { + public __initializeStruct(initializers: (__Options_MyStateSample | undefined), @memo() content: ((()=> void) | undefined)): void {} + public __updateStruct(initializers: (__Options_MyStateSample | undefined)): void {} + @memo() public build() { + TestComponent((() => { + if (true) { + } + }), (() => { + if (false) { + } + })); + } + public constructor() {} +} +@Component() export interface __Options_MyStateSample { +} +`; + +function testUITransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedUIScript)); +} + +pluginTester.run( + 'test no conditionScope in non-@Builder function', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testUITransformer], + }, + { + 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 index a30518a7e35dce2c6e43d84a5b9c1b214fb34eb7..4cd6d774a44df206a1403fed130665abf7f3caa5 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/builder-factory.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/builder-factory.ts @@ -33,7 +33,11 @@ export class BuilderFactory { const conditionScopeVisitor = ConditionScopeVisitor.getInstance(); funcBody = arkts.factory.updateBlock( funcBody, - funcBody.statements.map((st) => conditionScopeVisitor.visitor(st)) + funcBody.statements.map((st) => { + const newNode = conditionScopeVisitor.visitor(st); + conditionScopeVisitor.reset(); + return newNode; + }) ); return UIFactory.updateScriptFunction(_node, { body: funcBody }); } 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 index b8d9937c0fbe913966cef155af9bfe15ee6c9437..86ec83025b010cc0f9112fb8adadfd67b0c51b02 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/condition-scope-visitor.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/condition-scope-visitor.ts @@ -20,16 +20,33 @@ 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 _enforceUpdateCondition: boolean = false; + private _shouldUpdateCondition: boolean = true; private constructor() { super(); } + private get shouldUpdateCondition(): boolean { + if (this._enforceUpdateCondition) { + return true; + } + return this._shouldUpdateCondition; + } + + private set shouldUpdateCondition(newValue: boolean) { + if (this._enforceUpdateCondition) { + this._shouldUpdateCondition = true; + return; + } + this._shouldUpdateCondition = newValue; + } + static getInstance(): ConditionScopeVisitor { if (!this.instance) { this.instance = new ConditionScopeVisitor(); @@ -37,19 +54,39 @@ export class ConditionScopeVisitor extends AbstractVisitor { return this.instance; } + private enter(node: arkts.AstNode): void { + if (arkts.isVariableDeclarator(node) && arkts.NodeCache.getInstance().has(node)) { + this._enforceUpdateCondition = true; + } + } + + reset(): void { + this._enforceUpdateCondition = false; + this._shouldUpdateCondition = true; + } + visitor(node: arkts.AstNode): arkts.AstNode { - if (arkts.isIfStatement(node)) { + this.enter(node); + if (arkts.isIfStatement(node) && this.shouldUpdateCondition) { return BuilderLambdaFactory.updateIfElseContentBodyInBuilderLambda(node, true, true); } - if (arkts.isSwitchStatement(node)) { + if (arkts.isSwitchStatement(node) && this.shouldUpdateCondition) { return BuilderLambdaFactory.updateSwitchCaseContentBodyInBuilderLambda(node, true, true); } - if (arkts.isBlockStatement(node)) { + if (arkts.isBlockStatement(node) && this.shouldUpdateCondition) { const newStatements = node.statements.map((st) => BuilderLambdaFactory.updateContentBodyInBuilderLambda(st, true, true) ); return arkts.factory.updateBlock(node, newStatements); } + if ( + arkts.isArrowFunctionExpression(node) && + !arkts.NodeCache.getInstance().has(node) && + !arkts.NodeCache.getInstance().has(node.scriptFunction) + ) { + this.shouldUpdateCondition = false; + this._enforceUpdateCondition = false; + } return this.visitEachChild(node); } }