From 0bb77f8547c0799ceeff28a23cffa42b66dbf98e Mon Sep 17 00:00:00 2001 From: Cuecuexiaoyu Date: Wed, 20 Aug 2025 09:26:16 +0800 Subject: [PATCH 1/2] develop navigation Signed-off-by: Cuecuexiaoyu Change-Id: I30aa0f1461372b5db062a15698a2b67000fadae0 --- arkui-plugins/common/metadata-collector.ts | 2 +- arkui-plugins/common/predefines.ts | 7 + .../mock/component/basic-nav-destination.ets | 27 +++ .../demo/mock/component/basic-navigation.ets | 42 +++++ .../component/basic-nav-destination.test.ts | 108 +++++++++++ .../component/basic-navigation.test.ts | 175 ++++++++++++++++++ .../cache/componentAttributeCache.ts | 3 +- .../builder-lambda-translators/factory.ts | 57 ++++++ .../builder-lambda-translators/utils.ts | 10 + .../ui-plugins/component-transformer.ts | 8 + .../ui-plugins/entry-translators/factory.ts | 25 ++- 11 files changed, 460 insertions(+), 4 deletions(-) create mode 100644 arkui-plugins/test/demo/mock/component/basic-nav-destination.ets create mode 100644 arkui-plugins/test/demo/mock/component/basic-navigation.ets create mode 100644 arkui-plugins/test/ut/ui-plugins/component/basic-nav-destination.test.ts create mode 100644 arkui-plugins/test/ut/ui-plugins/component/basic-navigation.test.ts diff --git a/arkui-plugins/common/metadata-collector.ts b/arkui-plugins/common/metadata-collector.ts index 9f12ec95b..c640ad699 100644 --- a/arkui-plugins/common/metadata-collector.ts +++ b/arkui-plugins/common/metadata-collector.ts @@ -48,4 +48,4 @@ export class MetaDataCollector { this.fileAbsName = undefined; this.externalSourceName = undefined; } -} \ No newline at end of file +} diff --git a/arkui-plugins/common/predefines.ts b/arkui-plugins/common/predefines.ts index 642d0219a..f19a63779 100644 --- a/arkui-plugins/common/predefines.ts +++ b/arkui-plugins/common/predefines.ts @@ -36,6 +36,8 @@ 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_BUILDER_SOURCE_NAME: string = 'arkui.component.builder'; +export const ARKUI_NAVIGATION_SOURCE_NAME: string = 'arkui.component.navigation'; +export const ARKUI_NAV_DESTINATION_SOURCE_NAME: string = 'arkui.component.navDestination'; export enum ModuleType { HAR = 'har', @@ -119,6 +121,8 @@ export enum EntryParamNames { export enum InnerComponentNames { FOR_EACH = 'ForEach', + NAVIGATION = 'Navigation', + NAV_DESTINATION = 'NavDestination', } export enum InnerComponentAttributes { @@ -244,6 +248,9 @@ export enum NavigationNames { PAGE_PATH = 'pagePath', PAGE_FULL_PATH = 'pageFullPath', INTEGRATED_HSP = 'integratedHsp', + MODULE_INFO = 'ModuleInfo', + NAVIGATION_MODULE_INFO = 'NavigationModuleInfo', + IS_USER_CREATE_STACK = 'isUserCreateStack', } export enum ConditionNames { diff --git a/arkui-plugins/test/demo/mock/component/basic-nav-destination.ets b/arkui-plugins/test/demo/mock/component/basic-nav-destination.ets new file mode 100644 index 000000000..e080d6764 --- /dev/null +++ b/arkui-plugins/test/demo/mock/component/basic-nav-destination.ets @@ -0,0 +1,27 @@ +/* + * 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, Column, Button, NavDestination } from "@ohos.arkui.component" + +@Component +struct NavDestinationStruct { + build() { + NavDestination(){ + Column(){ + Button('abc') + } + }.width(80) + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/component/basic-navigation.ets b/arkui-plugins/test/demo/mock/component/basic-navigation.ets new file mode 100644 index 000000000..99b8afdae --- /dev/null +++ b/arkui-plugins/test/demo/mock/component/basic-navigation.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, Navigation, NavPathStack, Column, Button } from "@ohos.arkui.component" + +@Component +struct MyStateSample1 { + pathStack: NavPathStack = new NavPathStack() + + build() { + Navigation(this.pathStack){ + Column(){ + Button('abc').width(100).height(300) + } + }.width(80) + } +} + +@Component +struct MyStateSample2 { + pathStack: NavPathStack = new NavPathStack() + + build() { + Navigation(){ + Column(){ + Button('abc') + } + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/ut/ui-plugins/component/basic-nav-destination.test.ts b/arkui-plugins/test/ut/ui-plugins/component/basic-nav-destination.test.ts new file mode 100644 index 000000000..65d25b335 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/component/basic-nav-destination.test.ts @@ -0,0 +1,108 @@ +/* + * 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, 'basic-nav-destination.ets'), +]; + +const pluginTester = new PluginTester('test basic navDestination transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { NavDestinationAttribute as NavDestinationAttribute } from "arkui.component.navDestination"; + +import { ColumnAttribute as ColumnAttribute } from "arkui.component.column"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ButtonAttribute as ButtonAttribute } from "arkui.component.button"; + +import { ButtonImpl as ButtonImpl } from "arkui.component.button"; + +import { ColumnImpl as ColumnImpl } from "arkui.component.column"; + +import { NavDestinationImpl as NavDestinationImpl } from "arkui.component.navDestination"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Column as Column, Button as Button, NavDestination as NavDestination } from "@ohos.arkui.component"; + +function main() {} + +@Component() final struct NavDestinationStruct extends CustomComponent { + public __initializeStruct(initializers: (__Options_NavDestinationStruct | undefined), @memo() content: ((()=> void) | undefined)): void {} + + public __updateStruct(initializers: (__Options_NavDestinationStruct | undefined)): void {} + + @memo() public build() { + NavDestinationImpl(@memo() ((instance: NavDestinationAttribute): void => { + instance.setNavDestinationOptions({ + moduleName: "entry", + pagePath: "mock/component/basic-nav-destination", + }).width(80).applyAttributesFinish(); + return; + }), @memo() (() => { + ColumnImpl(@memo() ((instance: ColumnAttribute): void => { + instance.setColumnOptions(undefined).applyAttributesFinish(); + return; + }), @memo() (() => { + ButtonImpl(@memo() ((instance: ButtonAttribute): void => { + instance.setButtonOptions("abc", undefined).applyAttributesFinish(); + return; + }), undefined); + })); + })); + } + + public constructor() {} + +} + +@Component() export interface __Options_NavDestinationStruct { + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test basic navDestination transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/component/basic-navigation.test.ts b/arkui-plugins/test/ut/ui-plugins/component/basic-navigation.test.ts new file mode 100644 index 000000000..4b5fde3a0 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/component/basic-navigation.test.ts @@ -0,0 +1,175 @@ +/* + * 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, 'basic-navigation.ets'), +]; + +const pluginTester = new PluginTester('test basic navigation transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { NavigationAttribute as NavigationAttribute } from "arkui.component.navigation"; + +import { ColumnAttribute as ColumnAttribute } from "arkui.component.column"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ButtonAttribute as ButtonAttribute } from "arkui.component.button"; + +import { ButtonImpl as ButtonImpl } from "arkui.component.button"; + +import { ColumnImpl as ColumnImpl } from "arkui.component.column"; + +import { NavigationImpl as NavigationImpl } from "arkui.component.navigation"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Navigation as Navigation, NavPathStack as NavPathStack, Column as Column, Button as Button } from "@ohos.arkui.component"; + +function main() {} + +@Component() final struct MyStateSample1 extends CustomComponent { + public __initializeStruct(initializers: (__Options_MyStateSample1 | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_pathStack = ((({let gensym___1107384 = initializers; + (((gensym___1107384) == (null)) ? undefined : gensym___1107384.pathStack)})) ?? (new NavPathStack())); + } + + public __updateStruct(initializers: (__Options_MyStateSample1 | undefined)): void {} + + private __backing_pathStack?: NavPathStack; + + public get pathStack(): NavPathStack { + return (this.__backing_pathStack as NavPathStack); + } + + public set pathStack(value: NavPathStack) { + this.__backing_pathStack = value; + } + + @memo() public build() { + NavigationImpl(@memo() ((instance: NavigationAttribute): void => { + instance.setNavigationOptions(this.pathStack, { + moduleName: "entry", + pagePath: "mock/component/basic-navigation", + isUserCreateStack: true, + }).width(80).applyAttributesFinish(); + return; + }), @memo() (() => { + ColumnImpl(@memo() ((instance: ColumnAttribute): void => { + instance.setColumnOptions(undefined).applyAttributesFinish(); + return; + }), @memo() (() => { + ButtonImpl(@memo() ((instance: ButtonAttribute): void => { + instance.setButtonOptions("abc", undefined).width(100).height(300).applyAttributesFinish(); + return; + }), undefined); + })); + })); + } + + public constructor() {} + +} + +@Component() final struct MyStateSample2 extends CustomComponent { + public __initializeStruct(initializers: (__Options_MyStateSample2 | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_pathStack = ((({let gensym___199081302 = initializers; + (((gensym___199081302) == (null)) ? undefined : gensym___199081302.pathStack)})) ?? (new NavPathStack())); + } + + public __updateStruct(initializers: (__Options_MyStateSample2 | undefined)): void {} + + private __backing_pathStack?: NavPathStack; + + public get pathStack(): NavPathStack { + return (this.__backing_pathStack as NavPathStack); + } + + public set pathStack(value: NavPathStack) { + this.__backing_pathStack = value; + } + + @memo() public build() { + NavigationImpl(@memo() ((instance: NavigationAttribute): void => { + instance.setNavigationOptions(undefined, { + moduleName: "entry", + pagePath: "mock/component/basic-navigation", + isUserCreateStack: false, + }).applyAttributesFinish(); + return; + }), @memo() (() => { + ColumnImpl(@memo() ((instance: ColumnAttribute): void => { + instance.setColumnOptions(undefined).applyAttributesFinish(); + return; + }), @memo() (() => { + ButtonImpl(@memo() ((instance: ButtonAttribute): void => { + instance.setButtonOptions("abc", undefined).applyAttributesFinish(); + return; + }), undefined); + })); + })); + } + + public constructor() {} + +} + +@Component() export interface __Options_MyStateSample1 { + set pathStack(pathStack: (NavPathStack | undefined)) + + get pathStack(): (NavPathStack | undefined) + +} + +@Component() export interface __Options_MyStateSample2 { + set pathStack(pathStack: (NavPathStack | undefined)) + + get pathStack(): (NavPathStack | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test basic navigation transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/cache/componentAttributeCache.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/cache/componentAttributeCache.ts index 1914e3070..7c3237a58 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/cache/componentAttributeCache.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/cache/componentAttributeCache.ts @@ -14,7 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { checkIsTrailingLambdaInLastParam, isForEach } from '../utils'; +import { checkIsTrailingLambdaInLastParam, isForEach, isNavigationOrNavDestination } from '../utils'; import { collectTypeRecordFromParameter, collectTypeRecordFromTypeParameterDeclaration, @@ -24,6 +24,7 @@ import { TypeRecord, } from '../../../collectors/utils/collect-types'; import { factory as UIFactory } from '../../ui-factory'; +import { factory as builderLambdaFactory } from '../factory'; export interface ComponentRecord { name: string; diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts index 6bffb9de5..663126ff9 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts @@ -14,6 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; +import path from 'path'; import { BuilderLambdaNames, inferTypeFromValue, optionsHasField } from '../utils'; import { backingField, @@ -53,6 +54,7 @@ import { getTransformedComponentName, flatObjectExpressionToEntries, OptionsPropertyInfo, + isNavigationOrNavDestination, } from './utils'; import { hasDecorator, isDecoratorIntrinsicAnnotation } from '../property-translators/utils'; import { BuilderFactory } from './builder-factory'; @@ -69,6 +71,9 @@ import { DecoratorNames, StateManagementTypes, TypeNames, + InnerComponentNames, + ModuleType, + NavigationNames, } from '../../common/predefines'; import { ImportCollector } from '../../common/import-collector'; import { GenSymGenerator } from '../../common/gensym-generator'; @@ -84,6 +89,7 @@ import { StyleInternalsVisitor } from './style-internals-visitor'; import { ConditionBreakCache } from './cache/conditionBreakCache'; import { ComponentAttributeCache, ComponentRecord } from './cache/componentAttributeCache'; import { isForEach } from './utils'; +import { MetaDataCollector } from '../../common/metadata-collector'; export class factory { /** @@ -100,6 +106,9 @@ export class factory { const name: string = ident.name; const isFunctionCall: boolean = name !== BuilderLambdaNames.ORIGIN_METHOD_NAME; const newParams: arkts.Expression[] = [...prefixArgs, ...func.params]; + if (isNavigationOrNavDestination(newName ?? node.name.name)) { + newParams.splice(newParams.length - 1, 0, factory.createModuleInfoParam()); + } const updateFunc = arkts.factory .updateScriptFunction( func, @@ -125,6 +134,18 @@ export class factory { ); } + static createModuleInfoParam(): arkts.ETSParameterExpression { + return arkts.factory + .createParameterDeclaration( + arkts.factory.createIdentifier( + NavigationNames.MODULE_INFO, + UIFactory.createTypeReferenceFromString(NavigationNames.NAVIGATION_MODULE_INFO) + ), + undefined + ) + .setOptional(true); + } + /* * transform arguments in style node. */ @@ -545,6 +566,11 @@ export class factory { }, { isTrailingCall } ); + if (isNavigationOrNavDestination(type?.name, moduleName)) { + const isUserCreateStack: boolean | undefined = + type?.name === InnerComponentNames.NAVIGATION ? leaf.arguments.length > 1 : undefined; + modifiedArgs.push(factory.createModuleInfoArg(isUserCreateStack)); + } const lambdaBody = this.addOptionsArgsToLambdaBodyInStyleArg( lambdaBodyInfo, modifiedArgs, @@ -640,6 +666,37 @@ export class factory { return this.addApplyAttributesFinishToLambdaBodyEnd(newLambdaBody, shouldApplyAttribute); } + static createModuleInfoArg(isUserCreateStack: boolean | undefined): arkts.ObjectExpression { + const projectConfig = MetaDataCollector.getInstance().projectConfig; + const fileAbsName = MetaDataCollector.getInstance().fileAbsName; + const moduleName = projectConfig?.moduleName ?? ''; + const filePath = path.relative(projectConfig?.projectRootPath ?? '', fileAbsName ?? '').replace(/\.ets$/, ''); + const pagePath = projectConfig?.moduleType === ModuleType.HAR ? '' : filePath; + const properties: arkts.Property[] = [ + arkts.factory.createProperty( + arkts.factory.createIdentifier(NavigationNames.MODULE_NAME), + arkts.factory.createStringLiteral(moduleName) + ), + arkts.factory.createProperty( + arkts.factory.createIdentifier(NavigationNames.PAGE_PATH), + arkts.factory.createStringLiteral(pagePath) + ), + ]; + if (isUserCreateStack !== undefined) { + properties.push( + arkts.factory.createProperty( + arkts.factory.createIdentifier(NavigationNames.IS_USER_CREATE_STACK), + arkts.factory.createBooleanLiteral(isUserCreateStack) + ) + ); + } + return arkts.factory.createObjectExpression( + arkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + properties, + true + ); + } + /** * add `.applyAttributesFinish()` at the end of style argument body. */ diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts index 9c4a7531a..63a99fc73 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts @@ -22,6 +22,8 @@ import { ARKUI_IMPORT_PREFIX_NAMES, Dollars, InnerComponentAttributes, + ARKUI_NAV_DESTINATION_SOURCE_NAME, + ARKUI_NAVIGATION_SOURCE_NAME, InnerComponentNames, StructDecoratorNames, } from '../../common/predefines'; @@ -677,6 +679,14 @@ export function flatObjectExpressionToEntries( return entries; } +export function isNavigationOrNavDestination(name: string | undefined, sourceName?: string): boolean { + const externalSourceName = sourceName ?? MetaDataCollector.getInstance().externalSourceName; + return ( + (name === InnerComponentNames.NAVIGATION && externalSourceName === ARKUI_NAVIGATION_SOURCE_NAME) || + (name === InnerComponentNames.NAV_DESTINATION && externalSourceName === ARKUI_NAV_DESTINATION_SOURCE_NAME) + ); +} + /** * check whether the last parameter is trailing lambda in components. */ diff --git a/arkui-plugins/ui-plugins/component-transformer.ts b/arkui-plugins/ui-plugins/component-transformer.ts index ae383cc16..02fadf657 100644 --- a/arkui-plugins/ui-plugins/component-transformer.ts +++ b/arkui-plugins/ui-plugins/component-transformer.ts @@ -53,6 +53,8 @@ import { ENTRY_POINT_IMPORT_SOURCE_NAME, NavigationNames, EntryWrapperNames, + ARKUI_NAVIGATION_SOURCE_NAME, + ARKUI_NAV_DESTINATION_SOURCE_NAME, } from '../common/predefines'; import { generateInstantiateInterop } from './interop/interop'; @@ -208,6 +210,12 @@ export class ComponentTransformer extends AbstractVisitor { navInterface.modifiers = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT; return arkts.factory.updateEtsScript(node, [...node.statements, navInterface]); } + if ( + this.externalSourceName === ARKUI_NAVIGATION_SOURCE_NAME || + this.externalSourceName === ARKUI_NAV_DESTINATION_SOURCE_NAME + ) { + return arkts.factory.updateEtsScript(node, [...node.statements, entryFactory.createNavigationModuleInfo()]); + } if (this.isExternal && this.componentInterfaceCollection.length === 0 && this.entryNames.length === 0) { return node; } diff --git a/arkui-plugins/ui-plugins/entry-translators/factory.ts b/arkui-plugins/ui-plugins/entry-translators/factory.ts index e4dd29d22..ff9273469 100644 --- a/arkui-plugins/ui-plugins/entry-translators/factory.ts +++ b/arkui-plugins/ui-plugins/entry-translators/factory.ts @@ -356,6 +356,27 @@ export class factory { ); } + static createNavigationModuleInfo(): arkts.TSInterfaceDeclaration { + return arkts.factory.createInterfaceDeclaration( + [], + arkts.factory.createIdentifier(NavigationNames.NAVIGATION_MODULE_INFO), + undefined, + arkts.factory.createInterfaceBody([ + this.createClassProp(NavigationNames.MODULE_NAME), + this.createClassProp(NavigationNames.PAGE_PATH), + arkts.classPropertySetOptional( + this.createClassProp( + NavigationNames.IS_USER_CREATE_STACK, + arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BOOLEAN) + ), + true + ), + ]), + false, + false + ); + } + /** * helper for navInterfaceArg to generate class properties, e.g. buneleName: '...' */ @@ -423,11 +444,11 @@ export class factory { /** * helper for createNavInterface to generate class properties */ - static createClassProp(propName: string): arkts.ClassProperty { + static createClassProp(propName: string, type?: arkts.TypeNode): arkts.ClassProperty { return arkts.factory.createClassProperty( arkts.factory.createIdentifier(propName), undefined, - uiFactory.createTypeReferenceFromString('string'), + type ?? uiFactory.createTypeReferenceFromString('string'), arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, false ); -- Gitee From ac993f1eae2b83d720ada5afc57d891eed7d84a3 Mon Sep 17 00:00:00 2001 From: Cuecuexiaoyu Date: Sun, 24 Aug 2025 19:39:12 +0800 Subject: [PATCH 2/2] add router map transformation Signed-off-by: Cuecuexiaoyu Change-Id: Iea7a6762326e05bad72c0807f320ea6ecb821e2b --- arkui-plugins/common/metadata-collector.ts | 8 + arkui-plugins/common/predefines.ts | 9 + .../mix-navigation-nav-destination.ets | 121 ++++++ .../demo/mock/component/nav-no-lambda.ets | 29 ++ .../mix-navigation-nav-destination.test.ts | 410 ++++++++++++++++++ .../component/nav-no-lambda.test.ts | 136 ++++++ .../cache/componentAttributeCache.ts | 10 +- .../builder-lambda-translators/factory.ts | 34 +- .../builder-lambda-translators/utils.ts | 10 + .../ui-plugins/checked-transformer.ts | 8 +- .../ui-plugins/component-transformer.ts | 6 +- .../ui-plugins/entry-translators/factory.ts | 41 +- .../ui-plugins/struct-translators/factory.ts | 67 +++ .../ui-plugins/struct-translators/utils.ts | 42 ++ 14 files changed, 912 insertions(+), 19 deletions(-) create mode 100644 arkui-plugins/test/demo/mock/component/mix-navigation-nav-destination.ets create mode 100644 arkui-plugins/test/demo/mock/component/nav-no-lambda.ets create mode 100644 arkui-plugins/test/ut/ui-plugins/component/mix-navigation-nav-destination.test.ts create mode 100644 arkui-plugins/test/ut/ui-plugins/component/nav-no-lambda.test.ts diff --git a/arkui-plugins/common/metadata-collector.ts b/arkui-plugins/common/metadata-collector.ts index c640ad699..ed732acd2 100644 --- a/arkui-plugins/common/metadata-collector.ts +++ b/arkui-plugins/common/metadata-collector.ts @@ -13,12 +13,14 @@ * limitations under the License. */ +import { RouterInfo } from '../ui-plugins/struct-translators/utils'; import { ProjectConfig } from './plugin-context'; export class MetaDataCollector { public projectConfig: ProjectConfig | undefined; public fileAbsName: string | undefined; public externalSourceName: string | undefined; + public routerInfo: Map = new Map(); private static instance: MetaDataCollector | null = null; static getInstance(): MetaDataCollector { @@ -43,9 +45,15 @@ export class MetaDataCollector { return this; } + setRouterInfo(routerInfo: Map): this { + this.routerInfo = routerInfo; + return this; + } + reset(): void { this.projectConfig = undefined; this.fileAbsName = undefined; this.externalSourceName = undefined; + this.routerInfo.clear(); } } diff --git a/arkui-plugins/common/predefines.ts b/arkui-plugins/common/predefines.ts index f19a63779..dbf879850 100644 --- a/arkui-plugins/common/predefines.ts +++ b/arkui-plugins/common/predefines.ts @@ -85,6 +85,7 @@ export enum EntryWrapperNames { WRAPPER_CLASS_NAME = '__EntryWrapper', ENTRY_STORAGE_LOCAL_STORAGE_PROPERTY_NAME = '_entry_local_storage_', ENTRY_POINT_CLASS_NAME = 'EntryPoint', + NAVIGATION_BUILDER_REGISTER = 'NavigationBuilderRegister', REGISTER_NAMED_ROUTER = 'RegisterNamedRouter', ROUTER_NAME = 'routerName', INSTANCE = 'instance', @@ -125,6 +126,12 @@ export enum InnerComponentNames { NAV_DESTINATION = 'NavDestination', } +export enum BuilderNames { + BUILDER = 'builder', + WRAP_BUILDER = 'wrapBuilder', + WRAPPED_BUILDER = 'WrappedBuilder', +} + export enum InnerComponentAttributes { COMMON_METHOD = 'CommonMethod', } @@ -170,6 +177,7 @@ export enum TypeNames { ARRAY = 'Array', MAP = 'Map', STRING = 'string', + TYPE_T = 'T', } export enum DecoratorIntrinsicNames { @@ -251,6 +259,7 @@ export enum NavigationNames { MODULE_INFO = 'ModuleInfo', NAVIGATION_MODULE_INFO = 'NavigationModuleInfo', IS_USER_CREATE_STACK = 'isUserCreateStack', + NAME = 'name', } export enum ConditionNames { diff --git a/arkui-plugins/test/demo/mock/component/mix-navigation-nav-destination.ets b/arkui-plugins/test/demo/mock/component/mix-navigation-nav-destination.ets new file mode 100644 index 000000000..b795b25ba --- /dev/null +++ b/arkui-plugins/test/demo/mock/component/mix-navigation-nav-destination.ets @@ -0,0 +1,121 @@ +/* + * 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, BuilderParam, Builder, Component, NavPathStack, Navigation, NavPathInfo, NavDestination, NavigationMode } from '@ohos.arkui.component'; +import { State, Observed, ObjectLink, Link } from '@ohos.arkui.stateManagement'; + +@Component +export struct SubNavigation { + @Link isPortrait: boolean; + @State displayMode: number = 0; + @BuilderParam navDestination: ((name: String, param: Object|undefined) => void) | undefined; + @State primaryWidth: number | string = '50%'; + onNavigationModeChange?: OnNavigationModeChangeCallback = (mode: NavigationMode) => {}; + @State primaryStack: MyNavPathStack = new MyNavPathStack(); + @State secondaryStack: MyNavPathStack = new MyNavPathStack(); + + @Builder + SubNavDestination(name: string, param?: object) { + this.navDestination!(name, param as Object); + } + + build() { + NavDestination() { + Navigation(this.secondaryStack) { + Navigation(this.primaryStack) { + } + .hideNavBar(true) + .mode(NavigationMode.Stack) + .navDestination(this.SubNavDestination) + .hideTitleBar(true, true) + .hideToolBar(true, true) + .hideBackButton(true) + } + .onNavigationModeChange(this?.onNavigationModeChange) + .hideBackButton(true) + .hideTitleBar(true, true) + .navDestination(this.SubNavDestination) + .navBarWidth(this.primaryWidth) + } + } +} + +export enum SplitPolicy { + HOME_PAGE = 0, + DETAIL_PAGE = 1, + FULL_PAGE = 2, + PlACE_HOLDER_PAGE = 3, +} + +class MultiNavPolicyInfo { + public policy: SplitPolicy = SplitPolicy.DETAIL_PAGE; + public navInfo: NavPathInfo | undefined = undefined; + public isFullScreen: boolean | undefined = undefined; + + constructor(policy: SplitPolicy, navInfo: NavPathInfo) { + this.policy = policy; + this.navInfo = navInfo; + } +} + +export class MyNavPathStack extends NavPathStack { + public operates:NavPathStackOperate[] = []; + public type:string = 'NavPathStack'; + public policyInfoList: MultiNavPolicyInfo[] = []; + + public registerStackOperateCallback(operate: NavPathStackOperate) { + let index = this.operates.findIndex((item) => { return item === operate}); + if (index === -1) { + this.operates.push(operate); + } + } + + public unregisterStackOperateCallback(operate: NavPathStackOperate) { + let index = this.operates.findIndex((item) => { return item === operate}); + if (index !== -1) { + this.operates.splice(index, 1); + } + } + + public popInner(animated?: boolean): NavPathInfo | undefined { + console.log('MyNavPathStack pop from inner:'); + return super.pop({}, animated); + } + + public pop(animated?: boolean): NavPathInfo | undefined { + console.log('MyNavPathStack pop from system:'); + animated = typeof animated === 'undefined' ? true : animated + let ret: NavPathInfo | undefined = undefined; + ret = super.pop({}, animated); + this.policyInfoList.pop(); + this.operates.forEach((item) => { + item.onSystemPop(); + }) + return ret; + } +} + +interface NavPathStackOperate { + onSystemPop: ()=>void; +} + +interface MultiNavPathStackOperate { + onPrimaryPop: ()=>void; + onSecondaryPop: ()=>void; +} + +declare type OnNavigationModeChangeCallback = (mode: NavigationMode) => void; + +declare type OnHomeShowOnTopCallback = (name: string) => void; \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/component/nav-no-lambda.ets b/arkui-plugins/test/demo/mock/component/nav-no-lambda.ets new file mode 100644 index 000000000..916b18628 --- /dev/null +++ b/arkui-plugins/test/demo/mock/component/nav-no-lambda.ets @@ -0,0 +1,29 @@ +/* + * 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, Column, NavDestination, Navigation, NavPathStack } from "@ohos.arkui.component" + +@Component +struct NavDestinationStruct { + pathStack: NavPathStack = new NavPathStack() + + build() { + Column() { + NavDestination().width(80) + Navigation(this.pathStack).width(80) + Navigation().width(80) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/ut/ui-plugins/component/mix-navigation-nav-destination.test.ts b/arkui-plugins/test/ut/ui-plugins/component/mix-navigation-nav-destination.test.ts new file mode 100644 index 000000000..a64f1208e --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/component/mix-navigation-nav-destination.test.ts @@ -0,0 +1,410 @@ +/* + * 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, 'mix-navigation-nav-destination.ets'), +]; + +const pluginTester = new PluginTester('test mix usage of navigation and navDestination transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { IStateDecoratedVariable as IStateDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { LinkSourceType as LinkSourceType } from "arkui.stateManagement.decorator"; + +import { ILinkDecoratedVariable as ILinkDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { NavDestinationAttribute as NavDestinationAttribute } from "arkui.component.navDestination"; + +import { NavigationAttribute as NavigationAttribute } from "arkui.component.navigation"; + +import { NavigationImpl as NavigationImpl } from "arkui.component.navigation"; + +import { NavDestinationImpl as NavDestinationImpl } from "arkui.component.navDestination"; + +import { MemoSkip as MemoSkip } from "arkui.stateManagement.runtime"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Text as Text, BuilderParam as BuilderParam, Builder as Builder, Component as Component, NavPathStack as NavPathStack, Navigation as Navigation, NavPathInfo as NavPathInfo, NavDestination as NavDestination, NavigationMode as NavigationMode } from "@ohos.arkui.component"; + +import { State as State, Observed as Observed, ObjectLink as ObjectLink, Link as Link } from "@ohos.arkui.stateManagement"; + +function main() {} + + +@Component() export final struct SubNavigation extends CustomComponent { + public __initializeStruct(initializers: (__Options_SubNavigation | undefined), @memo() content: ((()=> void) | undefined)): void { + if (({let gensym___214357609 = initializers; + (((gensym___214357609) == (null)) ? undefined : gensym___214357609.__backing_isPortrait)})) { + this.__backing_isPortrait = STATE_MGMT_FACTORY.makeLink(this, "isPortrait", initializers!.__backing_isPortrait!); + }; + this.__backing_displayMode = STATE_MGMT_FACTORY.makeState(this, "displayMode", ((({let gensym___197056380 = initializers; + (((gensym___197056380) == (null)) ? undefined : gensym___197056380.displayMode)})) ?? (0))); + this.__backing_navDestination = ((((({let gensym___165803261 = initializers; + (((gensym___165803261) == (null)) ? undefined : gensym___165803261.navDestination)})) ?? (content))) ?? (undefined)) + this.__backing_primaryWidth = STATE_MGMT_FACTORY.makeState<(number | string)>(this, "primaryWidth", ((({let gensym___158861871 = initializers; + (((gensym___158861871) == (null)) ? undefined : gensym___158861871.primaryWidth)})) ?? ("50%"))); + this.__backing_onNavigationModeChange = ((({let gensym___114390532 = initializers; + (((gensym___114390532) == (null)) ? undefined : gensym___114390532.onNavigationModeChange)})) ?? (((mode: NavigationMode) => {}))); + this.__backing_primaryStack = STATE_MGMT_FACTORY.makeState(this, "primaryStack", ((({let gensym___224425708 = initializers; + (((gensym___224425708) == (null)) ? undefined : gensym___224425708.primaryStack)})) ?? (new MyNavPathStack()))); + this.__backing_secondaryStack = STATE_MGMT_FACTORY.makeState(this, "secondaryStack", ((({let gensym___57045085 = initializers; + (((gensym___57045085) == (null)) ? undefined : gensym___57045085.secondaryStack)})) ?? (new MyNavPathStack()))); + } + + public __updateStruct(initializers: (__Options_SubNavigation | undefined)): void {} + + private __backing_isPortrait?: ILinkDecoratedVariable; + + public get isPortrait(): boolean { + return this.__backing_isPortrait!.get(); + } + + public set isPortrait(value: boolean) { + this.__backing_isPortrait!.set(value); + } + + private __backing_displayMode?: IStateDecoratedVariable; + + public get displayMode(): number { + return this.__backing_displayMode!.get(); + } + + public set displayMode(value: number) { + this.__backing_displayMode!.set(value); + } + + private __backing_navDestination?: (((name: String, param: (Object | undefined))=> void) | undefined); + + public get navDestination(): (@memo() ((name: String, param: (Object | undefined))=> void) | undefined) { + return this.__backing_navDestination!; + } + + public set navDestination(value: (@memo() ((name: String, param: (Object | undefined))=> void) | undefined)) { + this.__backing_navDestination = value; + } + + private __backing_primaryWidth?: IStateDecoratedVariable<(number | string)>; + + public get primaryWidth(): (number | string) { + return this.__backing_primaryWidth!.get(); + } + + public set primaryWidth(value: (number | string)) { + this.__backing_primaryWidth!.set(value); + } + + private __backing_onNavigationModeChange?: (OnNavigationModeChangeCallback | undefined); + + public get onNavigationModeChange(): (OnNavigationModeChangeCallback | undefined) { + return (this.__backing_onNavigationModeChange as (OnNavigationModeChangeCallback | undefined)); + } + + public set onNavigationModeChange(value: (OnNavigationModeChangeCallback | undefined)) { + this.__backing_onNavigationModeChange = value; + } + + private __backing_primaryStack?: IStateDecoratedVariable; + + public get primaryStack(): MyNavPathStack { + return this.__backing_primaryStack!.get(); + } + + public set primaryStack(value: MyNavPathStack) { + this.__backing_primaryStack!.set(value); + } + + private __backing_secondaryStack?: IStateDecoratedVariable; + + public get secondaryStack(): MyNavPathStack { + return this.__backing_secondaryStack!.get(); + } + + public set secondaryStack(value: MyNavPathStack) { + this.__backing_secondaryStack!.set(value); + } + + @memo() public SubNavDestination(@MemoSkip() name: string, @MemoSkip() param?: object) { + this.navDestination!(name, (param as Object)); + } + + @memo() public build() { + NavDestinationImpl(@memo() ((instance: NavDestinationAttribute): void => { + instance.setNavDestinationOptions({ + moduleName: "entry", + pagePath: "mock/component/mix-navigation-nav-destination", + }).applyAttributesFinish(); + return; + }), @memo() (() => { + NavigationImpl(@memo() ((instance: NavigationAttribute): void => { + instance.setNavigationOptions(this.secondaryStack, { + moduleName: "entry", + pagePath: "mock/component/mix-navigation-nav-destination", + isUserCreateStack: true, + }).onNavigationModeChange(({let gensym%%_44 = this; + (((gensym%%_44) == (null)) ? undefined : gensym%%_44.onNavigationModeChange)})).hideBackButton(true).hideTitleBar(true, true).navDestination(this.SubNavDestination).navBarWidth(this.primaryWidth).applyAttributesFinish(); + return; + }), @memo() (() => { + NavigationImpl(@memo() ((instance: NavigationAttribute): void => { + instance.setNavigationOptions(this.primaryStack, { + moduleName: "entry", + pagePath: "mock/component/mix-navigation-nav-destination", + isUserCreateStack: true, + }).hideNavBar(true).mode(NavigationMode.Stack).navDestination(this.SubNavDestination).hideTitleBar(true, true).hideToolBar(true, true).hideBackButton(true).applyAttributesFinish(); + return; + }), @memo() (() => {})); + })); + })); + } + + public constructor() {} + + public static _buildCompatibleNode(options: __Options_SubNavigation): void { + return; + } + +} + +export final class SplitPolicy extends BaseEnum { + private readonly #ordinal: int; + + private static () {} + + public constructor(ordinal: int, value: int) { + super(value); + this.#ordinal = ordinal; + } + + public static readonly HOME_PAGE: SplitPolicy = new SplitPolicy(0, 0); + + public static readonly DETAIL_PAGE: SplitPolicy = new SplitPolicy(1, 1); + + public static readonly FULL_PAGE: SplitPolicy = new SplitPolicy(2, 2); + + public static readonly PlACE_HOLDER_PAGE: SplitPolicy = new SplitPolicy(3, 3); + + private static readonly #NamesArray: String[] = ["HOME_PAGE", "DETAIL_PAGE", "FULL_PAGE", "PlACE_HOLDER_PAGE"]; + + private static readonly #ValuesArray: int[] = [0, 1, 2, 3]; + + private static readonly #StringValuesArray: String[] = ["0", "1", "2", "3"]; + + private static readonly #ItemsArray: SplitPolicy[] = [SplitPolicy.HOME_PAGE, SplitPolicy.DETAIL_PAGE, SplitPolicy.FULL_PAGE, SplitPolicy.PlACE_HOLDER_PAGE]; + + public getName(): String { + return SplitPolicy.#NamesArray[this.#ordinal]; + } + + public static getValueOf(name: String): SplitPolicy { + for (let i = 0;((i) < (SplitPolicy.#NamesArray.length));(++i)) { + if (((name) == (SplitPolicy.#NamesArray[i]))) { + return SplitPolicy.#ItemsArray[i]; + } + } + throw new Error((("No enum constant SplitPolicy.") + (name))); + } + + public static fromValue(value: int): SplitPolicy { + for (let i = 0;((i) < (SplitPolicy.#ValuesArray.length));(++i)) { + if (((value) == (SplitPolicy.#ValuesArray[i]))) { + return SplitPolicy.#ItemsArray[i]; + } + } + throw new Error((("No enum SplitPolicy with value ") + (value))); + } + + public valueOf(): int { + return SplitPolicy.#ValuesArray[this.#ordinal]; + } + + public toString(): String { + return SplitPolicy.#StringValuesArray[this.#ordinal]; + } + + public static values(): SplitPolicy[] { + return SplitPolicy.#ItemsArray; + } + + public getOrdinal(): int { + return this.#ordinal; + } + + public static $_get(e: SplitPolicy): String { + return e.getName(); + } + +} + +class MultiNavPolicyInfo { + public policy: SplitPolicy = SplitPolicy.DETAIL_PAGE; + + public navInfo: (NavPathInfo | undefined) = undefined; + + public isFullScreen: (boolean | undefined) = undefined; + + public constructor(policy: SplitPolicy, navInfo: NavPathInfo) { + this.policy = policy; + this.navInfo = navInfo; + } + +} + +export class MyNavPathStack extends NavPathStack { + public operates: Array = []; + + public type: string = "NavPathStack"; + + public policyInfoList: Array = []; + + public registerStackOperateCallback(operate: NavPathStackOperate) { + let index = this.operates.findIndex(((item) => { + return ((item) === (operate)); + })); + if (((index) === (-1))) { + this.operates.push(operate); + } + } + + public unregisterStackOperateCallback(operate: NavPathStackOperate) { + let index = this.operates.findIndex(((item) => { + return ((item) === (operate)); + })); + if (((index) !== (-1))) { + this.operates.splice(index, 1); + } + } + + public popInner(animated?: boolean): (NavPathInfo | undefined) { + console.log("MyNavPathStack pop from inner:"); + return super.pop({}, animated); + } + + public pop(animated?: boolean): (NavPathInfo | undefined) { + console.log("MyNavPathStack pop from system:"); + animated = ((((typeof animated)) === ("undefined")) ? true : animated); + let ret: (NavPathInfo | undefined) = undefined; + ret = super.pop({}, animated); + this.policyInfoList.pop(); + this.operates.forEach(((item) => { + item.onSystemPop(); + })); + return ret; + } + + public constructor() {} + +} + +interface NavPathStackOperate { + set onSystemPop(onSystemPop: (()=> void)) + + get onSystemPop(): (()=> void) + +} + +interface MultiNavPathStackOperate { + set onPrimaryPop(onPrimaryPop: (()=> void)) + + get onPrimaryPop(): (()=> void) + set onSecondaryPop(onSecondaryPop: (()=> void)) + + get onSecondaryPop(): (()=> void) + +} + +type OnNavigationModeChangeCallback = ((mode: NavigationMode)=> void); + +type OnHomeShowOnTopCallback = ((name: string)=> void); + +@Retention({policy:"SOURCE"}) @interface __Link_intrinsic {} + +@Component() export interface __Options_SubNavigation { + @__Link_intrinsic() set isPortrait(isPortrait: (boolean | undefined)) + + @__Link_intrinsic() get isPortrait(): (boolean | undefined) + set __backing_isPortrait(__backing_isPortrait: (LinkSourceType | undefined)) + + get __backing_isPortrait(): (LinkSourceType | undefined) + set displayMode(displayMode: (number | undefined)) + + get displayMode(): (number | undefined) + set __backing_displayMode(__backing_displayMode: (IStateDecoratedVariable | undefined)) + + get __backing_displayMode(): (IStateDecoratedVariable | undefined) + set navDestination(navDestination: ((((name: String, param: (Object | undefined))=> void) | undefined) | undefined)) + + get navDestination(): ((((name: String, param: (Object | undefined))=> void) | undefined) | undefined) + set primaryWidth(primaryWidth: ((number | string) | undefined)) + + get primaryWidth(): ((number | string) | undefined) + set __backing_primaryWidth(__backing_primaryWidth: (IStateDecoratedVariable<(number | string)> | undefined)) + + get __backing_primaryWidth(): (IStateDecoratedVariable<(number | string)> | undefined) + set onNavigationModeChange(onNavigationModeChange: ((OnNavigationModeChangeCallback | undefined) | undefined)) + + get onNavigationModeChange(): ((OnNavigationModeChangeCallback | undefined) | undefined) + set primaryStack(primaryStack: (MyNavPathStack | undefined)) + + get primaryStack(): (MyNavPathStack | undefined) + set __backing_primaryStack(__backing_primaryStack: (IStateDecoratedVariable | undefined)) + + get __backing_primaryStack(): (IStateDecoratedVariable | undefined) + set secondaryStack(secondaryStack: (MyNavPathStack | undefined)) + + get secondaryStack(): (MyNavPathStack | undefined) + set __backing_secondaryStack(__backing_secondaryStack: (IStateDecoratedVariable | undefined)) + + get __backing_secondaryStack(): (IStateDecoratedVariable | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test mix usage of navigation and navDestination transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/component/nav-no-lambda.test.ts b/arkui-plugins/test/ut/ui-plugins/component/nav-no-lambda.test.ts new file mode 100644 index 000000000..c6f278446 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/component/nav-no-lambda.test.ts @@ -0,0 +1,136 @@ +/* + * 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, 'nav-no-lambda.ets'), +]; + +const pluginTester = new PluginTester('test basic navigation and navDestination transformation with no trailing lambda', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { ColumnAttribute as ColumnAttribute } from "arkui.component.column"; + +import { NavigationAttribute as NavigationAttribute } from "arkui.component.navigation"; + +import { NavigationImpl as NavigationImpl } from "arkui.component.navigation"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { NavDestinationAttribute as NavDestinationAttribute } from "arkui.component.navDestination"; + +import { NavDestinationImpl as NavDestinationImpl } from "arkui.component.navDestination"; + +import { ColumnImpl as ColumnImpl } from "arkui.component.column"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Column as Column, NavDestination as NavDestination, Navigation as Navigation, NavPathStack as NavPathStack } from "@ohos.arkui.component"; + +function main() {} + + +@Component() final struct NavDestinationStruct extends CustomComponent { + public __initializeStruct(initializers: (__Options_NavDestinationStruct | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_pathStack = ((({let gensym___1107384 = initializers; + (((gensym___1107384) == (null)) ? undefined : gensym___1107384.pathStack)})) ?? (new NavPathStack())); + } + + public __updateStruct(initializers: (__Options_NavDestinationStruct | undefined)): void {} + + private __backing_pathStack?: NavPathStack; + public get pathStack(): NavPathStack { + return (this.__backing_pathStack as NavPathStack); + } + + public set pathStack(value: NavPathStack) { + this.__backing_pathStack = value; + } + + @memo() public build() { + ColumnImpl(@memo() ((instance: ColumnAttribute): void => { + instance.setColumnOptions(undefined).applyAttributesFinish(); + return; + }), @memo() (() => { + NavDestinationImpl(@memo() ((instance: NavDestinationAttribute): void => { + instance.setNavDestinationOptions({ + moduleName: "entry", + pagePath: "entry/src/main/ets/pages/new", + }).width(80).applyAttributesFinish(); + return; + })); + NavigationImpl(@memo() ((instance: NavigationAttribute): void => { + instance.setNavigationOptions(this.pathStack, { + moduleName: "entry", + pagePath: "entry/src/main/ets/pages/new", + isUserCreateStack: true, + }).width(80).applyAttributesFinish(); + return; + }), undefined); + NavigationImpl(@memo() ((instance: NavigationAttribute): void => { + instance.setNavigationOptions(undefined, { + moduleName: "entry", + pagePath: "entry/src/main/ets/pages/new", + isUserCreateStack: false, + }).width(80).applyAttributesFinish(); + return; + })); + })); + } + + public constructor() {} + +} + +@Component() export interface __Options_NavDestinationStruct { + get pathStack(): (NavPathStack | undefined) + set pathStack(pathStack: (NavPathStack | undefined)) + get __options_has_pathStack(): (boolean | undefined) + set __options_has_pathStack(__options_has_pathStack: (boolean | undefined)) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test basic navigation and navDestination transformation with no trailing lambda', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/cache/componentAttributeCache.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/cache/componentAttributeCache.ts index 7c3237a58..003e19de1 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/cache/componentAttributeCache.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/cache/componentAttributeCache.ts @@ -29,7 +29,7 @@ import { factory as builderLambdaFactory } from '../factory'; export interface ComponentRecord { name: string; attributeRecords: ParameterRecord[]; - typeParams?: TypeParameterTypeRecord[]; + typeParameters?: TypeParameterTypeRecord[]; hasRestParameter?: boolean; hasReceiver?: boolean; hasLastTrailingLambda?: boolean; @@ -109,11 +109,15 @@ export class ComponentAttributeCache { const name: string = node.name.name; const hasRestParameter = node.scriptFunction.hasRestParameter; const hasReceiver = node.scriptFunction.hasReceiver; - const typeParams = collectTypeRecordFromTypeParameterDeclaration(node.scriptFunction.typeParams); + const typeParameters = collectTypeRecordFromTypeParameterDeclaration(node.scriptFunction.typeParams); const params = node.scriptFunction.params as arkts.ETSParameterExpression[]; const attributeRecords: ParameterRecord[] = []; const hasLastTrailingLambda = checkIsTrailingLambdaInLastParam(params); params.forEach((p, index) => { + if (isNavigationOrNavDestination(name) && index === params.length - 1) { + const record = collectTypeRecordFromParameter(builderLambdaFactory.createModuleInfoParam()); + attributeRecords.push(record); + } if (index === params.length - 1 && hasLastTrailingLambda) { return; } @@ -123,7 +127,7 @@ export class ComponentAttributeCache { const componentRecord: ComponentRecord = { name, attributeRecords, - typeParams, + typeParameters, hasRestParameter, hasReceiver, hasLastTrailingLambda, diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts index 663126ff9..48fa50605 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts @@ -55,6 +55,7 @@ import { flatObjectExpressionToEntries, OptionsPropertyInfo, isNavigationOrNavDestination, + getIsUserCreateStack, } from './utils'; import { hasDecorator, isDecoratorIntrinsicAnnotation } from '../property-translators/utils'; import { BuilderFactory } from './builder-factory'; @@ -106,9 +107,6 @@ export class factory { const name: string = ident.name; const isFunctionCall: boolean = name !== BuilderLambdaNames.ORIGIN_METHOD_NAME; const newParams: arkts.Expression[] = [...prefixArgs, ...func.params]; - if (isNavigationOrNavDestination(newName ?? node.name.name)) { - newParams.splice(newParams.length - 1, 0, factory.createModuleInfoParam()); - } const updateFunc = arkts.factory .updateScriptFunction( func, @@ -536,6 +534,7 @@ export class factory { ): (arkts.AstNode | undefined)[] { const { isFunctionCall, params, returnType, moduleName, isFromCommonMethod } = declInfo; const type: arkts.Identifier | undefined = builderLambdaType(leaf); + const typeName: string | undefined = type?.name; const args: (arkts.AstNode | undefined)[] = []; const modifiedArgs: (arkts.AstNode | undefined)[] = []; const secondLastArgInfo = buildSecondLastArgInfo(type, isFunctionCall); @@ -555,7 +554,7 @@ export class factory { const canAddMemo = checkIsMemoFromMemoableInfo(memoableInfo, false); const fallback = arkts.factory.createUndefinedLiteral(); const updatedArg = this.createOrUpdateArgInBuilderLambda(fallback, arg, canAddMemo, declInfo); - modifiedArg = factory.processModifiedArg(updatedArg, index, leaf.arguments, moduleName, type?.name); + modifiedArg = factory.processModifiedArg(updatedArg, index, leaf.arguments, moduleName, typeName); } const shouldInsertToArgs = !isFunctionCall || (index === params.length - 1 && hasLastTrailingLambda); if (shouldInsertToArgs) { @@ -566,14 +565,9 @@ export class factory { }, { isTrailingCall } ); - if (isNavigationOrNavDestination(type?.name, moduleName)) { - const isUserCreateStack: boolean | undefined = - type?.name === InnerComponentNames.NAVIGATION ? leaf.arguments.length > 1 : undefined; - modifiedArgs.push(factory.createModuleInfoArg(isUserCreateStack)); - } const lambdaBody = this.addOptionsArgsToLambdaBodyInStyleArg( lambdaBodyInfo, - modifiedArgs, + factory.addArgsInBuilderLambdaCall(modifiedArgs, typeName, moduleName, leaf), typeArguments, isFromCommonMethod ); @@ -583,6 +577,22 @@ export class factory { return args; } + static addArgsInBuilderLambdaCall( + modifiedArgs: (arkts.AstNode | undefined)[], + typeName: string | undefined, + moduleName: string, + leaf: arkts.CallExpression, + ): (arkts.AstNode | undefined)[] { + if (isNavigationOrNavDestination(typeName, moduleName)) { + const length: number = leaf.arguments.length; + if (typeName === InnerComponentNames.NAVIGATION && length === 0) { + modifiedArgs.push(arkts.factory.createUndefinedLiteral()); + } + modifiedArgs.push(factory.createModuleInfoArg(getIsUserCreateStack(typeName, leaf.arguments))); + } + return modifiedArgs; + } + /** * process `@ComponentBuilder` call arguments for some specific transformation. */ @@ -1214,7 +1224,7 @@ export class factory { const name = getDeclaredSetAttribtueMethodName(record.name); const hasReceiver = !!record.hasReceiver; const params = record.attributeRecords.map((record) => TypeFactory.createParameterFromRecord(record)); - const typeParams = record.typeParams?.map((p) => TypeFactory.createTypeParameterFromRecord(p)); + const typeParams = record.typeParameters?.map((p) => TypeFactory.createTypeParameterFromRecord(p)); const key = arkts.factory.createIdentifier(name); const kind = arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD; @@ -1285,7 +1295,7 @@ export class factory { attributeTypeParams, record.hasLastTrailingLambda ); - const typeParamItems = record.typeParams?.map((p) => TypeFactory.createTypeParameterFromRecord(p)); + const typeParamItems = record.typeParameters?.map((p) => TypeFactory.createTypeParameterFromRecord(p)); const typeParams = !!typeParamItems ? arkts.factory.createTypeParameterDeclaration(typeParamItems, typeParamItems.length) : undefined; diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts index 63a99fc73..2eb9a5384 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/utils.ts @@ -687,6 +687,16 @@ export function isNavigationOrNavDestination(name: string | undefined, sourceNam ); } +export function getIsUserCreateStack( + typeName: string | undefined, + args: readonly arkts.Expression[] +): boolean | undefined { + if (typeName === InnerComponentNames.NAVIGATION) { + return args.length > 1 || (args.length === 1 && !arkts.isArrowFunctionExpression(args[0])); + } + return undefined; +} + /** * check whether the last parameter is trailing lambda in components. */ diff --git a/arkui-plugins/ui-plugins/checked-transformer.ts b/arkui-plugins/ui-plugins/checked-transformer.ts index 6ad631797..5a1fcb407 100644 --- a/arkui-plugins/ui-plugins/checked-transformer.ts +++ b/arkui-plugins/ui-plugins/checked-transformer.ts @@ -32,6 +32,8 @@ import { LoaderJson, ResourceInfo, ScopeInfoCollection, + RouterInfo, + initRouterInfo, } from './struct-translators/utils'; import { collectCustomComponentScopeInfo, @@ -54,6 +56,7 @@ export class CheckedTransformer extends AbstractVisitor { projectConfig: ProjectConfig | undefined; aceBuildJson: LoaderJson; resourceInfo: ResourceInfo; + routerInfo: Map; constructor(projectConfig: ProjectConfig | undefined) { super(); @@ -61,8 +64,8 @@ export class CheckedTransformer extends AbstractVisitor { this.scope = { customComponents: [] }; this.aceBuildJson = loadBuildJson(this.projectConfig); this.resourceInfo = initResourceInfo(this.projectConfig, this.aceBuildJson); + this.routerInfo = initRouterInfo(this.aceBuildJson); this.legacyBuilderSet = new Set(); - this.initBuilderMap(); } initBuilderMap(): void { @@ -86,7 +89,8 @@ export class CheckedTransformer extends AbstractVisitor { MetaDataCollector.getInstance() .setProjectConfig(this.projectConfig) .setAbsName(this.program?.absName) - .setExternalSourceName(this.externalSourceName); + .setExternalSourceName(this.externalSourceName) + .setRouterInfo(this.routerInfo); } reset(): void { diff --git a/arkui-plugins/ui-plugins/component-transformer.ts b/arkui-plugins/ui-plugins/component-transformer.ts index 02fadf657..634625edb 100644 --- a/arkui-plugins/ui-plugins/component-transformer.ts +++ b/arkui-plugins/ui-plugins/component-transformer.ts @@ -285,7 +285,11 @@ export class ComponentTransformer extends AbstractVisitor { node.definition.implements, undefined, node.definition.super, - [entryFactory.generateRegisterNamedRouter(), ...node.definition.body], + [ + entryFactory.generateRegisterNamedRouter(), + entryFactory.generateNavigationBuilderRegister(), + ...node.definition.body, + ], node.definition.modifiers, arkts.classDefinitionFlags(node.definition) ) diff --git a/arkui-plugins/ui-plugins/entry-translators/factory.ts b/arkui-plugins/ui-plugins/entry-translators/factory.ts index ff9273469..8f02731a0 100644 --- a/arkui-plugins/ui-plugins/entry-translators/factory.ts +++ b/arkui-plugins/ui-plugins/entry-translators/factory.ts @@ -16,7 +16,13 @@ import * as arkts from '@koalaui/libarkts'; import * as path from 'path'; import { annotation, createAndInsertImportDeclaration } from '../../common/arkts-utils'; -import { ENTRY_POINT_IMPORT_SOURCE_NAME, EntryWrapperNames, NavigationNames } from '../../common/predefines'; +import { + ENTRY_POINT_IMPORT_SOURCE_NAME, + EntryWrapperNames, + BuilderNames, + NavigationNames, + TypeNames, +} from '../../common/predefines'; import { ProjectConfig } from '../../common/plugin-context'; import { factory as uiFactory } from '../ui-factory'; import { getRelativePagePath } from './utils'; @@ -485,4 +491,37 @@ export class factory { modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, }); } + + /** + * generate `NavigationBuilderRegister` method in header arkui.UserView. + */ + static generateNavigationBuilderRegister(): arkts.MethodDefinition { + const params = [ + uiFactory.createParameterDeclaration(NavigationNames.NAME, TypeNames.STRING), + arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier( + BuilderNames.BUILDER, + uiFactory.createComplexTypeFromStringAndTypeParameter(BuilderNames.WRAPPED_BUILDER, [ + uiFactory.createTypeReferenceFromString(TypeNames.TYPE_T), + ]) + ), + undefined + ), + ]; + return uiFactory.createMethodDefinition({ + key: arkts.factory.createIdentifier(EntryWrapperNames.NAVIGATION_BUILDER_REGISTER), + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + function: { + body: arkts.factory.createBlock([]), + typeParams: arkts.factory.createTypeParameterDeclaration( + [arkts.factory.createTypeParameter(arkts.factory.createIdentifier(TypeNames.TYPE_T))], + 0 + ), + params: params, + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, + }, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, + }); + } } diff --git a/arkui-plugins/ui-plugins/struct-translators/factory.ts b/arkui-plugins/ui-plugins/struct-translators/factory.ts index a7ebee0c7..c5f5171fe 100644 --- a/arkui-plugins/ui-plugins/struct-translators/factory.ts +++ b/arkui-plugins/ui-plugins/struct-translators/factory.ts @@ -58,6 +58,7 @@ import { ObservedAnnoInfo, getNoTransformationMembersInClass, isComputedMethod, + RouterInfo, } from './utils'; import { collectStateManagementTypeImport, generateThisBacking, hasDecorator } from '../property-translators/utils'; import { isComponentAttributeInterface } from '../builder-lambda-translators/utils'; @@ -73,6 +74,9 @@ import { RESOURCE_TYPE, ARKUI_BUILDER_SOURCE_NAME, TypeNames, + EntryWrapperNames, + BuilderNames, + ENTRY_POINT_IMPORT_SOURCE_NAME, } from '../../common/predefines'; import { ObservedTranslator } from '../property-translators/index'; import { addMemoAnnotation } from '../../collectors/memo-collectors/utils'; @@ -82,6 +86,7 @@ import { MethodTranslator } from '../property-translators/base'; import { MonitorCache } from '../property-translators/cache/monitorCache'; import { PropertyCache } from '../property-translators/cache/propertyCache'; import { ComponentAttributeCache } from '../builder-lambda-translators/cache/componentAttributeCache'; +import { MetaDataCollector } from '../../common/metadata-collector'; export class factory { /** @@ -832,6 +837,66 @@ export class factory { return node; } + static createNavigationBuilderRegister(routerInfo: RouterInfo): arkts.ExpressionStatement { + ImportCollector.getInstance().collectSource(BuilderNames.WRAP_BUILDER, ARKUI_BUILDER_SOURCE_NAME); + ImportCollector.getInstance().collectImport(BuilderNames.WRAP_BUILDER); + ImportCollector.getInstance().collectSource( + EntryWrapperNames.ENTRY_POINT_CLASS_NAME, + ENTRY_POINT_IMPORT_SOURCE_NAME + ); + ImportCollector.getInstance().collectImport(EntryWrapperNames.ENTRY_POINT_CLASS_NAME); + return arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression( + UIFactory.generateMemberExpression( + arkts.factory.createIdentifier(EntryWrapperNames.ENTRY_POINT_CLASS_NAME), + EntryWrapperNames.NAVIGATION_BUILDER_REGISTER + ), + undefined, + [ + arkts.factory.createStringLiteral(routerInfo.name), + arkts.factory.createCallExpression( + arkts.factory.createIdentifier(BuilderNames.WRAP_BUILDER), + undefined, + [arkts.factory.createIdentifier(routerInfo.buildFunction)] + ), + ] + ) + ); + } + + static addNavigationBuilderRegister(): arkts.ExpressionStatement[] { + const routerInfo: Map = MetaDataCollector.getInstance().routerInfo; + const filePath: string | undefined = MetaDataCollector.getInstance().fileAbsName; + if (!filePath || !routerInfo.has(filePath)) { + return []; + } + let navigationBuilders: arkts.ExpressionStatement[] = []; + routerInfo.get(filePath)!.forEach((info: RouterInfo) => { + navigationBuilders.push(factory.createNavigationBuilderRegister(info)); + }); + return navigationBuilders; + } + + static insertMembersInClassStaticBlock(node: arkts.ClassStaticBlock): arkts.ClassStaticBlock { + if (!node.value || !node.function) { + return node; + } + const func = node.function; + const body: arkts.AstNode | undefined = func.body; + if (!body || !arkts.isBlockStatement(body)) { + return node; + } + return arkts.factory.updateClassStaticBlock( + node, + arkts.factory.updateFunctionExpression( + node.value as arkts.FunctionExpression, + UIFactory.updateScriptFunction(func, { + body: arkts.factory.createBlock([...body.statements, ...factory.addNavigationBuilderRegister()]), + }) + ) + ); + } + static transformETSGlobalClass(node: arkts.ClassDeclaration, externalSourceName?: string): arkts.ClassDeclaration { if (!node.definition) { return node; @@ -847,6 +912,8 @@ export class factory { member.modifiers, false ); + } else if (arkts.isClassStaticBlock(member)) { + return factory.insertMembersInClassStaticBlock(member); } return member; }); diff --git a/arkui-plugins/ui-plugins/struct-translators/utils.ts b/arkui-plugins/ui-plugins/struct-translators/utils.ts index a25c5bf5c..65a2d70cb 100644 --- a/arkui-plugins/ui-plugins/struct-translators/utils.ts +++ b/arkui-plugins/ui-plugins/struct-translators/utils.ts @@ -57,6 +57,7 @@ export interface ResourceInfo { export interface LoaderJson { hspResourcesMap: Record; + routerMap: RouterMap; } export interface ResourceParameter { @@ -73,6 +74,16 @@ export interface ObservedAnnoInfo { classHasTrack: boolean; } +export type RouterMap = RouterInfo & { + ohmurl: string; + pageSourceFile: string; +}; + +export type RouterInfo = { + name: string; + buildFunction: string; +}; + export type ClassScopeInfo = ObservedAnnoInfo & { getters: arkts.MethodDefinition[]; }; @@ -139,6 +150,37 @@ export function loadBuildJson(projectConfig: ProjectConfig | undefined): any { return {}; } +/** + * Initializes routerInfo which maps absolute file paths to corresponding build functions + * + * @param aceBuildJson content of the file 'loader.json'. + */ +export function initRouterInfo(aceBuildJson: Partial): Map { + const routerInfo: Map = new Map(); + if (!aceBuildJson || !aceBuildJson.routerMap || !Array.isArray(aceBuildJson.routerMap)) { + return routerInfo; + } + aceBuildJson.routerMap.forEach((item) => { + if (item.pageSourceFile && item.name && item.buildFunction) { + setRouterInfo(routerInfo, item); + } + }); + return routerInfo; +} + +/** + * set router info based on the information of router map. + */ +function setRouterInfo(routerInfo: Map, routerMapItem: RouterMap): void { + const { pageSourceFile, name, buildFunction } = routerMapItem; + const filePath = path.resolve(pageSourceFile); + if (routerInfo.has(filePath)) { + routerInfo.get(filePath)!.push({ name: name, buildFunction: buildFunction }); + } else { + routerInfo.set(filePath, [{ name: name, buildFunction: buildFunction }]); + } +} + /** * Initialize all resources information, including app resources, system resources, dependent hap resources and rawfile resources. * -- Gitee