From 0a1922164665001de5fad37434f4b754c7223a04 Mon Sep 17 00:00:00 2001 From: Yuxin Feng Date: Thu, 12 Jun 2025 20:41:57 +0800 Subject: [PATCH] uiplugin LocalStorage & @LocalStorageLink transform Signed-off-by: Yuxin Feng Change-Id: I4a2c6b57586711dca7fa578182217c2cb5bfb632 --- arkui-plugins/common/predefines.ts | 28 +- .../localstoragelink-complex-type.ets | 43 +++ .../localstoragelink-primitive-type.ets | 27 ++ .../storage-use-shared-storage-false.ets | 25 ++ .../storage-use-shared-storage-true.ets | 25 ++ .../demo/mock/entry/localstorage/storage.ets | 26 ++ .../localstorage/use-shared-storage-false.ets | 25 ++ .../localstorage/use-shared-storage-true.ets | 25 ++ .../localstoragelink-complex-type.test.ts | 312 ++++++++++++++++++ .../localstoragelink-primitive-type.test.ts | 152 +++++++++ .../storage-use-shared-storage-false.test.ts | 87 +++++ .../storage-use-shared-storage-true.test.ts | 87 +++++ .../entry/localstorage/storage.test.ts | 87 +++++ .../use-shared-storage-false.test.ts | 87 +++++ .../use-shared-storage-true.test.ts | 87 +++++ .../ui-plugins/component-transformer.ts | 8 +- .../ui-plugins/entry-translators/factory.ts | 83 ++++- .../ui-plugins/entry-translators/utils.ts | 43 +-- .../property-translators/localstoragelink.ts | 112 +++---- .../ui-plugins/property-translators/utils.ts | 2 +- 20 files changed, 1267 insertions(+), 104 deletions(-) create mode 100644 arkui-plugins/test/demo/mock/decorators/localstoragelink/localstoragelink-complex-type.ets create mode 100644 arkui-plugins/test/demo/mock/decorators/localstoragelink/localstoragelink-primitive-type.ets create mode 100644 arkui-plugins/test/demo/mock/entry/localstorage/storage-use-shared-storage-false.ets create mode 100644 arkui-plugins/test/demo/mock/entry/localstorage/storage-use-shared-storage-true.ets create mode 100644 arkui-plugins/test/demo/mock/entry/localstorage/storage.ets create mode 100644 arkui-plugins/test/demo/mock/entry/localstorage/use-shared-storage-false.ets create mode 100644 arkui-plugins/test/demo/mock/entry/localstorage/use-shared-storage-true.ets create mode 100644 arkui-plugins/test/ut/ui-plugins/decorators/localstoragelink/localstoragelink-complex-type.test.ts create mode 100644 arkui-plugins/test/ut/ui-plugins/decorators/localstoragelink/localstoragelink-primitive-type.test.ts create mode 100644 arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage-use-shared-storage-false.test.ts create mode 100644 arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage-use-shared-storage-true.test.ts create mode 100644 arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage.test.ts create mode 100644 arkui-plugins/test/ut/ui-plugins/entry/localstorage/use-shared-storage-false.test.ts create mode 100644 arkui-plugins/test/ut/ui-plugins/entry/localstorage/use-shared-storage-true.test.ts diff --git a/arkui-plugins/common/predefines.ts b/arkui-plugins/common/predefines.ts index 7d9a4925c..b3c4c61fb 100644 --- a/arkui-plugins/common/predefines.ts +++ b/arkui-plugins/common/predefines.ts @@ -53,6 +53,19 @@ export enum StructDecoratorNames { RESUABLE = 'Reusable', } +export enum EntryWrapperNames { + ENTRY_FUNC = 'entry', + WRAPPER_CLASS_NAME = '__EntryWrapper', + ENTRY_STORAGE_LOCAL_STORAGE_PROPERTY_NAME = '_entry_local_storage_', + ENTRY_POINT_CLASS_NAME = 'EntryPoint', +} + +export enum EntryParamNames { + ENTRY_STORAGE = 'storage', + ENTRY_USE_SHARED_STORAGE = 'useSharedStorage', + ENTRY_ROUTE_NAME = 'routeName' +} + export enum DecoratorNames { STATE = 'State', STORAGE_LINK = 'StorageLink', @@ -87,8 +100,8 @@ export enum StateManagementTypes { LINK_SOURCE_TYPE = 'LinkSourceType', STORAGE_LINK_DECORATED = 'IStorageLinkDecoratedVariable', STORAGE_PROP_DECORATED = 'IStoragePropDecoratedVariable', + LOCAL_STORAGE_LINK_DECORATED = 'ILocalStorageLinkDecoratedVariable', PROP_DECORATED = 'IPropDecoratedVariable', - MUTABLE_STATE = 'MutableState', SYNCED_PROPERTY = 'SyncedProperty', PROVIDE_DECORATED = 'IProvideDecoratedVariable', CONSUME_DECORATED = 'IConsumeDecoratedVariable', @@ -109,6 +122,7 @@ export enum StateManagementTypes { MAKE_PROP = 'makeProp', MAKE_STORAGE_PROP = 'makeStorageProp', MAKE_STORAGE_LINK = 'makeStorageLink', + MAKE_LOCAL_STORAGE_LINK = 'makeLocalStorageLink', MAKE_PROVIDE = 'makeProvide', MAKE_CONSUME = 'makeConsume', MAKE_OBJECT_LINK = 'makeObjectLink', @@ -131,7 +145,7 @@ export const DECORATOR_TYPE_MAP = new Map( [DecoratorNames.STORAGE_LINK, StateManagementTypes.STORAGE_LINK_DECORATED], [DecoratorNames.STORAGE_PROP, StateManagementTypes.STORAGE_PROP_DECORATED], [DecoratorNames.LOCAL_STORAGE_PROP, StateManagementTypes.SYNCED_PROPERTY], - [DecoratorNames.LOCAL_STORAGE_LINK, StateManagementTypes.MUTABLE_STATE], + [DecoratorNames.LOCAL_STORAGE_LINK, StateManagementTypes.LOCAL_STORAGE_LINK_DECORATED], [DecoratorNames.OBJECT_LINK, StateManagementTypes.OBJECT_LINK_DECORATED], [DecoratorNames.PROVIDE, StateManagementTypes.PROVIDE_DECORATED], [DecoratorNames.CONSUME, StateManagementTypes.CONSUME_DECORATED], @@ -149,14 +163,7 @@ export const INTERMEDIATE_IMPORT_SOURCE: Map = new Map = new Map = new Map([ [Dollars.TRANSFORM_DOLLAR_RESOURCE, 'arkui.component.resources'], [Dollars.TRANSFORM_DOLLAR_RAWFILE, 'arkui.component.resources'], - [StateManagementTypes.MUTABLE_STATE, 'arkui.stateManagement.runtime'], [StateManagementTypes.SYNCED_PROPERTY, 'arkui.stateManagement.runtime'], [StateManagementTypes.STORAGE_LINK_STATE, 'arkui.stateManagement.runtime'], [StateManagementTypes.OBSERVABLE_PROXY, 'arkui.stateManagement.runtime'], diff --git a/arkui-plugins/test/demo/mock/decorators/localstoragelink/localstoragelink-complex-type.ets b/arkui-plugins/test/demo/mock/decorators/localstoragelink/localstoragelink-complex-type.ets new file mode 100644 index 000000000..6a2f5d196 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/localstoragelink/localstoragelink-complex-type.ets @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Entry } from "@ohos.arkui.component" +import { LocalStorageLink } from "@ohos.arkui.stateManagement" + +class Person{ + name: string = '' + constructor(name: string){} +} + +enum Status { + Success = 200, + NotFound = 404, + ServerError = 500 +} + +@Entry +@Component +struct MyStateSample { + @LocalStorageLink('Prop1') arrayA: number[] = [1,2,3]; + @LocalStorageLink('Prop2') objectA: Object = {}; + @LocalStorageLink('Prop3') dateA: Date = new Date('2021-08-08'); + @LocalStorageLink('Prop4') setA: Set = new Set(); + @LocalStorageLink('Prop5') mapA: Map = new Map(); + @LocalStorageLink('Prop6') unionA: string | undefined = ""; + @LocalStorageLink('Prop7') classA: Person = new Person("John"); + @LocalStorageLink('Prop8') enumA: Status = Status.NotFound; + + build() {} +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/localstoragelink/localstoragelink-primitive-type.ets b/arkui-plugins/test/demo/mock/decorators/localstoragelink/localstoragelink-primitive-type.ets new file mode 100644 index 000000000..8bc882e51 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/localstoragelink/localstoragelink-primitive-type.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, Entry } from "@ohos.arkui.component" +import { LocalStorageLink } from "@ohos.arkui.stateManagement" + +@Entry +@Component +struct MyStateSample { + @LocalStorageLink('Prop1') numA: number = 33; + @LocalStorageLink('Prop2') stringA: string = 'AA'; + @LocalStorageLink('Prop3') booleanA: boolean = true; + + build() {} +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/entry/localstorage/storage-use-shared-storage-false.ets b/arkui-plugins/test/demo/mock/entry/localstorage/storage-use-shared-storage-false.ets new file mode 100644 index 000000000..2a98d667f --- /dev/null +++ b/arkui-plugins/test/demo/mock/entry/localstorage/storage-use-shared-storage-false.ets @@ -0,0 +1,25 @@ +/* + * 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, Entry } from "@ohos.arkui.component" +import { LocalStorage } from "@ohos.arkui.stateManagement" + +const myStorage: ()=>LocalStorage = ()=>new LocalStorage() + +@Entry({storage: 'myStorage', useSharedStorage: false}) +@Component +struct MyStateSample { + build() {} +} diff --git a/arkui-plugins/test/demo/mock/entry/localstorage/storage-use-shared-storage-true.ets b/arkui-plugins/test/demo/mock/entry/localstorage/storage-use-shared-storage-true.ets new file mode 100644 index 000000000..e3c657bd3 --- /dev/null +++ b/arkui-plugins/test/demo/mock/entry/localstorage/storage-use-shared-storage-true.ets @@ -0,0 +1,25 @@ +/* + * 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, Entry } from "@ohos.arkui.component" +import { LocalStorage } from "@ohos.arkui.stateManagement" + +const myStorage: ()=>LocalStorage = ()=>new LocalStorage() + +@Entry({storage: 'myStorage', useSharedStorage: true}) +@Component +struct MyStateSample { + build() {} +} diff --git a/arkui-plugins/test/demo/mock/entry/localstorage/storage.ets b/arkui-plugins/test/demo/mock/entry/localstorage/storage.ets new file mode 100644 index 000000000..b1aefaefb --- /dev/null +++ b/arkui-plugins/test/demo/mock/entry/localstorage/storage.ets @@ -0,0 +1,26 @@ +/* + * 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, Entry } from "@ohos.arkui.component" +import { LocalStorage } from "@ohos.arkui.stateManagement" + +const myStorage: ()=>LocalStorage = ()=>new LocalStorage() + +@Entry({storage: 'myStorage'}) +@Component +struct MyStateSample { + build() {} + +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/entry/localstorage/use-shared-storage-false.ets b/arkui-plugins/test/demo/mock/entry/localstorage/use-shared-storage-false.ets new file mode 100644 index 000000000..7be065952 --- /dev/null +++ b/arkui-plugins/test/demo/mock/entry/localstorage/use-shared-storage-false.ets @@ -0,0 +1,25 @@ +/* + * 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, Entry } from "@ohos.arkui.component" +import { LocalStorage } from "@ohos.arkui.stateManagement" + +const myStorage: ()=>LocalStorage = ()=>new LocalStorage() + +@Entry({useSharedStorage: false}) +@Component +struct MyStateSample { + build() {} +} diff --git a/arkui-plugins/test/demo/mock/entry/localstorage/use-shared-storage-true.ets b/arkui-plugins/test/demo/mock/entry/localstorage/use-shared-storage-true.ets new file mode 100644 index 000000000..f327cf40c --- /dev/null +++ b/arkui-plugins/test/demo/mock/entry/localstorage/use-shared-storage-true.ets @@ -0,0 +1,25 @@ +/* + * 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, Entry } from "@ohos.arkui.component" +import { LocalStorage } from "@ohos.arkui.stateManagement" + +const myStorage: ()=>LocalStorage = ()=>new LocalStorage() + +@Entry({useSharedStorage: true}) +@Component +struct MyStateSample { + build() {} +} diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/localstoragelink/localstoragelink-complex-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/localstoragelink/localstoragelink-complex-type.test.ts new file mode 100644 index 000000000..faa6e7ce8 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/localstoragelink/localstoragelink-complex-type.test.ts @@ -0,0 +1,312 @@ +/* + * 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 { uiNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const LOCAL_STORAGELINK_DIR_PATH: string = 'decorators/localstoragelink'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, LOCAL_STORAGELINK_DIR_PATH, 'localstoragelink-complex-type.ets'), +]; + +const localStorageLinkTransform: Plugins = { + name: 'localStorageLink', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test LocalStorageLink complex type transform', buildConfig); + +const expectedScript: string = ` + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ILocalStorageLinkDecoratedVariable as ILocalStorageLinkDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { EntryPoint as EntryPoint } from "arkui.UserView"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Entry as Entry } from "@ohos.arkui.component"; + +import { LocalStorageLink as LocalStorageLink } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +class Person { + public name: string = ""; + + public constructor(name: string) {} + +} + +final class Status extends BaseEnum { + private readonly #ordinal: int; + + private static () {} + + public constructor(ordinal: int, value: int) { + super(value); + this.#ordinal = ordinal; + } + + public static readonly Success: Status = new Status(0, 200); + + public static readonly NotFound: Status = new Status(1, 404); + + public static readonly ServerError: Status = new Status(2, 500); + + private static readonly #NamesArray: String[] = ["Success", "NotFound", "ServerError"]; + + private static readonly #ValuesArray: int[] = [200, 404, 500]; + + private static readonly #StringValuesArray: String[] = ["200", "404", "500"]; + + private static readonly #ItemsArray: Status[] = [Status.Success, Status.NotFound, Status.ServerError]; + + public getName(): String { + return Status.#NamesArray[this.#ordinal]; + } + + public static getValueOf(name: String): Status { + for (let i = 0;((i) < (Status.#NamesArray.length));(++i)) { + if (((name) == (Status.#NamesArray[i]))) { + return Status.#ItemsArray[i]; + } + } + throw new Error((("No enum constant Status.") + (name))); + } + + public static fromValue(value: int): Status { + for (let i = 0;((i) < (Status.#ValuesArray.length));(++i)) { + if (((value) == (Status.#ValuesArray[i]))) { + return Status.#ItemsArray[i]; + } + } + throw new Error((("No enum Status with value ") + (value))); + } + + public valueOf(): int { + return Status.#ValuesArray[this.#ordinal]; + } + + public toString(): String { + return Status.#StringValuesArray[this.#ordinal]; + } + + public static values(): Status[] { + return Status.#ItemsArray; + } + + public getOrdinal(): int { + return this.#ordinal; + } + + public static $_get(e: Status): String { + return e.getName(); + } + +} + +@Entry({useSharedStorage:false,storage:"",routeName:""}) @Component({freezeWhenInactive:false}) final struct MyStateSample extends CustomComponent { + public __initializeStruct(initializers: __Options_MyStateSample | undefined, @memo() content: (()=> void) | undefined): void { + this.__backing_arrayA = STATE_MGMT_FACTORY.makeLocalStorageLink>(this, "Prop1", "arrayA", [1, 2, 3]) + this.__backing_objectA = STATE_MGMT_FACTORY.makeLocalStorageLink(this, "Prop2", "objectA", {}) + this.__backing_dateA = STATE_MGMT_FACTORY.makeLocalStorageLink(this, "Prop3", "dateA", new Date("2021-08-08")) + this.__backing_setA = STATE_MGMT_FACTORY.makeLocalStorageLink>(this, "Prop4", "setA", new Set()) + this.__backing_mapA = STATE_MGMT_FACTORY.makeLocalStorageLink>(this, "Prop5", "mapA", new Map()) + this.__backing_unionA = STATE_MGMT_FACTORY.makeLocalStorageLink(this, "Prop6", "unionA", "") + this.__backing_classA = STATE_MGMT_FACTORY.makeLocalStorageLink(this, "Prop7", "classA", new Person("John")) + this.__backing_enumA = STATE_MGMT_FACTORY.makeLocalStorageLink(this, "Prop8", "enumA", Status.NotFound) + } + + public __updateStruct(initializers: __Options_MyStateSample | undefined): void {} + + private __backing_arrayA?: ILocalStorageLinkDecoratedVariable>; + + public get arrayA(): Array { + return this.__backing_arrayA!.get(); + } + + public set arrayA(value: Array) { + this.__backing_arrayA!.set(value); + } + + private __backing_objectA?: ILocalStorageLinkDecoratedVariable; + + public get objectA(): Object { + return this.__backing_objectA!.get(); + } + + public set objectA(value: Object) { + this.__backing_objectA!.set(value); + } + + private __backing_dateA?: ILocalStorageLinkDecoratedVariable; + + public get dateA(): Date { + return this.__backing_dateA!.get(); + } + + public set dateA(value: Date) { + this.__backing_dateA!.set(value); + } + + private __backing_setA?: ILocalStorageLinkDecoratedVariable>; + + public get setA(): Set { + return this.__backing_setA!.get(); + } + + public set setA(value: Set) { + this.__backing_setA!.set(value); + } + + private __backing_mapA?: ILocalStorageLinkDecoratedVariable>; + + public get mapA(): Map { + return this.__backing_mapA!.get(); + } + + public set mapA(value: Map) { + this.__backing_mapA!.set(value); + } + + private __backing_unionA?: ILocalStorageLinkDecoratedVariable; + + public get unionA(): string | undefined { + return this.__backing_unionA!.get(); + } + + public set unionA(value: string | undefined) { + this.__backing_unionA!.set(value); + } + + private __backing_classA?: ILocalStorageLinkDecoratedVariable; + + public get classA(): Person { + return this.__backing_classA!.get(); + } + + public set classA(value: Person) { + this.__backing_classA!.set(value); + } + + private __backing_enumA?: ILocalStorageLinkDecoratedVariable; + + public get enumA(): Status { + return this.__backing_enumA!.get(); + } + + public set enumA(value: Status) { + this.__backing_enumA!.set(value); + } + + @memo() public _build(@memo() style: ((instance: MyStateSample)=> MyStateSample) | undefined, @memo() content: (()=> void) | undefined, initializers: __Options_MyStateSample | undefined): void {} + + private constructor() {} + +} + +@Entry({useSharedStorage:false,storage:"",routeName:""}) @Component({freezeWhenInactive:false}) export interface __Options_MyStateSample { + set arrayA(arrayA: Array | undefined) + + get arrayA(): Array | undefined + set __backing_arrayA(__backing_arrayA: ILocalStorageLinkDecoratedVariable> | undefined) + + get __backing_arrayA(): ILocalStorageLinkDecoratedVariable> | undefined + set objectA(objectA: Object | undefined) + + get objectA(): Object | undefined + set __backing_objectA(__backing_objectA: ILocalStorageLinkDecoratedVariable | undefined) + + get __backing_objectA(): ILocalStorageLinkDecoratedVariable | undefined + set dateA(dateA: Date | undefined) + + get dateA(): Date | undefined + set __backing_dateA(__backing_dateA: ILocalStorageLinkDecoratedVariable | undefined) + + get __backing_dateA(): ILocalStorageLinkDecoratedVariable | undefined + set setA(setA: Set | undefined) + + get setA(): Set | undefined + set __backing_setA(__backing_setA: ILocalStorageLinkDecoratedVariable> | undefined) + + get __backing_setA(): ILocalStorageLinkDecoratedVariable> | undefined + set mapA(mapA: Map | undefined) + + get mapA(): Map | undefined + set __backing_mapA(__backing_mapA: ILocalStorageLinkDecoratedVariable> | undefined) + + get __backing_mapA(): ILocalStorageLinkDecoratedVariable> | undefined + set unionA(unionA: string | undefined | undefined) + + get unionA(): string | undefined | undefined + set __backing_unionA(__backing_unionA: ILocalStorageLinkDecoratedVariable | undefined) + + get __backing_unionA(): ILocalStorageLinkDecoratedVariable | undefined + set classA(classA: Person | undefined) + + get classA(): Person | undefined + set __backing_classA(__backing_classA: ILocalStorageLinkDecoratedVariable | undefined) + + get __backing_classA(): ILocalStorageLinkDecoratedVariable | undefined + set enumA(enumA: Status | undefined) + + get enumA(): Status | undefined + set __backing_enumA(__backing_enumA: ILocalStorageLinkDecoratedVariable | undefined) + + get __backing_enumA(): ILocalStorageLinkDecoratedVariable | undefined + +} + +class __EntryWrapper extends EntryPoint { + @memo() public entry(): void { + MyStateSample._instantiateImpl(undefined, (() => { + return new MyStateSample(); + })); + } + + public constructor() {} + +} +`; + +function testLocalStorageLinkTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test LocalStorageLink complex type transform', + [localStorageLinkTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testLocalStorageLinkTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/localstoragelink/localstoragelink-primitive-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/localstoragelink/localstoragelink-primitive-type.test.ts new file mode 100644 index 000000000..816c3a911 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/localstoragelink/localstoragelink-primitive-type.test.ts @@ -0,0 +1,152 @@ +/* + * 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 { uiNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const LOCAL_STORAGELINK_DIR_PATH: string = 'decorators/localstoragelink'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, LOCAL_STORAGELINK_DIR_PATH, 'localstoragelink-primitive-type.ets'), +]; + +const localStorageLinkTransform: Plugins = { + name: 'localStorageLink', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test LocalStorageLink primitive type transform', buildConfig); + +const expectedScript: string = ` + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ILocalStorageLinkDecoratedVariable as ILocalStorageLinkDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { EntryPoint as EntryPoint } from "arkui.UserView"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Entry as Entry } from "@ohos.arkui.component"; + +import { LocalStorageLink as LocalStorageLink } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@Entry({useSharedStorage:false,storage:"",routeName:""}) @Component({freezeWhenInactive:false}) final struct MyStateSample extends CustomComponent { + public __initializeStruct(initializers: __Options_MyStateSample | undefined, @memo() content: (()=> void) | undefined): void { + this.__backing_numA = STATE_MGMT_FACTORY.makeLocalStorageLink(this, "Prop1", "numA", 33) + this.__backing_stringA = STATE_MGMT_FACTORY.makeLocalStorageLink(this, "Prop2", "stringA", "AA") + this.__backing_booleanA = STATE_MGMT_FACTORY.makeLocalStorageLink(this, "Prop3", "booleanA", true) + } + + public __updateStruct(initializers: __Options_MyStateSample | undefined): void {} + + private __backing_numA?: ILocalStorageLinkDecoratedVariable; + + public get numA(): number { + return this.__backing_numA!.get(); + } + + public set numA(value: number) { + this.__backing_numA!.set(value); + } + + private __backing_stringA?: ILocalStorageLinkDecoratedVariable; + + public get stringA(): string { + return this.__backing_stringA!.get(); + } + + public set stringA(value: string) { + this.__backing_stringA!.set(value); + } + + private __backing_booleanA?: ILocalStorageLinkDecoratedVariable; + + public get booleanA(): boolean { + return this.__backing_booleanA!.get(); + } + + public set booleanA(value: boolean) { + this.__backing_booleanA!.set(value); + } + + @memo() public _build(@memo() style: ((instance: MyStateSample)=> MyStateSample) | undefined, @memo() content: (()=> void) | undefined, initializers: __Options_MyStateSample | undefined): void {} + + private constructor() {} + +} + +@Entry({useSharedStorage:false,storage:"",routeName:""}) @Component({freezeWhenInactive:false}) export interface __Options_MyStateSample { + set numA(numA: number | undefined) + + get numA(): number | undefined + set __backing_numA(__backing_numA: ILocalStorageLinkDecoratedVariable | undefined) + + get __backing_numA(): ILocalStorageLinkDecoratedVariable | undefined + set stringA(stringA: string | undefined) + + get stringA(): string | undefined + set __backing_stringA(__backing_stringA: ILocalStorageLinkDecoratedVariable | undefined) + + get __backing_stringA(): ILocalStorageLinkDecoratedVariable | undefined + set booleanA(booleanA: boolean | undefined) + + get booleanA(): boolean | undefined + set __backing_booleanA(__backing_booleanA: ILocalStorageLinkDecoratedVariable | undefined) + + get __backing_booleanA(): ILocalStorageLinkDecoratedVariable | undefined + +} + +class __EntryWrapper extends EntryPoint { + @memo() public entry(): void { + MyStateSample._instantiateImpl(undefined, (() => { + return new MyStateSample(); + })); + } + + public constructor() {} + +} +`; + +function testLocalStorageLinkTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test LocalStorageLink primitive type transform', + [localStorageLinkTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testLocalStorageLinkTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage-use-shared-storage-false.test.ts b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage-use-shared-storage-false.test.ts new file mode 100644 index 000000000..3b0e02357 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage-use-shared-storage-false.test.ts @@ -0,0 +1,87 @@ +/* + * 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 FUNCTION_DIR_PATH: string = 'entry/localstorage'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, FUNCTION_DIR_PATH, 'storage-use-shared-storage-false.ets'), +]; + +const pluginTester = new PluginTester('test entry with storage and useSharedStorage false', buildConfig); + +const parsedTransform: Plugins = { + name: 'entry with storage', + parsed: uiTransform().parsed, +}; + +const expectedScript: string = ` + +import { EntryPoint as EntryPoint } from "arkui.UserView"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Entry as Entry } from "@ohos.arkui.component"; + +import { LocalStorage as LocalStorage } from "@ohos.arkui.stateManagement"; + +const myStorage: (()=> LocalStorage) = (() => new LocalStorage()) +@Entry({storage:"myStorage",useSharedStorage:false}) @Component() final struct MyStateSample extends CustomComponent { + public build() {} + + public constructor() { + super(myStorage()); + } + +} + +@Entry({storage:"myStorage",useSharedStorage:false}) @Component() export interface __Options_MyStateSample { + +} + +class __EntryWrapper extends EntryPoint { + public entry(): void { + MyStateSample(); + } + + public constructor() {} + +} +`; + +function testEntryTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test entry with storage and useSharedStorage false', + [parsedTransform, uiNoRecheck, recheck], + { + 'parsed': [testEntryTransformer], + }, + { + stopAfter: 'parsed', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage-use-shared-storage-true.test.ts b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage-use-shared-storage-true.test.ts new file mode 100644 index 000000000..0eed30ca5 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage-use-shared-storage-true.test.ts @@ -0,0 +1,87 @@ +/* + * 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 FUNCTION_DIR_PATH: string = 'entry/localstorage'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, FUNCTION_DIR_PATH, 'storage-use-shared-storage-true.ets'), +]; + +const pluginTester = new PluginTester('test entry with storage and useSharedStorage true', buildConfig); + +const parsedTransform: Plugins = { + name: 'entry with storage', + parsed: uiTransform().parsed, +}; + +const expectedScript: string = ` + +import { EntryPoint as EntryPoint } from "arkui.UserView"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Entry as Entry } from "@ohos.arkui.component"; + +import { LocalStorage as LocalStorage } from "@ohos.arkui.stateManagement"; + +const myStorage: (()=> LocalStorage) = (() => new LocalStorage()) +@Entry({storage:"myStorage",useSharedStorage:true}) @Component() final struct MyStateSample extends CustomComponent { + public build() {} + + public constructor() { + super(this.getUIContext().getSharedLocalStorage()); + } + +} + +@Entry({storage:"myStorage",useSharedStorage:true}) @Component() export interface __Options_MyStateSample { + +} + +class __EntryWrapper extends EntryPoint { + public entry(): void { + MyStateSample(); + } + + public constructor() {} + +} +`; + +function testEntryTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test entry with storage and useSharedStorage true', + [parsedTransform, uiNoRecheck, recheck], + { + 'parsed': [testEntryTransformer], + }, + { + stopAfter: 'parsed', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage.test.ts b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage.test.ts new file mode 100644 index 000000000..ea54265cf --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/storage.test.ts @@ -0,0 +1,87 @@ +/* + * 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 FUNCTION_DIR_PATH: string = 'entry/localstorage'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, FUNCTION_DIR_PATH, 'storage.ets'), +]; + +const pluginTester = new PluginTester('test entry with only storage', buildConfig); + +const parsedTransform: Plugins = { + name: 'entry with storage', + parsed: uiTransform().parsed, +}; + +const expectedScript: string = ` + +import { EntryPoint as EntryPoint } from "arkui.UserView"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Entry as Entry } from "@ohos.arkui.component"; + +import { LocalStorage as LocalStorage } from "@ohos.arkui.stateManagement"; + +const myStorage: (()=> LocalStorage) = (() => new LocalStorage()) +@Entry({storage:"myStorage"}) @Component() final struct MyStateSample extends CustomComponent { + public build() {} + + public constructor() { + super(myStorage()); + } + +} + +@Entry({storage:"myStorage"}) @Component() export interface __Options_MyStateSample { + +} + +class __EntryWrapper extends EntryPoint { + public entry(): void { + MyStateSample(); + } + + public constructor() {} + +} +`; + +function testEntryStorageTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test entry with only storage', + [parsedTransform, uiNoRecheck, recheck], + { + 'parsed': [testEntryStorageTransformer], + }, + { + stopAfter: 'parsed', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/entry/localstorage/use-shared-storage-false.test.ts b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/use-shared-storage-false.test.ts new file mode 100644 index 000000000..a0360ac3d --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/use-shared-storage-false.test.ts @@ -0,0 +1,87 @@ +/* + * 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 FUNCTION_DIR_PATH: string = 'entry/localstorage'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, FUNCTION_DIR_PATH, 'use-shared-storage-false.ets'), +]; + +const pluginTester = new PluginTester('test entry with only useSharedStorage false', buildConfig); + +const parsedTransform: Plugins = { + name: 'entry with storage', + parsed: uiTransform().parsed, +}; + +const expectedScript: string = ` + +import { EntryPoint as EntryPoint } from "arkui.UserView"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Entry as Entry } from "@ohos.arkui.component"; + +import { LocalStorage as LocalStorage } from "@ohos.arkui.stateManagement"; + +const myStorage: (()=> LocalStorage) = (() => new LocalStorage()) +@Entry({useSharedStorage:false}) @Component() final struct MyStateSample extends CustomComponent { + public build() {} + + public constructor() { + super(undefined); + } + +} + +@Entry({useSharedStorage:false}) @Component() export interface __Options_MyStateSample { + +} + +class __EntryWrapper extends EntryPoint { + public entry(): void { + MyStateSample(); + } + + public constructor() {} + +} +`; + +function testEntryTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test entry with only useSharedStorage false', + [parsedTransform, uiNoRecheck, recheck], + { + 'parsed': [testEntryTransformer], + }, + { + stopAfter: 'parsed', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/entry/localstorage/use-shared-storage-true.test.ts b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/use-shared-storage-true.test.ts new file mode 100644 index 000000000..db97cb50e --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/entry/localstorage/use-shared-storage-true.test.ts @@ -0,0 +1,87 @@ +/* + * 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 FUNCTION_DIR_PATH: string = 'entry/localstorage'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, FUNCTION_DIR_PATH, 'use-shared-storage-true.ets'), +]; + +const pluginTester = new PluginTester('test entry with only useSharedStorage true', buildConfig); + +const parsedTransform: Plugins = { + name: 'entry with storage', + parsed: uiTransform().parsed, +}; + +const expectedScript: string = ` + +import { EntryPoint as EntryPoint } from "arkui.UserView"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Entry as Entry } from "@ohos.arkui.component"; + +import { LocalStorage as LocalStorage } from "@ohos.arkui.stateManagement"; + +const myStorage: (()=> LocalStorage) = (() => new LocalStorage()) +@Entry({useSharedStorage:true}) @Component() final struct MyStateSample extends CustomComponent { + public build() {} + + public constructor() { + super(this.getUIContext().getSharedLocalStorage()); + } + +} + +@Entry({useSharedStorage:true}) @Component() export interface __Options_MyStateSample { + +} + +class __EntryWrapper extends EntryPoint { + public entry(): void { + MyStateSample(); + } + + public constructor() {} + +} +`; + +function testEntryTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test entry with only useSharedStorage true', + [parsedTransform, uiNoRecheck, recheck], + { + 'parsed': [testEntryTransformer], + }, + { + stopAfter: 'parsed', + } +); diff --git a/arkui-plugins/ui-plugins/component-transformer.ts b/arkui-plugins/ui-plugins/component-transformer.ts index 1bde82161..794495864 100644 --- a/arkui-plugins/ui-plugins/component-transformer.ts +++ b/arkui-plugins/ui-plugins/component-transformer.ts @@ -34,7 +34,6 @@ import { collect, createAndInsertImportDeclaration, } from '../common/arkts-utils'; -import { EntryWrapperNames, findEntryWithStorageInClassAnnotations } from './entry-translators/utils'; import { factory as entryFactory } from './entry-translators/factory'; import { hasDecoratorName, findDecoratorInfos, isDecoratorAnnotation } from './property-translators/utils'; import { factory } from './ui-factory'; @@ -46,6 +45,7 @@ import { DecoratorNames, DECORATOR_TYPE_MAP, ENTRY_POINT_IMPORT_SOURCE_NAME, + EntryWrapperNames } from '../common/predefines'; import { generateInstantiateInterop } from './interop/interop'; @@ -228,11 +228,7 @@ export class ComponentTransformer extends AbstractVisitor { const newDefinitionBody: arkts.AstNode[] = []; if (!!scopeInfo.annotations?.entry) { this.entryNames.push(className); - const entryWithStorage: arkts.ClassProperty | undefined = - findEntryWithStorageInClassAnnotations(definition); - if (!!entryWithStorage) { - newDefinitionBody.push(entryFactory.createEntryLocalStorageInClass(entryWithStorage)); - } + entryFactory.transformEntryParams(definition); } const newDefinition: arkts.ClassDefinition = this.createNewDefinition( node, diff --git a/arkui-plugins/ui-plugins/entry-translators/factory.ts b/arkui-plugins/ui-plugins/entry-translators/factory.ts index 09d1c85b7..a0323b1c0 100644 --- a/arkui-plugins/ui-plugins/entry-translators/factory.ts +++ b/arkui-plugins/ui-plugins/entry-translators/factory.ts @@ -14,9 +14,9 @@ */ import * as arkts from '@koalaui/libarkts'; -import { EntryWrapperNames } from './utils'; +import { findEntryParam } from './utils'; import { annotation, createAndInsertImportDeclaration } from '../../common/arkts-utils'; -import { ENTRY_POINT_IMPORT_SOURCE_NAME } from '../../common/predefines'; +import { ENTRY_POINT_IMPORT_SOURCE_NAME, EntryWrapperNames } from '../../common/predefines'; export class factory { /** @@ -141,11 +141,7 @@ export class factory { */ static generateEntryProperty(name: string): arkts.ClassProperty { const exp = arkts.factory.createExpressionStatement( - arkts.factory.createCallExpression( - arkts.factory.createIdentifier(name), - undefined, - [] - ) + arkts.factory.createCallExpression(arkts.factory.createIdentifier(name), undefined, []) ); const key: arkts.Identifier = arkts.factory.createIdentifier(EntryWrapperNames.ENTRY_FUNC); const block: arkts.BlockStatement = arkts.factory.createBlock([exp]); @@ -281,4 +277,77 @@ export class factory { program ); } + + /** + * transformEntryParams, e.g. `@Entry`({useSharedStorage: ..., storage: ...}) + */ + static transformEntryParams(definition: arkts.ClassDefinition): void { + const { storage, useSharedStorage, routeName } = findEntryParam(definition); + + const ctor: arkts.MethodDefinition | undefined = definition.body.find( + (member) => + arkts.isMethodDefinition(member) && + member.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_CONSTRUCTOR + ) as arkts.MethodDefinition | undefined; + if (!ctor) { + return; + } + let superCall: arkts.CallExpression | undefined; + if (storage && !useSharedStorage && storage.value && arkts.isStringLiteral(storage.value)) { + superCall = arkts.factory.createCallExpression(arkts.factory.createSuperExpression(), undefined, [ + arkts.factory.createCallExpression( + arkts.factory.createIdentifier(storage.value.str), + undefined, + undefined + ), + ]); + } else if (useSharedStorage && useSharedStorage.value && arkts.isBooleanLiteral(useSharedStorage.value)) { + let storageArg = arkts.factory.createUndefinedLiteral(); + if (storage && storage.value && arkts.isStringLiteral(storage.value)) { + storageArg = arkts.factory.createCallExpression( + arkts.factory.createIdentifier(storage.value.str), + undefined, + undefined + ); + } + superCall = arkts.factory.createCallExpression(arkts.factory.createSuperExpression(), undefined, [ + useSharedStorage.value.value ? factory.getSharedLocalStorage() : storageArg, + ]); + } + if (superCall && ctor.scriptFunction.body && arkts.isBlockStatement(ctor.scriptFunction.body)) { + ctor.scriptFunction.setBody( + arkts.factory.updateBlock(ctor.scriptFunction.body, [ + ...ctor.scriptFunction.body.statements, + arkts.factory.createExpressionStatement(superCall), + ]) + ); + } + } + + /** + * helper for transformEntryParams to create getSharedLocalStorage in super() + */ + static getSharedLocalStorage(): arkts.CallExpression { + return arkts.factory.createCallExpression( + arkts.factory.createMemberExpression( + arkts.factory.createCallExpression( + arkts.factory.createMemberExpression( + arkts.factory.createThisExpression(), + arkts.factory.createIdentifier('getUIContext'), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + undefined, + undefined + ), + arkts.factory.createIdentifier('getSharedLocalStorage'), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + undefined, + undefined + ); + } } diff --git a/arkui-plugins/ui-plugins/entry-translators/utils.ts b/arkui-plugins/ui-plugins/entry-translators/utils.ts index a987f631b..06552829e 100644 --- a/arkui-plugins/ui-plugins/entry-translators/utils.ts +++ b/arkui-plugins/ui-plugins/entry-translators/utils.ts @@ -16,15 +16,7 @@ import * as arkts from '@koalaui/libarkts'; import { factory } from './factory'; import { isAnnotation } from '../../common/arkts-utils'; -import { StructDecoratorNames } from '../../common/predefines'; - -export enum EntryWrapperNames { - ENTRY_FUNC = 'entry', - WRAPPER_CLASS_NAME = '__EntryWrapper', - ENTRY_STORAGE_ANNOTATION_KEY = 'storage', - ENTRY_STORAGE_LOCAL_STORAGE_PROPERTY_NAME = '_entry_local_storage_', - ENTRY_POINT_CLASS_NAME = 'EntryPoint', -} +import { StructDecoratorNames, EntryWrapperNames, EntryParamNames } from '../../common/predefines'; /** * @deprecated @@ -66,18 +58,27 @@ export function isEntryWrapperClass(node: arkts.AstNode): node is arkts.ClassDec } /** - * find `{storage: ""}` in `@Entry({storage: ""})` (i.e. annotation's properties). + * find annotation's properties in `@Entry()` (i.e. storage, useSharedStorage, routeName.). * * @param node class definition node */ -export function findEntryWithStorageInClassAnnotations(node: arkts.ClassDefinition): arkts.ClassProperty | undefined { - const annotation = node.annotations.find((anno) => { - if (!isAnnotation(anno, StructDecoratorNames.ENTRY)) return false; - const property = anno.properties?.at(0); - if (!property || !arkts.isClassProperty(property)) return false; - if (!property.key || !arkts.isIdentifier(property.key)) return false; - if (!property.value || !arkts.isStringLiteral(property.value)) return false; - return property.key.name === EntryWrapperNames.ENTRY_STORAGE_ANNOTATION_KEY; - }); - return annotation?.properties?.at(0) as arkts.ClassProperty | undefined; -} +export function findEntryParam(node: arkts.ClassDefinition): Record { + const annotation = node.annotations.find((anno) => isAnnotation(anno, StructDecoratorNames.ENTRY)); + const result = { + storage: undefined, + useSharedStorage: undefined, + routeName: undefined, + } as Record; + if (!annotation || !annotation.properties) { + return result; + } + for (const prop of annotation.properties) { + if (arkts.isClassProperty(prop) && prop.key && arkts.isIdentifier(prop.key)) { + const name = prop.key.name as EntryParamNames; + if (name in result) { + result[name] = prop; + } + } + } + return result; +} diff --git a/arkui-plugins/ui-plugins/property-translators/localstoragelink.ts b/arkui-plugins/ui-plugins/property-translators/localstoragelink.ts index cc1e0ba11..a5f44d0c0 100755 --- a/arkui-plugins/ui-plugins/property-translators/localstoragelink.ts +++ b/arkui-plugins/ui-plugins/property-translators/localstoragelink.ts @@ -16,12 +16,16 @@ import * as arkts from '@koalaui/libarkts'; import { backingField, expectName } from '../../common/arkts-utils'; -import { DecoratorNames, StateManagementTypes } from '../../common/predefines'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; import { GetterSetter, InitializerConstructor } from './types'; import { - collectStateManagementTypeImport, generateToRecord, + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + collectStateManagementTypeImport, hasDecorator, PropertyCache, } from './utils'; @@ -29,14 +33,15 @@ import { factory } from './factory'; function getLocalStorageLinkValueStr(node: arkts.AstNode): string | undefined { if (!arkts.isClassProperty(node) || !node.value) return undefined; + return arkts.isStringLiteral(node.value) ? node.value.str : undefined; } function getLocalStorageLinkAnnotationValue(anno: arkts.AnnotationUsage): string | undefined { - const isStorageLinkAnnotation: boolean = + const isLocalStorageLinkAnnotation: boolean = !!anno.expr && arkts.isIdentifier(anno.expr) && anno.expr.name === DecoratorNames.LOCAL_STORAGE_LINK; - if (isStorageLinkAnnotation && anno.properties.length === 1) { + if (isLocalStorageLinkAnnotation && anno.properties.length === 1) { return getLocalStorageLinkValueStr(anno.properties.at(0)!); } return undefined; @@ -52,7 +57,6 @@ function getLocalStorageLinkValueInAnnotation(node: arkts.ClassProperty): string return str; } } - return undefined; } @@ -74,78 +78,70 @@ export class LocalStorageLinkTranslator extends PropertyTranslator implements In } } + generateInitializeStruct(newName: string, originalName: string): arkts.AstNode { + const localStorageLinkValueStr: string | undefined = getLocalStorageLinkValueInAnnotation(this.property); + if (!localStorageLinkValueStr) { + throw new Error('LocalStorageLink required only one value!!'); + } + + const args: arkts.Expression[] = [ + arkts.factory.createStringLiteral(localStorageLinkValueStr), + arkts.factory.create1StringLiteral(originalName), + this.property.value ?? arkts.factory.createUndefinedLiteral(), + ]; + factory.judgeIfAddWatchFunc(args, this.property); + collectStateManagementTypeImport(StateManagementTypes.LOCAL_STORAGE_LINK_DECORATED); + return arkts.factory.createAssignmentExpression( + generateThisBacking(newName), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_LOCAL_STORAGE_LINK, + this.property.typeAnnotation, + args, + true + ) + ); + } + translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { const field = factory.createOptionalClassProperty( newName, this.property, - StateManagementTypes.MUTABLE_STATE, + StateManagementTypes.LOCAL_STORAGE_LINK_DECORATED, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE ); - - const member = arkts.factory.createTSNonNullExpression( - arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier(newName), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ) + const thisValue: arkts.Expression = generateThisBacking(newName, false, true); + const thisGet: arkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const thisSet: arkts.ExpressionStatement = arkts.factory.createExpressionStatement( + generateGetOrSetCall(thisValue, GetSetTypes.SET) ); - const thisValue: arkts.MemberExpression = arkts.factory.createMemberExpression( - member, - arkts.factory.createIdentifier('value'), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ); - const getter: arkts.MethodDefinition = this.translateGetter( originalName, this.property.typeAnnotation, - thisValue + thisGet ); const setter: arkts.MethodDefinition = this.translateSetter( originalName, this.property.typeAnnotation, - thisValue + thisSet ); return [field, getter, setter]; } - generateInitializeStruct(newName: string, originalName: string): arkts.AstNode { - const localStorageLinkValueStr: string | undefined = getLocalStorageLinkValueInAnnotation(this.property); - if (!localStorageLinkValueStr) { - throw new Error('LocalStorageLink required only one value!!'); - } - - const call = arkts.factory.createCallExpression( - arkts.factory.createIdentifier(StateManagementTypes.STORAGE_LINK_STATE), - this.property.typeAnnotation ? [this.property.typeAnnotation] : [], - [ - arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier('_entry_local_storage_'), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ), - arkts.factory.createStringLiteral(localStorageLinkValueStr), - this.property.value ?? arkts.factory.createUndefinedLiteral(), - ] - ); - collectStateManagementTypeImport(StateManagementTypes.STORAGE_LINK_STATE); + translateGetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + returnValue: arkts.Expression + ): arkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } - return arkts.factory.createAssignmentExpression( - arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier(newName), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ), - arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, - call - ); + translateSetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + statement: arkts.AstNode + ): arkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); } } diff --git a/arkui-plugins/ui-plugins/property-translators/utils.ts b/arkui-plugins/ui-plugins/property-translators/utils.ts index 2a750395a..67837884f 100644 --- a/arkui-plugins/ui-plugins/property-translators/utils.ts +++ b/arkui-plugins/ui-plugins/property-translators/utils.ts @@ -360,7 +360,7 @@ function getDifferentAnnoTypeValue(value: arkts.Expression): string | boolean { return value.dumpSrc(); } -export function generateGetOrSetCall(beforCall: arkts.AstNode, type: GetSetTypes) { +export function generateGetOrSetCall(beforCall: arkts.AstNode, type: GetSetTypes): arkts.CallExpression { return arkts.factory.createCallExpression( arkts.factory.createMemberExpression( beforCall, -- Gitee