diff --git a/arkui-plugins/common/metadata-collector.ts b/arkui-plugins/common/metadata-collector.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9f9dd5443b94da22eb5170c795ae7d2289eea87 --- /dev/null +++ b/arkui-plugins/common/metadata-collector.ts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ProjectConfig } from './plugin-context'; + +export class MetaDataCollector { + public projectConfig: ProjectConfig | undefined; + public fileAbsName: string | undefined; + private static instance: MetaDataCollector; + + static getInstance(): MetaDataCollector { + if (!this.instance) { + this.instance = new MetaDataCollector(); + } + return this.instance; + } + + setProjectConfig(config: ProjectConfig | undefined): this { + this.projectConfig = config; + return this; + } + + setAbsName(fileName: string | undefined) { + this.fileAbsName = fileName; + return this; + } + + reset(): void { + this.projectConfig = undefined; + this.fileAbsName = undefined; + } +} \ No newline at end of file diff --git a/arkui-plugins/common/predefines.ts b/arkui-plugins/common/predefines.ts index 87bdd941348238e34a500f4bb95d6523babd0a68..655939ea4d7476dfbc5aa1eb97b1f7a980c9ea7b 100644 --- a/arkui-plugins/common/predefines.ts +++ b/arkui-plugins/common/predefines.ts @@ -40,6 +40,9 @@ 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_COMPONENT_NAVIGATION = 'arkui.component.navigation'; +export const ARKUI_COMPONENT_NAVDESTINATION = 'arkui.component.navDestination'; +export const ARKUI_COMPONENT_BUILDER = 'arkui.component.builder'; export enum ModuleType { HAR = 'har', @@ -182,6 +185,17 @@ export enum NavigationNames { PAGE_PATH = 'pagePath', PAGE_FULL_PATH = 'pageFullPath', INTEGRATED_HSP = 'integratedHsp', + NAVIGATION = 'Navigation', + NAVIGATION_MODULE_INFO = 'NavigationModuleInfo', + IS_USER_CREATE_STATCK = 'isUserCreateStack', + MODULE_INFO = 'moduleInfo', + NAVDESTINATION = 'NavDestination', + NAVIGATION_BUILDER_REGISTER = 'NavigationBuilderRegister', +} + +export enum BuilderNames { + WRAPPED_BUILDER = 'WrappedBuilder', + WRAP_BUILDER = 'wrapBuilder' } export const RESOURCE_TYPE: Record = { @@ -258,7 +272,9 @@ export const INTERMEDIATE_IMPORT_SOURCE: Map = new Map implements AnimatableArithmetic { + pointX: number = 1 + pointY: number = 1 + plus(rhs: AnimatableArithmetic): PointVector {return new PointVector()} + subtract(rhs: AnimatableArithmetic): PointVector{return new PointVector()} + multiply(scale: number): PointVector{return new PointVector()} + equals(rhs: AnimatableArithmetic): boolean{return true} +} + +@AnimatableExtend +function animatablePoint (this: TextAttribute, point: PointVector): this{ + this.width(point.pointX); + this.height(point.pointY); + return this +} + +@Entry() @Component -struct AnimatablePropertyExample { +struct MyStateSample { + @State width1: number = 80 build() { - Column() { + Column(){ Text("AnimatableProperty") - .backgroundColor(Color.Red) - .animation({ duration: 2000, curve: Curve.Ease }) - .fontSize(20) - .animation({ duration: 2000, curve: Curve.Ease }) - .width("100%") + .animatableWidth(this.width1) } } } \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/navigation/navdestination-basic.ets b/arkui-plugins/test/demo/mock/navigation/navdestination-basic.ets new file mode 100644 index 0000000000000000000000000000000000000000..c173e8ff33e3afe6731abecdc3cc6bafe6442f6e --- /dev/null +++ b/arkui-plugins/test/demo/mock/navigation/navdestination-basic.ets @@ -0,0 +1,28 @@ +/* + * 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, NavDestination, Column, Button } from "@ohos.arkui.component" + +@Component +struct MyStateSample { + + build() { + NavDestination(){ + Column(){ + Button('abc') + } + }.width(80) + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/navigation/navigation-basic.ets b/arkui-plugins/test/demo/mock/navigation/navigation-basic.ets new file mode 100644 index 0000000000000000000000000000000000000000..932f7b870908e85a91181c9eca58946b43a3fa2d --- /dev/null +++ b/arkui-plugins/test/demo/mock/navigation/navigation-basic.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, Navigation, NavPathStack, Column, Button } from "@ohos.arkui.component" + +@Component +struct MyStateSample { + pathStack: NavPathStack = new NavPathStack() + + build() { + Navigation(this.pathStack){ + Column(){ + Button('abc') + } + }.width(80) + } +} \ No newline at end of file diff --git a/arkui-plugins/test/ut/ui-plugins/animation/animatable-extend-basic.test.ts b/arkui-plugins/test/ut/ui-plugins/animation/animatable-extend-basic.test.ts index d0f5ac29d50499a8173da1d11a6ccb61832c8924..beaa83a34e4ef9244a5266c2f5767e952d33f162 100644 --- a/arkui-plugins/test/ut/ui-plugins/animation/animatable-extend-basic.test.ts +++ b/arkui-plugins/test/ut/ui-plugins/animation/animatable-extend-basic.test.ts @@ -36,14 +36,20 @@ const animatableExtendTransform: Plugins = { parsed: uiTransform().parsed, }; -const pluginTester = new PluginTester('test basic animatableExtend transform', buildConfig); +const pluginTester = new PluginTester('test basic AnimatableExtend transform', buildConfig); const expectedScript: string = ` +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { IStateDecoratedVariable as IStateDecoratedVariable } from "arkui.stateManagement.decorator"; + import { memo as memo } from "arkui.stateManagement.runtime"; import { TextAttribute as TextAttribute } from "arkui.component.text"; +import { AnimatableArithmetic as AnimatableArithmetic } from "arkui.component.common"; + import { NavInterface as NavInterface } from "arkui.UserView"; import { PageLifeCycle as PageLifeCycle } from "arkui.component.customComponent"; @@ -56,9 +62,9 @@ import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customCo import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; -import { Text as Text, Column as Column, Component as Component, Color as Color, Curve as Curve } from "@ohos.arkui.component"; +import { Component as Component, TextAttribute as TextAttribute, Text as Text, Entry as Entry, AnimatableArithmetic as AnimatableArithmetic, Column as Column, AnimatableExtend as AnimatableExtend } from "@ohos.arkui.component"; -import { Entry as Entry } from "@ohos.arkui.component"; +import { State as State } from "@ohos.arkui.stateManagement"; function main() {} @@ -68,29 +74,82 @@ __EntryWrapper.RegisterNamedRouter("", new __EntryWrapper(), ({ pagePath: "../../../animation/animatable-extend-basic", pageFullPath: "test/demo/mock/animation/animatable-extend-basic", integratedHsp: "false", - } as NavInterface)); +} as NavInterface)); +@AnimatableExtend() function animatableWidth(this: TextAttribute, width: number): TextAttribute { + this.__createOrSetAnimatableProperty("animatableWidth", width, ((width: (number | AnimatableArithmetic)) => { + width = (width as number); + this.width(width); + this.height(width); + })); + return this; +} + +@AnimatableExtend() function animatablePoint(this: TextAttribute, point: PointVector): TextAttribute { + this.__createOrSetAnimatableProperty("animatablePoint", point, ((point: (number | AnimatableArithmetic)) => { + point = (point as PointVector); + this.width(point.pointX); + this.height(point.pointY); + })); + return this; +} + + +class Point { + public x: number = 2; + + public y: number = 3; + + public constructor() {} + +} -@Entry({useSharedStorage:false,storage:"",routeName:""}) @Component() final struct AnimatablePropertyExample extends CustomComponent implements PageLifeCycle { - public __initializeStruct(initializers: (__Options_AnimatablePropertyExample | undefined), @memo() content: ((()=> void) | undefined)): void {} +class PointVector extends Array implements AnimatableArithmetic { + public pointX: number = 1; - public __updateStruct(initializers: (__Options_AnimatablePropertyExample | undefined)): void {} + public pointY: number = 1; + + public plus(rhs: AnimatableArithmetic): PointVector { + return new PointVector(); + } + + public subtract(rhs: AnimatableArithmetic): PointVector { + return new PointVector(); + } + + public multiply(scale: number): PointVector { + return new PointVector(); + } + + public equals(rhs: AnimatableArithmetic): boolean { + return true; + } + + public constructor() {} + +} + +@Entry({useSharedStorage:false,storage:"",routeName:""}) @Component() final struct MyStateSample extends CustomComponent implements PageLifeCycle { + public __initializeStruct(initializers: (__Options_MyStateSample | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_width1 = STATE_MGMT_FACTORY.makeState(this, "width1", ((({let gensym___149527867 = initializers; + (((gensym___149527867) == (null)) ? undefined : gensym___149527867.width1)})) ?? (80))); + } + + public __updateStruct(initializers: (__Options_MyStateSample | undefined)): void {} + + private __backing_width1?: IStateDecoratedVariable; + + public get width1(): number { + return this.__backing_width1!.get(); + } + + public set width1(value: number) { + this.__backing_width1!.set(value); + } @memo() public build() { - Column(undefined, undefined, (() => { + Column(undefined, undefined, @memo() (() => { Text(@memo() ((instance: TextAttribute): void => { - instance.animationStart({ - duration: 2000, - curve: Curve.Ease, - }).backgroundColor(Color.Red).animationStop({ - duration: 2000, - curve: Curve.Ease, - }).animationStart({ - duration: 2000, - curve: Curve.Ease, - }).fontSize(20).animationStop({ - duration: 2000, - curve: Curve.Ease, - }).width("100%"); + animatableWidth(instance, this.width1); return; }), "AnimatableProperty", undefined, undefined); })); @@ -100,14 +159,20 @@ __EntryWrapper.RegisterNamedRouter("", new __EntryWrapper(), ({ } -@Entry({useSharedStorage:false,storage:"",routeName:""}) @Component() export interface __Options_AnimatablePropertyExample { +@Entry({useSharedStorage:false,storage:"",routeName:""}) @Component() export interface __Options_MyStateSample { + set width1(width1: (number | undefined)) + + get width1(): (number | undefined) + set __backing_width1(__backing_width1: (IStateDecoratedVariable | undefined)) + + get __backing_width1(): (IStateDecoratedVariable | undefined) } class __EntryWrapper extends EntryPoint { @memo() public entry(): void { - AnimatablePropertyExample._instantiateImpl(undefined, (() => { - return new AnimatablePropertyExample(); + MyStateSample._instantiateImpl(undefined, (() => { + return new MyStateSample(); }), undefined, undefined, undefined); } @@ -126,10 +191,10 @@ function testAnimatableExtendTransformer(this: PluginTestContext): void { } pluginTester.run( - 'test basic animation transform', + 'test basic AnimatableExtend transform', [animatableExtendTransform, uiNoRecheck, recheck], { - checked: [testAnimatableExtendTransformer], + 'checked:ui-no-recheck': [testAnimatableExtendTransformer], }, { stopAfter: 'checked', diff --git a/arkui-plugins/test/ut/ui-plugins/animation/animation-basic.test.ts b/arkui-plugins/test/ut/ui-plugins/animation/animation-basic.test.ts index ac52cd1e5823253a82f4eeb27ca53abfebb99c25..812ebc218c35b6d2c7a5e20417487dd20a3b646b 100644 --- a/arkui-plugins/test/ut/ui-plugins/animation/animation-basic.test.ts +++ b/arkui-plugins/test/ut/ui-plugins/animation/animation-basic.test.ts @@ -75,7 +75,7 @@ __EntryWrapper.RegisterNamedRouter("", new __EntryWrapper(), ({ public __updateStruct(initializers: (__Options_AnimatablePropertyExample | undefined)): void {} @memo() public build() { - Column(undefined, undefined, (() => { + Column(undefined, undefined, @memo() (() => { Text(@memo() ((instance: TextAttribute): void => { instance.animationStart({ duration: 2000, @@ -117,19 +117,20 @@ class __EntryWrapper extends EntryPoint { const expectedHeader = ` - animationStart(value: AnimateParam | undefined): this - animationStop(value: AnimateParam | undefined): this + animationStart(value: (AnimateParam | undefined)): this + animationStop(value: (AnimateParam | undefined)): this `; function testAnimationTransformer(this: PluginTestContext): void { expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); + expect(parseDumpSrc(this.declContexts?.['arkui.component.common']?.scriptSnapshot ?? '')).toContain(parseDumpSrc(expectedHeader)); } pluginTester.run( 'test basic animation transform', [animationTransform, uiNoRecheck, recheck], { - checked: [testAnimationTransformer], + 'checked:ui-no-recheck': [testAnimationTransformer], }, { stopAfter: 'checked', diff --git a/arkui-plugins/test/ut/ui-plugins/navigation/navdestination_basic.test.ts b/arkui-plugins/test/ut/ui-plugins/navigation/navdestination_basic.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..25b6257ac8e20ca33363d353b11e9943ba4cc194 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/navigation/navdestination_basic.test.ts @@ -0,0 +1,116 @@ +/* + * 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 NAVIGATION_DIR_PATH: string = 'navigation'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, NAVIGATION_DIR_PATH, 'navdestination-basic.ets'), +]; + +const navigationTransform: Plugins = { + name: 'navdestination', + parsed: uiTransform().parsed, +}; + +const pluginTester = new PluginTester('test basic navdestination transform', buildConfig); + +const expectedScript: string = ` + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { NavDestinationAttribute as NavDestinationAttribute } from "arkui.component.navDestination"; + +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, NavDestination as NavDestination, Column as Column, Button as Button } from "@ohos.arkui.component"; + +function main() {} + + + +@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() { + NavDestination(@memo() ((instance: NavDestinationAttribute): void => { + instance.width(80); + return; + }), { + moduleName: "entry", + pagePath: "../../../navigation/navdestination-basic", + }, @memo() (() => { + Column(undefined, undefined, @memo() (() => { + Button(undefined, "abc", undefined, undefined); + })); + })); + } + + private constructor() {} + +} + +@Component() export interface __Options_MyStateSample { + +} +`; + +const expectedHeader = + ` + @memo() export function NavDestination(style: (@memo() ((instance: NavDestinationAttribute)=> void) | undefined), moduleInfo: NavigationModuleInfo, @memo() content_?: (()=> void)): void + `; + +const expectedHeader1 = `export interface NavigationModuleInfo { + set moduleName(moduleName: string) + + get moduleName(): string + set pagePath(pagePath: string) + + get pagePath(): string +}`; + +function testNavDestinationTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); + expect(parseDumpSrc(this.declContexts?.['arkui.component.navDestination']?.scriptSnapshot ?? '')).toContain(parseDumpSrc(expectedHeader)); + expect(parseDumpSrc(this.declContexts?.['arkui.component.navDestination']?.scriptSnapshot ?? '')).toContain(parseDumpSrc(expectedHeader1)); +} + +pluginTester.run( + 'test basic NavDestination transform', + [navigationTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testNavDestinationTransformer], + }, + { + stopAfter: 'checked', + tracing: { externalSourceNames: ['arkui.component.navDestination'] }, + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/navigation/navigation_basic.test.ts b/arkui-plugins/test/ut/ui-plugins/navigation/navigation_basic.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..087d303ad4d2944d2549555c3d6e2367e17e81f7 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/navigation/navigation_basic.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 NAVIGATION_DIR_PATH: string = 'navigation'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, NAVIGATION_DIR_PATH, 'navigation-basic.ets'), +]; + +const navigationTransform: Plugins = { + name: 'navigation', + parsed: uiTransform().parsed, +}; + +const pluginTester = new PluginTester('test basic navigation transform', buildConfig); + +const expectedScript: string = ` + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { NavigationAttribute as NavigationAttribute } from "arkui.component.navigation"; + +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, Navigation as Navigation, NavPathStack as NavPathStack, Column as Column, Button as Button } from "@ohos.arkui.component"; + +function main() {} + + + +@Component() final struct MyStateSample extends CustomComponent { + public __initializeStruct(initializers: (__Options_MyStateSample | 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_MyStateSample | 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() { + Navigation(@memo() ((instance: NavigationAttribute): void => { + instance.width(80); + return; + }), this.pathStack, { + moduleName: "entry", + pagePath: "../../../navigation/navigation-basic", + isUserCreateStack: true, + }, @memo() (() => { + Column(undefined, undefined, @memo() (() => { + Button(undefined, "abc", undefined, undefined); + })); + })); + } + + private constructor() {} + +} + +@Component() export interface __Options_MyStateSample { + set pathStack(pathStack: (NavPathStack | undefined)) + + get pathStack(): (NavPathStack | undefined) + +} +`; + +const expectedHeader = + ` + @memo() export function Navigation(style: (@memo() ((instance: NavigationAttribute)=> void) | undefined), pathInfos: NavPathStack, moduleInfo: NavigationModuleInfo, @memo() content_?: (()=> void)): void + `; + +const expectedHeader1 = `export interface NavigationModuleInfo { + set moduleName(moduleName: string) + + get moduleName(): string + set pagePath(pagePath: string) + + get pagePath(): string + set isUserCreateStack(isUserCreateStack: boolean) + + get isUserCreateStack(): boolean +}`; + +function testNavigationTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); + expect(parseDumpSrc(this.declContexts?.['arkui.component.navigation']?.scriptSnapshot ?? '')).toContain(parseDumpSrc(expectedHeader)); + expect(parseDumpSrc(this.declContexts?.['arkui.component.navigation']?.scriptSnapshot ?? '')).toContain(parseDumpSrc(expectedHeader1)); +} + +pluginTester.run( + 'test basic navigation transform', + [navigationTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testNavigationTransformer], + }, + { + stopAfter: 'checked', + tracing: { externalSourceNames: ['arkui.component.navigation'] }, + } +); diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/builder-lambda-transformer.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/builder-lambda-transformer.ts index aba68967cd55f7aa67be5962fa72a91821f137ad..2b52188796e2f250bfe269961edb8309e281865e 100644 --- a/arkui-plugins/ui-plugins/builder-lambda-translators/builder-lambda-transformer.ts +++ b/arkui-plugins/ui-plugins/builder-lambda-translators/builder-lambda-transformer.ts @@ -18,6 +18,7 @@ import { ImportCollector } from '../../common/import-collector'; import { isBuilderLambda, isBuilderLambdaMethodDecl } from './utils'; import { factory } from './factory'; import { ProjectConfig } from '../../common/plugin-context'; +import { MetaDataCollector } from '../../common/metadata-collector'; export class BuilderLambdaTransformer extends AbstractVisitor { projectConfig: ProjectConfig | undefined; @@ -33,6 +34,7 @@ export class BuilderLambdaTransformer extends AbstractVisitor { } visitor(beforeChildren: arkts.AstNode): arkts.AstNode { + MetaDataCollector.getInstance().setProjectConfig(this.projectConfig).setAbsName(this.program?.absName); if (arkts.isCallExpression(beforeChildren) && isBuilderLambda(beforeChildren)) { const lambda = factory.transformBuilderLambda(beforeChildren); return this.visitEachChild(lambda); diff --git a/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts b/arkui-plugins/ui-plugins/builder-lambda-translators/factory.ts index 4aede630988609c741f39ea51db5b1e189fee1d8..a323a4cbeeddcddb254e26160f0e036e693240b2 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 * as path from 'path'; import { BuilderLambdaNames } from '../utils'; import { backingField, @@ -47,9 +48,20 @@ 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 { + AnimationNames, + BindableDecl, + DecoratorIntrinsicNames, + DecoratorNames, + NavigationNames, + ARKUI_COMPONENT_NAVDESTINATION, + ARKUI_COMPONENT_NAVIGATION, +} from '../../common/predefines'; import { ImportCollector } from '../../common/import-collector'; +import { MetaDataCollector } from '../../common/metadata-collector'; import { addMemoAnnotation, collectMemoableInfoInParameter } from '../../collectors/memo-collectors/utils'; +import { getRelativePagePath } from '../entry-translators/utils'; +import { factory as uiFactory } from '../ui-factory'; export class factory { /** @@ -65,7 +77,16 @@ export class factory { const func: arkts.ScriptFunction = node.scriptFunction; let newParams: arkts.Expression[] = []; if (func.params.length > 0) { - newParams.push(...prefixArgs, ...func.params); + newParams.push(...prefixArgs); + newParams.push(...func.params.slice(0, func.params.length - 1)); + if ( + (externalSourceName === ARKUI_COMPONENT_NAVIGATION && node.name.name === NavigationNames.NAVIGATION) || + (externalSourceName === ARKUI_COMPONENT_NAVDESTINATION && + node.name.name === NavigationNames.NAVDESTINATION) + ) { + newParams.push(this.createModuleInfoArgForNavigation()); + } + newParams.push(func.params.at(func.params.length - 1)!); } const updateFunc = arkts.factory .updateScriptFunction( @@ -401,6 +422,15 @@ export class factory { leaf.arguments, params, (arg, param, index) => { + if ( + (type?.name === NavigationNames.NAVIGATION || type?.name === NavigationNames.NAVDESTINATION) && + index === params.length - 1 + ) { + const isUserCreateStack = + type?.name === NavigationNames.NAVIGATION ? leaf.arguments.length > 1 : undefined; + const moduleInfoObjExpr = factory.moduleInfoObjExpr(isUserCreateStack); + args.push(this.createOrUpdateArgInBuilderLambda(moduleInfoObjExpr, undefined, undefined)); + } let modifiedArg: arkts.AstNode | undefined; if (index === params.length - 2 && !arg) { modifiedArg = this.createSecondLastArgInBuilderLambda(secondLastArgInfo); @@ -423,6 +453,55 @@ export class factory { return filterDefined(args); } + /** + * generate `moduleInfo: NavigationModuleInfo` in `@ComponentBuilder` Navigation and NavDestination + */ + static createModuleInfoArgForNavigation(): arkts.ETSParameterExpression { + return arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier( + NavigationNames.MODULE_INFO, + uiFactory.createTypeReferenceFromString(NavigationNames.NAVIGATION_MODULE_INFO) + ), + undefined + ); + } + + /** + * create moduleInfo param in Navigation and NavDestination + */ + static moduleInfoObjExpr( + isUserCreateStack?: boolean + ): arkts.ObjectExpression { + const projectConfig = MetaDataCollector.getInstance().projectConfig; + const fileAbsName = MetaDataCollector.getInstance().fileAbsName; + const projectRoot = projectConfig?.moduleRootPath + ? path.join(projectConfig.moduleRootPath, 'src', 'main', 'ets') + : ''; + const properties = [ + arkts.factory.createProperty( + arkts.factory.createIdentifier(NavigationNames.MODULE_NAME), + arkts.factory.createStringLiteral(projectConfig?.moduleName ?? '') + ), + arkts.factory.createProperty( + arkts.factory.createIdentifier(NavigationNames.PAGE_PATH), + arkts.factory.createStringLiteral(getRelativePagePath(projectRoot, fileAbsName ?? '')) + ), + ]; + if (isUserCreateStack !== undefined) { + properties.push( + arkts.factory.createProperty( + arkts.factory.createIdentifier(NavigationNames.IS_USER_CREATE_STATCK), + arkts.factory.createBooleanLiteral(isUserCreateStack) + ) + ); + } + return arkts.factory.createObjectExpression( + arkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + properties, + false + ); + } + /** * update if-else in trailing lambda contents in a builder lambda call. */ diff --git a/arkui-plugins/ui-plugins/checked-transformer.ts b/arkui-plugins/ui-plugins/checked-transformer.ts index 0dbc8b4458dfb19a07dc4ca473510db86443c33a..83e0d9cb17983ab2a6baed7a550c96dede22708b 100644 --- a/arkui-plugins/ui-plugins/checked-transformer.ts +++ b/arkui-plugins/ui-plugins/checked-transformer.ts @@ -33,16 +33,20 @@ import { LoaderJson, ResourceInfo, ScopeInfoCollection, - isForEachDecl + isForEachDecl, + initNavigation, + RouterInfo } from './struct-translators/utils'; import { collectCustomComponentScopeInfo, CustomComponentNames, isCustomComponentClass } from './utils'; import { findAndCollectMemoableNode } from '../collectors/memo-collectors/factory'; +import { MetaDataCollector } from '../common/metadata-collector'; export class CheckedTransformer extends AbstractVisitor { private scope: ScopeInfoCollection; projectConfig: ProjectConfig | undefined; - aceBuildJson: LoaderJson; + aceBuildJson: Partial; resourceInfo: ResourceInfo; + navigationInfo: Map; constructor(projectConfig: ProjectConfig | undefined) { super(); @@ -50,6 +54,7 @@ export class CheckedTransformer extends AbstractVisitor { this.scope = { customComponents: [] }; this.aceBuildJson = loadBuildJson(this.projectConfig); this.resourceInfo = initResourceInfo(this.projectConfig, this.aceBuildJson); + this.navigationInfo = initNavigation(this.aceBuildJson); } reset(): void { @@ -59,6 +64,7 @@ export class CheckedTransformer extends AbstractVisitor { ImportCollector.getInstance().reset(); DeclarationCollector.getInstance().reset(); LogCollector.getInstance().reset(); + MetaDataCollector.getInstance().reset(); } enter(node: arkts.AstNode): void { @@ -107,6 +113,7 @@ export class CheckedTransformer extends AbstractVisitor { visitor(beforeChildren: arkts.AstNode): arkts.AstNode { this.enter(beforeChildren); + MetaDataCollector.getInstance().setProjectConfig(this.projectConfig).setAbsName(this.program?.absName) if (arkts.isCallExpression(beforeChildren) && isBuilderLambda(beforeChildren)) { const lambda = builderLambdaFactory.transformBuilderLambda(beforeChildren); return this.visitEachChild(lambda); @@ -134,7 +141,7 @@ export class CheckedTransformer extends AbstractVisitor { entryFactory.addMemoToEntryWrapperClassMethods(node); return node; } else if (arkts.isClassDeclaration(node)) { - return structFactory.transformNormalClass(node); + return structFactory.transformNormalClass(node, this.isExternal, this.navigationInfo, this.program?.absName); } else if (arkts.isCallExpression(node)) { return structFactory.transformCallExpression(node, this.projectConfig, this.resourceInfo); } else if (arkts.isMethodDefinition(node) && isForEachDecl(node, this.externalSourceName)) { diff --git a/arkui-plugins/ui-plugins/component-transformer.ts b/arkui-plugins/ui-plugins/component-transformer.ts index 6c9642d8e46c0442da952b7042435312c9dbdc9e..25649683fa484173d5d22d98caf02199265c4470 100644 --- a/arkui-plugins/ui-plugins/component-transformer.ts +++ b/arkui-plugins/ui-plugins/component-transformer.ts @@ -49,7 +49,9 @@ import { DECORATOR_TYPE_MAP, ENTRY_POINT_IMPORT_SOURCE_NAME, NavigationNames, - EntryWrapperNames + EntryWrapperNames, + ARKUI_COMPONENT_NAVIGATION, + ARKUI_COMPONENT_NAVDESTINATION } from '../common/predefines'; import { generateInstantiateInterop } from './interop/interop'; import { createCustomDialogMethod, isNewCustomDialogController, transformController } from './customdialog'; @@ -221,6 +223,15 @@ export class ComponentTransformer extends AbstractVisitor { navInterface.modifiers = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT; return arkts.factory.updateEtsScript(node, [...node.statements, navInterface]); } + if (this.isExternal && (this.externalSourceName === ARKUI_COMPONENT_NAVIGATION || + this.externalSourceName === ARKUI_COMPONENT_NAVDESTINATION) + ) { + const navModuleInfo = entryFactory.createNavModuleInfo( + this.externalSourceName === ARKUI_COMPONENT_NAVIGATION + ); + navModuleInfo.modifiers = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT; + return arkts.factory.updateEtsScript(node, [...node.statements, navModuleInfo]); + } if (this.isExternal && this.componentInterfaceCollection.length === 0 && this.entryNames.length === 0) { return node; } @@ -281,7 +292,7 @@ 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 e4dd29d224a73bb929913285ac9d5fb2cb602a91..0efe3c8ab69f0400f4a636e3c005bb34597e0ff0 100644 --- a/arkui-plugins/ui-plugins/entry-translators/factory.ts +++ b/arkui-plugins/ui-plugins/entry-translators/factory.ts @@ -16,7 +16,12 @@ 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, + NavigationNames, + BuilderNames, +} from '../../common/predefines'; import { ProjectConfig } from '../../common/plugin-context'; import { factory as uiFactory } from '../ui-factory'; import { getRelativePagePath } from './utils'; @@ -464,4 +469,76 @@ export class factory { modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC, }); } + + /** + * generate interface NavigationModuleInfo in `arkui.component.navDestination` & `arkui.component.navDestination` + */ + static createNavModuleInfo(isNavigation: boolean): arkts.TSInterfaceDeclaration { + const body = [ + factory.createClassProp(NavigationNames.MODULE_NAME), + factory.createClassProp(NavigationNames.PAGE_PATH), + ]; + if (isNavigation) { + body.push( + arkts.factory.createClassProperty( + arkts.factory.createIdentifier(NavigationNames.IS_USER_CREATE_STATCK), + undefined, + arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_BOOLEAN), + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + ) + ); + } + const NavigationModuleInfo = arkts.factory.createInterfaceDeclaration( + [], + arkts.factory.createIdentifier(NavigationNames.NAVIGATION_MODULE_INFO), + undefined, + arkts.factory.createInterfaceBody(body), + false, + false + ); + NavigationModuleInfo.modifiers = arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_EXPORT; + return NavigationModuleInfo; + } + + /** + * generate generateNavigationBuilderRegister method in header arkui.UserView + */ + static generateNavigationBuilderRegister() { + const params = [ + arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier('name', uiFactory.createTypeReferenceFromString('string')), + undefined + ), + arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier( + 'builder', + arkts.factory.createTypeReference( + arkts.factory.createTypeReferencePart( + arkts.factory.createIdentifier(BuilderNames.WRAPPED_BUILDER), + arkts.factory.createTSTypeParameterInstantiation([ + uiFactory.createTypeReferenceFromString('T'), + ]) + ) + ) + ), + undefined + ), + ]; + return uiFactory.createMethodDefinition({ + key: arkts.factory.createIdentifier(NavigationNames.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('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 c450bf11ee2ede2d78346f0346b0c250e13e5171..bb8fcaed0f31222dffe596c99121bf3e6d0e0402 100644 --- a/arkui-plugins/ui-plugins/struct-translators/factory.ts +++ b/arkui-plugins/ui-plugins/struct-translators/factory.ts @@ -15,6 +15,7 @@ import * as arkts from '@koalaui/libarkts'; import { + BuilderLambdaNames, CustomComponentNames, getCustomComponentOptionsName, getGettersFromClassDecl, @@ -47,6 +48,7 @@ import { getResourceParams, isResourceNode, isForEachCall, + RouterInfo } from './utils'; import { collectStateManagementTypeImport, hasDecorator, PropertyCache } from '../property-translators/utils'; import { ProjectConfig } from '../../common/plugin-context'; @@ -59,10 +61,14 @@ import { ModuleType, StateManagementTypes, RESOURCE_TYPE, + NavigationNames, + ARKUI_COMPONENT_BUILDER, + BuilderNames, } from '../../common/predefines'; import { ObservedTrackTranslator } from '../property-translators/observedTrack'; import { addMemoAnnotation } from '../../collectors/memo-collectors/utils'; import { generateArkUICompatible, isArkUICompatible } from '../interop/interop'; +import { BUILD_NAME } from 'ui-syntax-plugins/utils'; export class factory { /** @@ -661,22 +667,26 @@ export class factory { return node; } - static transformNormalClass(node: arkts.ClassDeclaration): arkts.ClassDeclaration { + static transformNormalClass( + node: arkts.ClassDeclaration, + isExternal: boolean, + navigationInfo: Map, + fileName?: string + ): arkts.ClassDeclaration { if (!node.definition) { return node; } if (isEtsGlobalClass(node)) { - const updatedBody = node.definition.body.map((member: arkts.AstNode) => { - arkts.isMethodDefinition(member) && propertyFactory.addMemoToBuilderClassMethod(member); - if (arkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.ANIMATABLE_EXTEND)) { - member = arkts.factory.updateMethodDefinition( - member, - member.kind, - member.name, - factory.transformAnimatableExtend(member.scriptFunction), - member.modifiers, - false - ); + const updatedBody = node.definition.body.map((member) => { + if (!arkts.isMethodDefinition(member)) { + return member; + } + propertyFactory.addMemoToBuilderClassMethod(member); + if (hasDecorator(member, DecoratorNames.ANIMATABLE_EXTEND)) { + return factory.transformAnimatableExtend(member); + } + if (member.name.name === '_$init$_' && !isExternal) { + return factory.transformRouterMapBuilder(member, navigationInfo, fileName); } return member; }); @@ -700,6 +710,71 @@ export class factory { return arkts.factory.updateClassDeclaration(node, newClassDef); } + /* + * transform build functions in router map + */ + static transformRouterMapBuilder( + node: arkts.MethodDefinition, + navigationInfo: Map, + fileName?: string + ) { + if (node.scriptFunction.body && arkts.isBlockStatement(node.scriptFunction.body)) { + return uiFactory.updateMethodDefinition(node, { + function: { + body: arkts.factory.updateBlock(node.scriptFunction.body, [ + ...node.scriptFunction.body.statements, + ...factory.createNavigationInit(navigationInfo, fileName), + ]), + }, + }); + } + return node; + } + + /* + * for current file's build functions in router map, create NavigationBuilderRegister functions + */ + static createNavigationInit( + navigationInfo: Map, + fileName?: string + ): arkts.ExpressionStatement[] { + const builderStatements: arkts.ExpressionStatement[] = []; + if (!fileName) return builderStatements; + const routerInfoArr: Array | undefined = navigationInfo.get(fileName); + routerInfoArr?.forEach((item) => { + builderStatements.push(factory.createNavigationRegister(item)); + }); + return builderStatements; + } + + /* + * create NavigationBuilderRegister('...', wrapBuilder(...)) + */ + static createNavigationRegister(routerInfo: RouterInfo): arkts.ExpressionStatement { + ImportCollector.getInstance().collectSource(BuilderNames.WRAP_BUILDER, ARKUI_COMPONENT_BUILDER); + ImportCollector.getInstance().collectImport(BuilderNames.WRAP_BUILDER); + return arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression( + arkts.factory.createMemberExpression( + arkts.factory.createIdentifier('EntryPoint'), + arkts.factory.createIdentifier(NavigationNames.NAVIGATION_BUILDER_REGISTER), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + undefined, + [ + arkts.factory.createStringLiteral(routerInfo.name), + arkts.factory.createCallExpression( + arkts.factory.createIdentifier(BuilderNames.WRAP_BUILDER), + undefined, + [arkts.factory.createIdentifier(routerInfo.buildFunction)] + ), + ] + ) + ); + } + static updateObservedTrackClassDef(node: arkts.ClassDefinition): arkts.ClassDefinition { const isObserved: boolean = hasDecorator(node, DecoratorNames.OBSERVED); const classHasTrack: boolean = node.body.some( @@ -817,13 +892,17 @@ export class factory { /* * transform @AnimatableExtend method */ - static transformAnimatableExtend(node: arkts.ScriptFunction): arkts.ScriptFunction { - if (!arkts.isEtsParameterExpression(node.params[1]) || !node.body || !arkts.isBlockStatement(node.body)) { + static transformAnimatableExtend(node: arkts.MethodDefinition): arkts.MethodDefinition { + if ( + !arkts.isEtsParameterExpression(node.scriptFunction.params[1]) || + !node.scriptFunction.body || + !arkts.isBlockStatement(node.scriptFunction.body) + ) { return node; } - const funcName: arkts.StringLiteral = arkts.factory.createStringLiteral(node.id?.name!); - const paramValue: arkts.ETSParameterExpression = node.params[1]; - const originStatements: arkts.Statement[] = [...node.body.statements]; + const funcName: arkts.StringLiteral = arkts.factory.createStringLiteral(node.scriptFunction.id?.name!); + const paramValue: arkts.ETSParameterExpression = node.scriptFunction.params[1]; + const originStatements: arkts.Statement[] = [...node.scriptFunction.body.statements]; const createOrSetStatement = arkts.factory.createExpressionStatement( arkts.factory.createCallExpression( arkts.factory.createMemberExpression( @@ -841,18 +920,11 @@ export class factory { ] ) ); - return arkts.factory.updateScriptFunction( - node, - arkts.factory.createBlock([createOrSetStatement, originStatements[originStatements.length - 1]]), - arkts.FunctionSignature.createFunctionSignature( - node.typeParams, - node.params, - node.returnTypeAnnotation, - node.hasReceiver - ), - node.flags, - node.modifiers - ); + return uiFactory.updateMethodDefinition(node, { + function: { + body: arkts.factory.createBlock([createOrSetStatement, originStatements[originStatements.length - 1]]), + }, + }); } /* diff --git a/arkui-plugins/ui-plugins/struct-translators/struct-transformer.ts b/arkui-plugins/ui-plugins/struct-translators/struct-transformer.ts index 3101184cdba3342b645ef30ed3c71c73166d4cfb..ebc10f768f41b35d300cb610c7c51087a873ed31 100644 --- a/arkui-plugins/ui-plugins/struct-translators/struct-transformer.ts +++ b/arkui-plugins/ui-plugins/struct-translators/struct-transformer.ts @@ -25,6 +25,8 @@ import { ResourceInfo, loadBuildJson, initResourceInfo, + initNavigation, + RouterInfo } from './utils'; import { factory } from './factory'; import { isEntryWrapperClass } from '../entry-translators/utils'; @@ -37,8 +39,9 @@ import { generateArkUICompatible, isArkUICompatible } from '../interop/interop'; export class StructTransformer extends AbstractVisitor { private scope: ScopeInfoCollection; projectConfig: ProjectConfig | undefined; - aceBuildJson: LoaderJson; + aceBuildJson: Partial; resourceInfo: ResourceInfo; + navigationInfo: Map; constructor(projectConfig: ProjectConfig | undefined) { super(); @@ -46,6 +49,7 @@ export class StructTransformer extends AbstractVisitor { this.scope = { customComponents: [] }; this.aceBuildJson = loadBuildJson(this.projectConfig); this.resourceInfo = initResourceInfo(this.projectConfig, this.aceBuildJson); + this.navigationInfo = initNavigation(this.aceBuildJson); } reset(): void { @@ -97,7 +101,7 @@ export class StructTransformer extends AbstractVisitor { entryFactory.addMemoToEntryWrapperClassMethods(node); return node; } else if (arkts.isClassDeclaration(node)) { - return factory.transformNormalClass(node); + return factory.transformNormalClass(node, this.isExternal, this.navigationInfo, this.program?.absName); } else if (arkts.isCallExpression(node) && isResourceNode(node)) { return factory.transformResource(node, this.projectConfig, this.resourceInfo); } else if (isArkUICompatible(node)) { diff --git a/arkui-plugins/ui-plugins/struct-translators/utils.ts b/arkui-plugins/ui-plugins/struct-translators/utils.ts index bb3123e317cceecd15fc5ecf8ac7e5531876e5d1..5a97d57e6497cdf10e14267be5e92e134f159d35 100644 --- a/arkui-plugins/ui-plugins/struct-translators/utils.ts +++ b/arkui-plugins/ui-plugins/struct-translators/utils.ts @@ -53,8 +53,21 @@ export interface ResourceInfo { rawfile: Set; } +export interface RouterMap { + ohmurl: string; + name: string; + pageSourceFile: string; + buildFunction: string; +} + +export interface RouterInfo { + name: string, + buildFunction: string, +} + export interface LoaderJson { hspResourcesMap: Record; + routerMap: RouterMap[]; } export interface ResourceParameter { @@ -123,7 +136,7 @@ export function isForEachCall(node: arkts.CallExpression): boolean { * * @param projectConfig configuration information of the project. */ -export function loadBuildJson(projectConfig: ProjectConfig | undefined): any { +export function loadBuildJson(projectConfig: ProjectConfig | undefined): Partial { if (!!projectConfig && projectConfig.buildLoaderJson && fs.existsSync(projectConfig.buildLoaderJson)) { try { const content = fs.readFileSync(projectConfig.buildLoaderJson, 'utf-8'); @@ -142,7 +155,7 @@ export function loadBuildJson(projectConfig: ProjectConfig | undefined): any { * @param projectConfig configuration information of the project. * @param aceBuildJson content of the file 'loader.json'. */ -export function initResourceInfo(projectConfig: ProjectConfig | undefined, aceBuildJson: LoaderJson): ResourceInfo { +export function initResourceInfo(projectConfig: ProjectConfig | undefined, aceBuildJson: Partial): ResourceInfo { let resourcesList: ResourceList = { app: new Map>(), sys: new Map>(), @@ -165,7 +178,7 @@ export function initResourceInfo(projectConfig: ProjectConfig | undefined, aceBu function readAppResource( resourcesList: ResourceList, projectConfig: ProjectConfig, - aceBuildJson: LoaderJson, + aceBuildJson: Partial, rawfile: Set ): void { if ('hspResourcesMap' in aceBuildJson && aceBuildJson.hspResourcesMap) { @@ -253,15 +266,16 @@ function rescordResourceNameAndIdMap(resourceMap: Map, projectConfig: ProjectConfig, resourcesList: ResourceList): void { projectConfig.hspResourcesMap = true; - for (const hspName in aceBuildJson.hspResourcesMap) { - if (fs.existsSync(aceBuildJson.hspResourcesMap[hspName])) { + const hspResourcesMap = aceBuildJson?.hspResourcesMap ?? {}; + for (const hspName in hspResourcesMap) { + if (fs.existsSync(hspResourcesMap[hspName])) { const resourceMap: ResourceMap = new Map>(); resourcesList[hspName] = new Map>(); - const hspResource: string = fs.readFileSync(aceBuildJson.hspResourcesMap[hspName], 'utf-8'); + const hspResource: string = fs.readFileSync(hspResourcesMap[hspName], 'utf-8'); const resourceArr: string[] = hspResource.split(/\n/); - processResourceArr(resourceArr, resourceMap, aceBuildJson.hspResourcesMap[hspName]); + processResourceArr(resourceArr, resourceMap, hspResourcesMap[hspName]); for (const [key, value] of resourceMap) { resourcesList[hspName].set(key, value); } @@ -520,3 +534,25 @@ export function isForEachDecl(node: arkts.MethodDefinition, sourceName: string | arkts.hasModifierFlag(node.scriptFunction, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_DECLARE); return isForEach && isMethodDecl && !!sourceName && sourceName === ARKUI_FOREACH_SOURCE_NAME; } + +/** + * Initializes routerInfo which maps absolute file paths to corresponding build functions + * + * @param aceBuildJson content of the file 'loader.json'. + */ +export function initNavigation(aceBuildJson: Partial): Map { + const routerInfo: Map = new Map(); + if (aceBuildJson && aceBuildJson.routerMap && Array.isArray(aceBuildJson.routerMap)) { + aceBuildJson.routerMap.forEach((item) => { + if (item.pageSourceFile && item.name && item.buildFunction) { + const filePath = path.resolve(item.pageSourceFile); + if (routerInfo.has(filePath)) { + routerInfo.get(filePath)!.push({ name: item.name, buildFunction: item.buildFunction }); + } else { + routerInfo.set(filePath, [{ name: item.name, buildFunction: item.buildFunction }]); + } + } + }); + } + return routerInfo; +} \ No newline at end of file