From e080cc3411453bd1baadeebe366e81bf8b2d2cef Mon Sep 17 00:00:00 2001 From: Cuecuexiaoyu Date: Tue, 1 Jul 2025 15:46:20 +0800 Subject: [PATCH] transform repeat arguments to arrow function type. Signed-off-by: Cuecuexiaoyu Change-Id: Icf712317d95b8730a23bd314508443859f9052af --- arkui-plugins/common/predefines.ts | 2 + .../test/demo/mock/component/repeat.ets | 42 ++++++ .../ut/ui-plugins/component/repeat.test.ts | 139 ++++++++++++++++++ .../builder-lambda-translators/factory.ts | 34 ++++- .../ui-plugins/struct-translators/factory.ts | 52 ++++--- .../ui-plugins/struct-translators/utils.ts | 11 +- 6 files changed, 249 insertions(+), 31 deletions(-) create mode 100644 arkui-plugins/test/demo/mock/component/repeat.ets create mode 100644 arkui-plugins/test/ut/ui-plugins/component/repeat.test.ts diff --git a/arkui-plugins/common/predefines.ts b/arkui-plugins/common/predefines.ts index 87bdd9413..e8e32b77a 100644 --- a/arkui-plugins/common/predefines.ts +++ b/arkui-plugins/common/predefines.ts @@ -40,6 +40,7 @@ export const CUSTOM_COMPONENT_IMPORT_SOURCE_NAME: string = 'arkui.component.cust export const ENTRY_POINT_IMPORT_SOURCE_NAME: string = 'arkui.UserView'; export const ARKUI_COMPONENT_COMMON_SOURCE_NAME: string = 'arkui.component.common'; export const ARKUI_FOREACH_SOURCE_NAME: string = 'arkui.component.forEach'; +export const ARKUI_REPEAT_SOURCE_NAME: string = 'arkui.component.repeat'; export enum ModuleType { HAR = 'har', @@ -101,6 +102,7 @@ export enum EntryParamNames { export enum InnerComponentNames { FOR_EACH = 'ForEach', + REPEAT = 'Repeat', } export enum DecoratorNames { diff --git a/arkui-plugins/test/demo/mock/component/repeat.ets b/arkui-plugins/test/demo/mock/component/repeat.ets new file mode 100644 index 000000000..0695416a0 --- /dev/null +++ b/arkui-plugins/test/demo/mock/component/repeat.ets @@ -0,0 +1,42 @@ +/* + * 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, Column, Repeat, RepeatItem } from "@kit.ArkUI" + +interface Person { + name: string; + age: number +} + +class AB { + per: string = 'hello'; + bar: Array = new Array('xx', 'yy', 'zz') +} + +@Component +struct ImportStruct { + arr: string[] = ['a', 'b', 'c'] + getArray() { + return new Array({ name: 'LiHua', age:25 } as Person, { name: 'Amy', age:18 } as Person) + } + + build() { + Column() { + Repeat(this.arr) + Repeat(this.getArray()) + Repeat((new AB()).bar) + } + } +} diff --git a/arkui-plugins/test/ut/ui-plugins/component/repeat.test.ts b/arkui-plugins/test/ut/ui-plugins/component/repeat.test.ts new file mode 100644 index 000000000..b69781b28 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/component/repeat.test.ts @@ -0,0 +1,139 @@ +/* + * 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 COMPONENT_DIR_PATH: string = 'component'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, COMPONENT_DIR_PATH, 'repeat.ets'), +]; + +const pluginTester = new PluginTester('test Repeat component transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'repeat', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +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, Column as Column, Repeat as Repeat, RepeatItem as RepeatItem } from "@kit.ArkUI"; + +function main() {} + +interface Person { + set name(name: string) + + get name(): string + set age(age: number) + + get age(): number + +} + +class AB { + public per: string = "hello"; + + public bar: Array = new Array("xx", "yy", "zz"); + + public constructor() {} + +} + +@Component() final struct ImportStruct extends CustomComponent { + public __initializeStruct(initializers: (__Options_ImportStruct | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_arr = ((({let gensym___244068973 = initializers; + (((gensym___244068973) == (null)) ? undefined : gensym___244068973.arr)})) ?? (["a", "b", "c"])); + } + + public __updateStruct(initializers: (__Options_ImportStruct | undefined)): void {} + + private __backing_arr?: Array; + + public get arr(): Array { + return (this.__backing_arr as Array); + } + + public set arr(value: Array) { + this.__backing_arr = value; + } + + public getArray() { + return new Array(({ + name: "LiHua", + age: 25, + } as Person), ({ + name: "Amy", + age: 18, + } as Person)); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Repeat(undefined, (() => { + return this.arr; + })); + Repeat(undefined, (() => { + return this.getArray(); + })); + Repeat(undefined, (() => { + return new AB().bar; + })); + })); + } + + private constructor() {} + +} + +@Component() export interface __Options_ImportStruct { + set arr(arr: (Array | undefined)) + + get arr(): (Array | undefined) + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test Repeat component transformation', + [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 55997c0c3..2a4bb2163 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts @@ -46,7 +46,15 @@ import { } from './utils'; import { isDecoratorIntrinsicAnnotation } from '../property-translators/utils'; import { factory as PropertyFactory } from '../property-translators/factory'; -import { AnimationNames, BindableDecl, DecoratorIntrinsicNames, DecoratorNames } from '../../common/predefines'; +import { factory as uiFactory } from '../ui-factory'; +import { + AnimationNames, + ARKUI_REPEAT_SOURCE_NAME, + BindableDecl, + DecoratorIntrinsicNames, + DecoratorNames, + InnerComponentNames, +} from '../../common/predefines'; import { ImportCollector } from '../../common/import-collector'; import { addMemoAnnotation, collectMemoableInfoInParameter } from '../../collectors/memo-collectors/utils'; @@ -66,6 +74,9 @@ export class factory { if (func.params.length > 0) { newParams.push(...prefixArgs, ...func.params); } + if (node.name.name === InnerComponentNames.REPEAT && externalSourceName === ARKUI_REPEAT_SOURCE_NAME) { + this.transformRepeatArguments(node, newParams); + } const updateFunc = arkts.factory .updateScriptFunction( func, @@ -675,4 +686,25 @@ export class factory { ) ); } + + static transformRepeatArguments(node: arkts.MethodDefinition, newParams: arkts.Expression[]): void { + const specificParam = node.scriptFunction.params[0]; + if ( + arkts.isEtsParameterExpression(specificParam) && + specificParam.type && + arkts.isTypeNode(specificParam.type) + ) { + newParams.splice( + 1, + 1, + arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier( + specificParam.identifier.name, + uiFactory.createLambdaFunctionType([], specificParam.type) + ), + undefined + ) + ); + } + } } diff --git a/arkui-plugins/ui-plugins/struct-translators/factory.ts b/arkui-plugins/ui-plugins/struct-translators/factory.ts index c450bf11e..2185ea153 100644 --- a/arkui-plugins/ui-plugins/struct-translators/factory.ts +++ b/arkui-plugins/ui-plugins/struct-translators/factory.ts @@ -21,7 +21,7 @@ import { getTypeNameFromTypeParameter, getTypeParamsFromClassDecl, isCustomComponentInterface, - isKnownMethodDefinition + isKnownMethodDefinition, } from '../utils'; import { factory as uiFactory } from '../ui-factory'; import { factory as propertyFactory } from '../property-translators/factory'; @@ -46,7 +46,7 @@ import { ResourceParameter, getResourceParams, isResourceNode, - isForEachCall, + isSpecificCall, } from './utils'; import { collectStateManagementTypeImport, hasDecorator, PropertyCache } from '../property-translators/utils'; import { ProjectConfig } from '../../common/plugin-context'; @@ -59,6 +59,7 @@ import { ModuleType, StateManagementTypes, RESOURCE_TYPE, + InnerComponentNames, } from '../../common/predefines'; import { ObservedTrackTranslator } from '../property-translators/observedTrack'; import { addMemoAnnotation } from '../../collectors/memo-collectors/utils'; @@ -858,32 +859,38 @@ export class factory { /* * add arrow function type to arguments of call expression. */ - static transformCallArguments(node: arkts.CallExpression): arkts.CallExpression { - if (!arkts.isArrowFunctionExpression(node.arguments[1])) { - return node; - } - const argTypeParam: arkts.Expression = node.arguments[1].scriptFunction.params[0]; - if ( - !arkts.isEtsParameterExpression(argTypeParam) || - !argTypeParam.type || - !arkts.isTypeNode(argTypeParam.type) - ) { - return node; + static transformCallArguments( + node: arkts.CallExpression, + index: number, + callName: string = '' + ): arkts.CallExpression { + let referenceType: arkts.TypeNode | undefined = undefined; + if (callName === InnerComponentNames.FOR_EACH) { + if (!arkts.isArrowFunctionExpression(node.arguments[1])) { + return node; + } + const argTypeParam: arkts.Expression = node.arguments[1].scriptFunction.params[0]; + if ( + !arkts.isEtsParameterExpression(argTypeParam) || + !argTypeParam.type || + !arkts.isTypeNode(argTypeParam.type) + ) { + return node; + } + referenceType = uiFactory.createComplexTypeFromStringAndTypeParameter('Array', [argTypeParam.type.clone()]); } - const referenceType = uiFactory.createComplexTypeFromStringAndTypeParameter('Array', [ - argTypeParam.type.clone(), - ]); const newArrowArg: arkts.ArrowFunctionExpression = arkts.factory.createArrowFunction( uiFactory.createScriptFunction({ - body: arkts.factory.createBlock([arkts.factory.createReturnStatement(node.arguments[0])]), + body: arkts.factory.createBlock([arkts.factory.createReturnStatement(node.arguments[index])]), returnTypeAnnotation: referenceType, flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, }) ); return arkts.factory.updateCallExpression(node, node.expression, node.typeArguments, [ + ...node.arguments.slice(0, index), newArrowArg, - ...node.arguments.slice(1), + ...node.arguments.slice(index + 1), ]); } @@ -915,11 +922,14 @@ export class factory { projectConfig: ProjectConfig | undefined, resourceInfo: ResourceInfo ): arkts.CallExpression { - if (arkts.isCallExpression(node) && isResourceNode(node)) { + if (isResourceNode(node)) { return this.transformResource(node, projectConfig, resourceInfo); } - if (arkts.isCallExpression(node) && isForEachCall(node)) { - return this.transformCallArguments(node); + if (isSpecificCall(node, InnerComponentNames.FOR_EACH) && node.arguments.length > 0) { + return this.transformCallArguments(node, 0, InnerComponentNames.FOR_EACH); + } + if (isSpecificCall(node, InnerComponentNames.REPEAT) && node.arguments.length > 1) { + return this.transformCallArguments(node, 1); } if (isArkUICompatible(node)) { return generateArkUICompatible(node as arkts.CallExpression); diff --git a/arkui-plugins/ui-plugins/struct-translators/utils.ts b/arkui-plugins/ui-plugins/struct-translators/utils.ts index bb3123e31..a2b5c63a4 100644 --- a/arkui-plugins/ui-plugins/struct-translators/utils.ts +++ b/arkui-plugins/ui-plugins/struct-translators/utils.ts @@ -107,15 +107,8 @@ export function isResourceNode(node: arkts.CallExpression, ignoreDecl: boolean = return true; } -export function isForEachCall(node: arkts.CallExpression): boolean { - if ( - arkts.isIdentifier(node.expression) && - node.expression.name === InnerComponentNames.FOR_EACH && - node.arguments.length >= 2 - ) { - return true; - } - return false; +export function isSpecificCall(node: arkts.CallExpression, callName: string): boolean { + return !!arkts.isIdentifier(node.expression) && node.expression.name === callName; } /** -- Gitee