diff --git a/arkui-plugins/common/arkts-utils.ts b/arkui-plugins/common/arkts-utils.ts index 59016f2fd2f84d36b1bdb5df737e15e5f30c3004..1b01d924856e72a453897a32d3fbf7dbefd5098d 100644 --- a/arkui-plugins/common/arkts-utils.ts +++ b/arkui-plugins/common/arkts-utils.ts @@ -17,7 +17,6 @@ import * as arkts from '@koalaui/libarkts'; import { DeclarationCollector } from './declaration-collector'; import { ARKUI_IMPORT_PREFIX_NAMES, DecoratorNames } from './predefines'; - /** * create and insert `import { as } from ` to the top of script's statements. */ @@ -39,6 +38,10 @@ export function createAndInsertImportDeclaration( return; } +export function isNumeric(str: string): boolean { + return /^\d+$/.test(str); +} + export function annotation(name: string): arkts.AnnotationUsage { const ident: arkts.Identifier = arkts.factory.createIdentifier(name).setAnnotationUsage(); const annotation: arkts.AnnotationUsage = arkts.factory.createAnnotationUsage(ident); diff --git a/arkui-plugins/common/predefines.ts b/arkui-plugins/common/predefines.ts index bfe8a03131d253aa28895e9bc00638ffaf900d83..e2ff4e25b06d0c68ee84a12cc4da16417d89d561 100644 --- a/arkui-plugins/common/predefines.ts +++ b/arkui-plugins/common/predefines.ts @@ -95,6 +95,28 @@ export enum EntryWrapperNames { PARAM = 'param' } +export enum ObservedNames { + V1_RERENDER_ID = '____V1RenderId', + RERENDER_ID = 'renderId', + SET_V1_RERENDER_ID = 'setV1RenderId', + META = 'meta', + CONDITIONAL_ADD_REF = 'conditionalAddRef', + ADD_REF = 'addRef', + SHOULD_ADD_REF = 'shouldAddRef', + NEW_NAME = 'newName', + PROPERTY_PREFIX = '', + NEW_VALUE = 'newValue', + FIRE_CHANGE = 'fireChange', + EXECUATE_WATCHES = 'executeOnSubscribingWatches', +} + +export enum MonitorNames { + PATH = 'path', + VALUE_CALL_CACK = 'valueCallback', + I_MONITOR = 'IMonitor', + M_PARAM = '_m', +} + export enum EntryParamNames { ENTRY_STORAGE = 'storage', ENTRY_USE_SHARED_STORAGE = 'useSharedStorage', @@ -115,6 +137,7 @@ export enum DecoratorNames { CONSUME = 'Consume', OBJECT_LINK = 'ObjectLink', OBSERVED = 'Observed', + OBSERVED_V2 = 'ObservedV2', WATCH = 'Watch', BUILDER_PARAM = 'BuilderParam', BUILDER = 'Builder', @@ -123,6 +146,7 @@ export enum DecoratorNames { LOCAL_STORAGE_LINK = 'LocalStorageLink', REUSABLE = 'Reusable', TRACK = 'Track', + TRACE = 'Trace', JSONSTRINGIFYIGNORE = 'JSONStringifyIgnore', JSONRENAME = 'JSONRename', ANIMATABLE_EXTEND = 'AnimatableExtend', @@ -130,6 +154,17 @@ export enum DecoratorNames { LOCAL = 'Local', LOCAL_STORAGE_PROP_REF = 'LocalStoragePropRef', STORAGE_PROP_REF = 'StoragePropRef', + PARAM = 'Param', + ONCE = 'Once', + PROVIDER = 'Provider', + CONSUMER = 'Consumer', + MONITOR = 'Monitor', + COMPUTED = 'Computed', + EVENT = 'Event', +} + +export enum TypeNames { + NULLISH_TYPE = 'NullishType', } export enum DecoratorIntrinsicNames { @@ -152,6 +187,12 @@ export enum StateManagementTypes { PROP_REF_DECORATED = 'IPropRefDecoratedVariable', LOCAL_DECORATED = 'ILocalDecoratedVariable', LOCAL_STORAGE_PROP_REF_DECORATED = 'ILocalStoragePropRefDecoratedVariable', + PARAM_DECORATED = 'IParamDecoratedVariable', + ONCE_DECORATED = 'IParamOnceDecoratedVariable', + PROVIDER_DECORATED = 'IProviderDecoratedVariable', + CONSUMER_DECORATED = 'IConsumerDecoratedVariable', + COMPUTED_DECORATED = 'IComputedDecoratedVariable', + MONITOR_DECORATED = 'IMonitorDecoratedVariable', MUTABLE_STATE_META = 'IMutableStateMeta', OBSERVED_OBJECT = 'IObservedObject', WATCH_ID_TYPE = 'WatchIdType', @@ -177,6 +218,14 @@ export enum StateManagementTypes { MAKE_OBJECT_LINK = 'makeObjectLink', MAKE_SUBSCRIBED_WATCHES = 'makeSubscribedWatches', MAKE_MUTABLESTATE_META = 'makeMutableStateMeta', + MAKE_PARAM = 'makeParam', + MAKE_PARAM_ONCE = 'makeParamOnce', + MAKE_PROVIDER = 'makeProvider', + MAKE_CONSUMER = 'makeConsumer', + MAKE_COMPUTED = 'makeComputed', + MAKE_MONITOR = 'makeMonitor', + UI_UTILS = 'UIUtils', + MAKE_OBSERVED = 'makeObserved', } export enum AnimationNames { @@ -231,6 +280,10 @@ export const DECORATOR_TYPE_MAP = new Map( [DecoratorNames.PROVIDE, StateManagementTypes.PROVIDE_DECORATED], [DecoratorNames.CONSUME, StateManagementTypes.CONSUME_DECORATED], [DecoratorNames.LOCAL, StateManagementTypes.LOCAL_DECORATED], + [DecoratorNames.PARAM, StateManagementTypes.PARAM_DECORATED], + [DecoratorNames.ONCE, StateManagementTypes.ONCE_DECORATED], + [DecoratorNames.PROVIDER, StateManagementTypes.PROVIDER_DECORATED], + [DecoratorNames.CONSUMER, StateManagementTypes.CONSUMER_DECORATED], ]); export const INTERMEDIATE_IMPORT_SOURCE: Map = new Map([ @@ -250,6 +303,12 @@ export const INTERMEDIATE_IMPORT_SOURCE: Map = new Map = new Map = new Map [StateManagementTypes.STORAGE_LINK_STATE, 'arkui.stateManagement.runtime'], [StateManagementTypes.OBSERVABLE_PROXY, 'arkui.stateManagement.runtime'], [StateManagementTypes.PROP_STATE, 'arkui.stateManagement.runtime'], + [StateManagementTypes.UI_UTILS, 'arkui.stateManagement.utils'], [AnimationNames.ANIMATABLE_ARITHMETIC, 'arkui.component.common'] ]); diff --git a/arkui-plugins/test/demo/mock/decorators/computed/computed-in-observedv2-class.ets b/arkui-plugins/test/demo/mock/decorators/computed/computed-in-observedv2-class.ets new file mode 100644 index 0000000000000000000000000000000000000000..864244caa3e743f348dabf389489383fe0d08806 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/computed/computed-in-observedv2-class.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 { Computed, ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +@ObservedV2 +class Name { + @Trace firstName: string = 'Hua'; + @Trace lastName: string = 'Li'; + + @Computed + get fullName(): string { + console.info('---------Computed----------'); + return this.firstName + ' ' + this.lastName; + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/computed/computed-in-struct.ets b/arkui-plugins/test/demo/mock/decorators/computed/computed-in-struct.ets new file mode 100644 index 0000000000000000000000000000000000000000..ec221d404da24d6fde57e57ced996cb7bccd7851 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/computed/computed-in-struct.ets @@ -0,0 +1,46 @@ +/* + * 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 { ComponentV2, Column, Button, Divider, Text } from "@ohos.arkui.component" +import { Computed, Local } from "@ohos.arkui.stateManagement" + +@ComponentV2 +struct Index { + @Local firstName: string = 'Li'; + @Local lastName: string = 'Hua'; + age: number = 20; + + @Computed + get fullName(): string { + console.info("---------Computed----------"); + return this.firstName + ' ' + this.lastName + this.age; + } + + build() { + Column() { + Text(this.lastName + ' ' + this.firstName) + Text(this.lastName + ' ' + this.firstName) + Divider() + Text(this.fullName) + Text(this.fullName) + Button('changed lastName').onClick((e) => { + this.lastName += 'a'; + }) + Button('changed age').onClick((e) => { + this.age++; + }) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/event/event-initialize.ets b/arkui-plugins/test/demo/mock/decorators/event/event-initialize.ets new file mode 100644 index 0000000000000000000000000000000000000000..a596e9324b48f3d19ef390534be812e437e6a7a4 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/event/event-initialize.ets @@ -0,0 +1,52 @@ +/* + * 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 { ComponentV2, Column, Text } from "@ohos.arkui.component" +import { Event, Param, Local } from "@ohos.arkui.stateManagement" + +@ComponentV2 +struct Child { + @Param index: number = 0; + @Event changeIndex: (val: number) => void; + @Event testEvent: (val: number) => number; + @Event testEvent2: (val: number) => number = (val: number) => { return val }; + + build() { + Column() { + Text(`Child index: ${this.index}`) + .onClick((e) => { + this.changeIndex(20); + console.log(`after changeIndex ${this.index}`); + }) + } + } +} + +@ComponentV2 +struct Index { + @Local index: number = 0; + + build() { + Column() { + Child({ + index: this.index, + changeIndex: (val: number) => { + this.index = val; + console.log(`in changeIndex ${this.index}`); + } + }) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/local/local-basic-type.ets b/arkui-plugins/test/demo/mock/decorators/local/local-basic-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..178326815e36b14b66da2478a372ba3522282597 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/local/local-basic-type.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 { ComponentV2 } from "@ohos.arkui.component" +import { Local } from "@ohos.arkui.stateManagement" + +@ComponentV2 +struct Parent { + @Local localVar1: string = 'stateVar1'; + @Local localVar2: number = 50; + @Local localVar3: boolean = true; + @Local localVar4: undefined = undefined; + @Local localVar5: null = null; + + build() { + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/local/local-complex-type.ets b/arkui-plugins/test/demo/mock/decorators/local/local-complex-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..7a0d9f40ededb353f3ae58137c0ba9ee059dcae4 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/local/local-complex-type.ets @@ -0,0 +1,48 @@ +/* + * 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 { ComponentV2 } from "@ohos.arkui.component" +import { Local } from "@ohos.arkui.stateManagement" + +class Per { + num: number; + constructor(num: number) { + this.num = num; + } +} + +enum StateType { + TYPE1 = 0, + TYPE2 = 1, + TYPE3 = 3 +} + +@ComponentV2 +struct Parent { + @Local localVar1: Per = new Per(6); + @Local localVar2: Array = new Array(3,6,8); + @Local localVar3: StateType = StateType.TYPE3; + @Local localVar4: Set = new Set(new Array('aa', 'bb')); + @Local localVar5: boolean[] = [true, false]; + @Local localVar6: Array = new Array(new Per(7), new Per(11)); + @Local localVar7: Per[] = [new Per(7), new Per(11)]; + @Local localVar9: Date = new Date('2025-4-23'); + @Local localVar10: Map = new Map([[0, new Per(7)], [1, new Per(10)]]); + @Local localVar11: string | number = 0.0; + @Local localVar12: Set | Per = new Per(6); + + build() { + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/monitor/monitor-in-observedv2-class.ets b/arkui-plugins/test/demo/mock/decorators/monitor/monitor-in-observedv2-class.ets new file mode 100644 index 0000000000000000000000000000000000000000..a10548d25c6805cc35facd9822e1de3bdaa1eaec --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/monitor/monitor-in-observedv2-class.ets @@ -0,0 +1,67 @@ +/* + * 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 { ComponentV2, Column, Button } from "@ohos.arkui.component" +import { Monitor, IMonitor, ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +@ObservedV2 +class Info { + @Trace name: string = "Tom"; + @Trace region: string = "North"; + @Trace job: string = "Teacher"; + age: number = 25; + + @Monitor(["name"]) + onNameChange(monitor: IMonitor) { + console.info(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); + } + + @Monitor(["age"]) + onAgeChange(monitor: IMonitor) { + console.info(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); + } + + @Monitor(["region", "job"]) + onChange(monitor: IMonitor) { + monitor.dirty.forEach((path: string) => { + console.info(`${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); + }) + } +} + +@ComponentV2 +struct Index { + info: Info = new Info(); + build() { + Column() { + Button("change name") + .onClick((e) => { + this.info.name = "Jack"; + }) + Button("change age") + .onClick((e) => { + this.info.age = 26; + }) + Button("change region") + .onClick((e) => { + this.info.region = "South"; + }) + Button("change job") + .onClick((e) => { + this.info.job = "Driver"; + }) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/monitor/monitor-in-struct.ets b/arkui-plugins/test/demo/mock/decorators/monitor/monitor-in-struct.ets new file mode 100644 index 0000000000000000000000000000000000000000..412af0371e5485ce0b690f27dd37258092b24825 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/monitor/monitor-in-struct.ets @@ -0,0 +1,40 @@ +/* + * 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 { ComponentV2, Column, Button } from "@ohos.arkui.component" +import { Monitor, Local, IMonitor } from "@ohos.arkui.stateManagement" + +@ComponentV2 +struct Index { + @Local message: string = "Hello World"; + @Local name: string = "Tom"; + @Local age: number = 24; + @Monitor(["message", "name"]) + onStrChange(monitor: IMonitor) { + monitor.dirty.forEach((path: string) => { + console.info(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); + }); + } + + build() { + Column() { + Button("change string") + .onClick((e) => { + this.message += "!"; + this.name = "Jack"; + }) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/monitor/monitor-params.ets b/arkui-plugins/test/demo/mock/decorators/monitor/monitor-params.ets new file mode 100644 index 0000000000000000000000000000000000000000..395c70a69a07eece897d0f24e03215bca44c864f --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/monitor/monitor-params.ets @@ -0,0 +1,78 @@ +/* + * 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 { ComponentV2, Column, Button } from "@ohos.arkui.component" +import { Monitor, IMonitor, ObservedV2, Trace, Local } from "@ohos.arkui.stateManagement" + +class AAA { + aaa: BBB = new BBB() +} + +class BBB { + bbb: CCC = new CCC() +} + +class CCC { + ccc: Array = new Array(new DDD(10), new DDD(12), new DDD(16)) +} + +class DDD { + dd: number[] + constructor(dd: number) { + this.dd = []; + for(let i = 4; i < dd; i++) { + this.dd.push(i); + } + } +} + +class EEE { + ee: FFF = new FFF() +} + +class FFF { + ff: GGG = new GGG() +} + +class GGG {} + +@ObservedV2 +class Info { + @Trace name: string = "Tom"; + @Trace strArr: string[] = ["North", "east"]; + @Trace job: AAA = new AAA(); + age: number = 25; + + @Monitor(["name"]) + onNameChange(monitor: IMonitor) {} + + @Monitor(["strArr.0", "age"]) + onAgeChange(monitor: IMonitor) {} + + @Monitor(["job.aaa.bbb.ccc.1.dd.0"]) + onJobChange(monitor: IMonitor) {} +} + +@ComponentV2 +struct Index { + @Local per: EEE = new EEE() + @Local v1: boolean = true + @Local numArr: string[] = ['1','3','5'] + + @Monitor(["per.ee.ff", "v1", "numArr.1"]) + onPerChange(monitor: IMonitor) {} + + build() {} +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/observed-track/observed-track.ets b/arkui-plugins/test/demo/mock/decorators/observed-track/observed-track.ets index 98d037faab81d8617518243c6a0718daab34b54d..b4023bb2e7fa862f71f8ed2a60d958bd5a9fe9bf 100644 --- a/arkui-plugins/test/demo/mock/decorators/observed-track/observed-track.ets +++ b/arkui-plugins/test/demo/mock/decorators/observed-track/observed-track.ets @@ -20,6 +20,11 @@ import { Observed, Track } from "@ohos.arkui.stateManagement" class B { propB: number = 1 @Track trackB: number = 2 + @Track newProp: boolean; + + constructor(newProp: boolean) { + this.newProp = newProp; + } } @Component diff --git a/arkui-plugins/test/demo/mock/decorators/observedv2-trace/base-observedv2-trace.ets b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/base-observedv2-trace.ets new file mode 100644 index 0000000000000000000000000000000000000000..89a06a3cb68deead0011586e018d95ad550e79db --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/base-observedv2-trace.ets @@ -0,0 +1,33 @@ +/* + * 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 { ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +@ObservedV2 +class CC { + propB: number = 1 + @Trace traceB: number = 2 +} + +@ObservedV2 +class DD { + propB: number = 1 + @Trace traceE: number = 2 + @Trace tracef: number = 2 + vv: string + constructor(vv1: string) { + this.vv = vv1 + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-serialization.ets b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-serialization.ets new file mode 100644 index 0000000000000000000000000000000000000000..035c520612aef06b76325a2ead9378cce477c6f3 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-serialization.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 { ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +@Retention({policy: "SOURCE"}) +export declare @interface TestDecor {} + +@ObservedV2 +class testJSONStringifyIgnore { + var1: number = 1 + @Trace var2: number = 2 + @JSONStringifyIgnore var3: number = 3 + @TestDecor var4: number = 4 + @JSONStringifyIgnore @Trace var5: number = 5 + @JSONStringifyIgnore @TestDecor var6: number = 6 + @TestDecor @Trace var7: number = 7 + @JSONStringifyIgnore @TestDecor @Trace var8: number = 8 +} + +@ObservedV2 +class testJsonRename { + var1: number = 1 + @Trace var2: number = 2 + @JSONRename('name3') var3: number = 3 + @TestDecor var4: number = 4 + @JSONRename('name5') @Trace var5: number = 5 + @JSONRename('name6') @TestDecor var6: number = 6 + @TestDecor @Trace var7: number = 7 + @JSONRename('name8') @TestDecor @Trace var8: number = 8 +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-extends.ets b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-extends.ets new file mode 100644 index 0000000000000000000000000000000000000000..94c66ff97c3d432b9639368884a12ae72ca0d7c7 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-extends.ets @@ -0,0 +1,32 @@ +/* + * 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 { ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +@ObservedV2 +class A { + propA: number = 1 + @Trace traceA: number = 2 +} + + +class G extends A { + propG: number = 1; +} + +@ObservedV2 +class H extends G { + @Trace propG: number = 1; +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-implements.ets b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-implements.ets new file mode 100644 index 0000000000000000000000000000000000000000..fdd79e4e5420e52c33bda12058a0ee9f3750c5b8 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-implements.ets @@ -0,0 +1,34 @@ +/* + * 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 { ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +interface PropInterface { + propF: number +} + +interface trackInterface { + trackF: number +} + +@ObservedV2 +class F implements PropInterface, trackInterface { + @Trace propF: number = 1 + trackF: number = 2 + @Trace bb: boolean + constructor(bb: boolean) { + this.bb = bb + } +} diff --git a/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-types.ets b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-types.ets new file mode 100644 index 0000000000000000000000000000000000000000..a5197ef85fc7533691e77c2fc8ca126551f484a4 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/observedv2-trace-types.ets @@ -0,0 +1,51 @@ +/* + * 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 { ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +class Person{} + +enum Status { + Success = 200, + NotFound = 404, + ServerError = 500 +} + +@ObservedV2 +class mixed1 { + @Trace numA: number = 33; + @Trace stringA: string = 'AA'; + @Trace booleanA: boolean = true; + @Trace arrayA: number[] = [1,2,3]; + @Trace objectA: Object = {}; + @Trace dateA: Date = new Date('2021-08-08'); + @Trace setA: Set = new Set(); + @Trace mapA: Map = new Map(); + @Trace unionA: string | undefined = ""; + @Trace classA: Person = new Person(); + @Trace enumA: Status = Status.NotFound; + + numB: number = 33; + stringB: string = 'AA'; + booleanB: boolean = true; + arrayB: number[] = [1,2,3]; + objectB: Object = {}; + dateB: Date = new Date('2021-08-08'); + setB: Set = new Set(); + mapB: Map = new Map(); + unionB: string | undefined = ""; + classB: Person = new Person(); + enumB: Status = Status.NotFound; +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/observedv2-trace/static-observedv2-trace.ets b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/static-observedv2-trace.ets new file mode 100644 index 0000000000000000000000000000000000000000..ef145e65f0aaa19d6e45dcccfdc74762e6c205a5 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/static-observedv2-trace.ets @@ -0,0 +1,23 @@ +/* + * 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 { ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +@ObservedV2 +class CC { + propB: number = 1 + static propA: number = 1 + @Trace static traceB: number = 2 +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/observedv2-trace/trace-with-constructor.ets b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/trace-with-constructor.ets new file mode 100644 index 0000000000000000000000000000000000000000..5210335ab8e9c36c8998009c72015104259554a0 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/observedv2-trace/trace-with-constructor.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 { ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +@ObservedV2 +class FF { + propB: number = 1 + @Trace traceE: number = 2 + @Trace vv: string + constructor(vv1: string) { + this.vv = vv1 + } +} diff --git a/arkui-plugins/test/demo/mock/decorators/once/once-basic-type.ets b/arkui-plugins/test/demo/mock/decorators/once/once-basic-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..dc79a560473e01bc4b53dd1ec54af303c2ff4649 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/once/once-basic-type.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 { ComponentV2 } from "@ohos.arkui.component" +import { Param, Once } from "@ohos.arkui.stateManagement" + +@ComponentV2 +struct Parent { + @Param @Once onceVar1: string = 'stateVar1'; + @Param @Once onceVar2: number = 50; + @Param @Once onceVar3: boolean = true; + @Param @Once onceVar4: undefined = undefined; + @Param @Once onceVar5: null = null; + + build() { + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/once/once-complex-type.ets b/arkui-plugins/test/demo/mock/decorators/once/once-complex-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..0ce4ad2402cf2cf876071988c90393c24883db08 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/once/once-complex-type.ets @@ -0,0 +1,48 @@ +/* + * 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 { ComponentV2 } from "@ohos.arkui.component" +import { Param, Once } from "@ohos.arkui.stateManagement" + +class Per { + num: number; + constructor(num: number) { + this.num = num; + } +} + +enum StateType { + TYPE1 = 0, + TYPE2 = 1, + TYPE3 = 3 +} + +@ComponentV2 +struct Parent { + @Once @Param onceVar1: Per = new Per(6); + @Once @Param onceVar2: Array = new Array(3,6,8); + @Once @Param onceVar3: StateType = StateType.TYPE3; + @Once @Param onceVar4: Set = new Set(new Array('aa', 'bb')); + @Once @Param onceVar5: boolean[] = [true, false]; + @Once @Param onceVar6: Array = new Array(new Per(7), new Per(11)); + @Once @Param onceVar7: Per[] = [new Per(7), new Per(11)]; + @Once @Param onceVar8: (sr: string)=>void = (sr: string)=>{}; + @Once @Param onceVar9: Date = new Date('2025-4-23'); + @Once @Param onceVar10: Map = new Map([[0, new Per(7)], [1, new Per(10)]]); + @Once @Param onceVar11: string | number = 0.0; + @Once @Param onceVar12: Set | Per = new Per(6); + + build() {} +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/once/once-with-require.ets b/arkui-plugins/test/demo/mock/decorators/once/once-with-require.ets new file mode 100644 index 0000000000000000000000000000000000000000..e346a73dabf5f08c2dc9ff3585aafeeeb8c49e56 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/once/once-with-require.ets @@ -0,0 +1,56 @@ +/* + * 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 { ComponentV2, Column, Text, Button } from "@ohos.arkui.component" +import { Param, Once, ObservedV2, Trace, Require, Local } from "@ohos.arkui.stateManagement" + +@ObservedV2 +class Info { + @Trace name: string = "info"; +} + +@ComponentV2 +struct Child { + @Param @Once onceParamNum: number = 0; + @Param @Once @Require onceParamInfo: Info; + + build() { + Column() { + Text(`Child onceParamNum: ${this.onceParamNum}`) + Text(`Child onceParamInfo: ${this.onceParamInfo.name}`) + Button("changeOnceParamNum") + .onClick((e) => { + this.onceParamNum++; + }) + } + } +} + +@ComponentV2 +struct Index { + @Local localNum: number = 10; + @Local localInfo: Info = new Info(); + + build() { + Column() { + Text(`Parent localNum: ${this.localNum}`) + Text(`Parent localInfo: ${this.localInfo.name}`) + Child({ + onceParamNum: this.localNum, + onceParamInfo: this.localInfo + }) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/param/param-basic-type.ets b/arkui-plugins/test/demo/mock/decorators/param/param-basic-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..a80f8d1d3d855cd055406744c2d0df69e7838d65 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/param/param-basic-type.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 { ComponentV2 } from "@ohos.arkui.component" +import { Param } from "@ohos.arkui.stateManagement" + +@ComponentV2 +struct Parent { + @Param paramVar1: string = 'stateVar1'; + @Param paramVar2: number = 50; + @Param paramVar3: boolean = true; + @Param paramVar4: undefined = undefined; + @Param paramVar5: null = null; + + build() { + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/param/param-complex-type.ets b/arkui-plugins/test/demo/mock/decorators/param/param-complex-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..52a543a33cb7d50115364d2ba7c35008272a1871 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/param/param-complex-type.ets @@ -0,0 +1,48 @@ +/* + * 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 { ComponentV2 } from "@ohos.arkui.component" +import { Param } from "@ohos.arkui.stateManagement" + +class Per { + num: number; + constructor(num: number) { + this.num = num; + } +} + +enum StateType { + TYPE1 = 0, + TYPE2 = 1, + TYPE3 = 3 +} + +@ComponentV2 +struct Parent { + @Param paramVar1: Per = new Per(6); + @Param paramVar2: Array = new Array(3,6,8); + @Param paramVar3: StateType = StateType.TYPE3; + @Param paramVar4: Set = new Set(new Array('aa', 'bb')); + @Param paramVar5: boolean[] = [true, false]; + @Param paramVar6: Array = new Array(new Per(7), new Per(11)); + @Param paramVar7: Per[] = [new Per(7), new Per(11)]; + @Param paramVar8: (sr: string)=>void = (sr: string)=>{}; + @Param paramVar9: Date = new Date('2025-4-23'); + @Param paramVar10: Map = new Map([[0, new Per(7)], [1, new Per(10)]]); + @Param paramVar11: string | number = 0.0; + @Param paramVar12: Set | Per = new Per(6); + + build() {} +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/param/param-with-require.ets b/arkui-plugins/test/demo/mock/decorators/param/param-with-require.ets new file mode 100644 index 0000000000000000000000000000000000000000..b6e542ad2c32debc419b468f96dbadcdd8ef66c9 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/param/param-with-require.ets @@ -0,0 +1,75 @@ +/* + * 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 { ComponentV2, Column, ForEach, Button, Text } from "@ohos.arkui.component" +import { Param, Require, Local } from "@ohos.arkui.stateManagement" + +class Region { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +class Info { + name: string; + age: number; + region: Region; + constructor(name: string, age: number, x: number, y: number) { + this.name = name; + this.age = age; + this.region = new Region(x, y); + } +} + +@ComponentV2 +struct Index { + @Local infoList: Info[] = [new Info("Alice", 8, 0, 0), new Info("Barry", 10, 1, 20), new Info("Cindy", 18, 24, 40)]; + build() { + Column() { + ForEach(this.infoList, (info: Info) => { + MiddleComponent({ info: info }) + }) + Button("change") + .onClick((e) => { + this.infoList[0] = new Info("Atom", 40, 27, 90); + this.infoList[1].name = "Bob"; + this.infoList[2].region = new Region(7, 9); + }) + } + } +} +@ComponentV2 +struct MiddleComponent { + @Require @Param info: Info; + build() { + Column() { + Text(`name: ${this.info.name}`) + Text(`age: ${this.info.age}`) + SubComponent({ region: this.info.region }) + } + } +} +@ComponentV2 +struct SubComponent { + @Require @Param region: Region; + build() { + Column() { + Text(`region: ${this.region.x}-${this.region.y}`) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/provide-and-consume/consume-basic-type.ets b/arkui-plugins/test/demo/mock/decorators/provide-and-consume/consume-basic-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..bcc75a5d61b6254659b266e12d4b69af4aff8c68 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/provide-and-consume/consume-basic-type.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 } from "@ohos.arkui.component" +import { Consume } from "@ohos.arkui.stateManagement" + +@Component +struct PropParent { + @Consume conVar1: string; + @Consume conVar2: number; + @Consume conVar3: boolean; + @Consume conVar4: undefined; + @Consume conVar5: null; + + build() { + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/provide-and-consume/consume-complex-type.ets b/arkui-plugins/test/demo/mock/decorators/provide-and-consume/consume-complex-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..848f1dbf470951722c7fb7479c514877ba944e28 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/provide-and-consume/consume-complex-type.ets @@ -0,0 +1,50 @@ +/* + * 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 } from "@ohos.arkui.component" +import { Consume } from "@ohos.arkui.stateManagement" + +class Per { + num: number; + constructor(num: number) { + this.num = num; + } +} + +enum PropType { + TYPE1 = 0, + TYPE2 = 1, + TYPE3 = 3 +} + +@Component +struct Parent { + @Consume conVar1: Per; + @Consume conVar2: Array; + @Consume conVar3: PropType; + @Consume conVar4: Set; + @Consume conVar5: boolean[]; + @Consume conVar6: Array; + @Consume conVar7: Per[]; + @Consume conVar8: (sr: string)=>void; + @Consume conVar9: Date; + @Consume conVar10: Map; + @Consume conVar11: string | number; + @Consume conVar12: Set | Per; + @Consume conVar13: Set | null; + + build() { + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/provide-and-consume/provide-to-consume.ets b/arkui-plugins/test/demo/mock/decorators/provide-and-consume/provide-to-consume.ets new file mode 100644 index 0000000000000000000000000000000000000000..c0768056a465271652e3323a7317759d7f64f103 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/provide-and-consume/provide-to-consume.ets @@ -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 { Component, Column, Text } from "@ohos.arkui.component" +import { Consume, Provide } from "@ohos.arkui.stateManagement" + +@Component +struct Child { + @Consume num: number; + @Consume('ss') str: string; + + build() { + Column() { + Text(`Child num: ${this.num}`) + Text(`Child str: ${this.str}`) + } + } +} + +@Component +struct Parent { + @Provide num: number = 10; + @Provide({ alias: 'ss' }) str: string = 'hello'; + + build() { + Column() { + Text(`Parent num: ${this.num}`) + Text(`Parent str: ${this.str}`) + Child() + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/consumer-basic-type.ets b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/consumer-basic-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..cedc4f2eb37fb4ab54838328b81e482ca3c3cdfe --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/consumer-basic-type.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 { ComponentV2 } from "@ohos.arkui.component" +import { Consumer } from "@ohos.arkui.stateManagement" + +@ComponentV2 +struct Parent { + @Consumer consumerVar1: string = 'propVar1'; + @Consumer consumerVar2: number = 50; + @Consumer consumerVar3: boolean = true; + @Consumer consumerVar4: undefined = undefined; + @Consumer consumerVar5: null = null; + + build() { + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/consumer-complex-type.ets b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/consumer-complex-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..243a52feba2cf9c8bf77761bcfb2d418b70054de --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/consumer-complex-type.ets @@ -0,0 +1,47 @@ +/* + * 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 { ComponentV2 } from "@ohos.arkui.component" +import { Consumer } from "@ohos.arkui.stateManagement" + +class Per { + num: number; + constructor(num: number) { + this.num = num; + } +} + +enum StateType { + TYPE1 = 0, + TYPE2 = 1, + TYPE3 = 3 +} + +@ComponentV2 +struct Parent { + @Consumer paramVar1: Per = new Per(6); + @Consumer paramVar2: Array = new Array(3,6,8); + @Consumer paramVar3: StateType = StateType.TYPE3; + @Consumer paramVar4: Set = new Set(new Array('aa', 'bb')); + @Consumer paramVar5: boolean[] = [true, false]; + @Consumer paramVar6: Array = new Array(new Per(7), new Per(11)); + @Consumer paramVar7: Per[] = [new Per(7), new Per(11)]; + @Consumer paramVar9: Date = new Date('2025-4-23'); + @Consumer paramVar10: Map = new Map([[0, new Per(7)], [1, new Per(10)]]); + @Consumer paramVar11: string | number = 0.0; + @Consumer paramVar12: Set | Per = new Per(6); + + build() {} +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-basic-type.ets b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-basic-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..312eed3373e1d928864ec550d582b37ba9ce7fb1 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-basic-type.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 { ComponentV2 } from "@ohos.arkui.component" +import { Provider } from "@ohos.arkui.stateManagement" + +@ComponentV2 +struct Parent { + @Provider providerVar1: string = 'propVar1'; + @Provider providerVar2: number = 50; + @Provider providerVar3: boolean = true; + @Provider providerVar4: undefined = undefined; + @Provider providerVar5: null = null; + + build() { + } +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-complex-type.ets b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-complex-type.ets new file mode 100644 index 0000000000000000000000000000000000000000..1a6fc86c70c278268b6d8c5b910b2561e84302cb --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-complex-type.ets @@ -0,0 +1,47 @@ +/* + * 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 { ComponentV2 } from "@ohos.arkui.component" +import { Provider } from "@ohos.arkui.stateManagement" + +class Per { + num: number; + constructor(num: number) { + this.num = num; + } +} + +enum StateType { + TYPE1 = 0, + TYPE2 = 1, + TYPE3 = 3 +} + +@ComponentV2 +struct Parent { + @Provider paramVar1: Per = new Per(6); + @Provider paramVar2: Array = new Array(3,6,8); + @Provider paramVar3: StateType = StateType.TYPE3; + @Provider paramVar4: Set = new Set(new Array('aa', 'bb')); + @Provider paramVar5: boolean[] = [true, false]; + @Provider paramVar6: Array = new Array(new Per(7), new Per(11)); + @Provider paramVar7: Per[] = [new Per(7), new Per(11)]; + @Provider paramVar9: Date = new Date('2025-4-23'); + @Provider paramVar10: Map = new Map([[0, new Per(7)], [1, new Per(10)]]); + @Provider paramVar11: string | number = 0.0; + @Provider paramVar12: Set | Per = new Per(6); + + build() {} +} \ No newline at end of file diff --git a/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-to-consumer.ets b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-to-consumer.ets new file mode 100644 index 0000000000000000000000000000000000000000..6bb0cd890317bf011c6f641d1637c15287232551 --- /dev/null +++ b/arkui-plugins/test/demo/mock/decorators/provider-and-consumer/provider-to-consumer.ets @@ -0,0 +1,72 @@ +/* + * 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 { ComponentV2, DragEvent, Button, Column, Text, ForEach, Divider } from "@ohos.arkui.component" +import { Provider, Consumer, Local, ObservedV2, Trace } from "@ohos.arkui.stateManagement" + +@ObservedV2 +class User { + @Trace name: string; + @Trace age: number; + + constructor(name: string, age: number) { + this.name = name; + this.age = age; + } +} + +const data: User[] = [new User('Json', 10), new User('Eric', 15)]; + +@ComponentV2 +struct Parent { + @Provider('data') users: User[] = data; + + + build() { + Column() { + Child() + Button('add new user') + .onClick((e) => { + this.users.push(new User('Molly', 18)); + }) + Button('age++') + .onClick((e) => { + this.users[0].age++; + }) + Button('change name') + .onClick((e) => { + this.users[0].name = 'Shelly'; + }) + } + } +} + + +@ComponentV2 +struct Child { + @Consumer('data') users: User[] = []; + + build() { + Column() { + ForEach(this.users, (item: User) => { + Column() { + Text(`name: ${item.name}`).fontSize(30) + Text(`age: ${item.age}`).fontSize(30) + Divider() + } + }) + } + } +} \ No newline at end of file diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/computed/computed-in-observedv2-class.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/computed/computed-in-observedv2-class.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd46414df8dd29538e2eea80e8dce6bbc26cb35a --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/computed/computed-in-observedv2-class.test.ts @@ -0,0 +1,141 @@ +/* + * 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 BUILDER_LAMBDA_DIR_PATH: string = 'decorators/computed'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, BUILDER_LAMBDA_DIR_PATH, 'computed-in-observedv2-class.ets'), +]; + +const pluginTester = new PluginTester('test @Computed decorator in @ObservedV2 class', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { Computed as Computed, ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + +@ObservedV2() class Name implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + @JSONRename({newName:"firstName"}) private __backing_firstName: string = "Hua"; + + @JSONStringifyIgnore() private __meta_firstName: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"lastName"}) private __backing_lastName: string = "Li"; + + @JSONStringifyIgnore() private __meta_lastName: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + private __computed_fullName = STATE_MGMT_FACTORY.makeComputed((() => { + console.info("---------Computed----------"); + return ((((this.firstName) + (" "))) + (this.lastName)); + }), "fullName"); + + @Computed() public get fullName(): string { + return this.__computed_fullName!.get(); + } + + public get firstName(): string { + this.conditionalAddRef(this.__meta_firstName); + return UIUtils.makeObserved(this.__backing_firstName); + } + + public set firstName(newValue: string) { + if (((this.__backing_firstName) !== (newValue))) { + this.__backing_firstName = newValue; + this.__meta_firstName.fireChange(); + this.executeOnSubscribingWatches("firstName"); + } + } + + public get lastName(): string { + this.conditionalAddRef(this.__meta_lastName); + return UIUtils.makeObserved(this.__backing_lastName); + } + + public set lastName(newValue: string) { + if (((this.__backing_lastName) !== (newValue))) { + this.__backing_lastName = newValue; + this.__meta_lastName.fireChange(); + this.executeOnSubscribingWatches("lastName"); + } + } + + public constructor() {} + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @Computed decorator in @ObservedV2 class', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/computed/computed-in-struct.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/computed/computed-in-struct.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ce255df6bdbc5052ed91936586e1abf3ae94461 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/computed/computed-in-struct.test.ts @@ -0,0 +1,165 @@ +/* + * 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 BUILDER_LAMBDA_DIR_PATH: string = 'decorators/computed'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, BUILDER_LAMBDA_DIR_PATH, 'computed-in-struct.ets'), +]; + +const pluginTester = new PluginTester('test @Computed decorator in @ComponentV2 struct', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ILocalDecoratedVariable as ILocalDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ButtonAttribute as ButtonAttribute } from "arkui.component.button"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, Button as Button, Divider as Divider, Text as Text } from "@ohos.arkui.component"; + +import { Computed as Computed, Local as Local } from "@ohos.arkui.stateManagement"; + +function main() {} + +@ComponentV2() final struct Index extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Index | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_firstName = STATE_MGMT_FACTORY.makeLocal(this, "firstName", "Li"); + this.__backing_lastName = STATE_MGMT_FACTORY.makeLocal(this, "lastName", "Hua"); + this.__backing_age = ((({let gensym___216981064 = initializers; + (((gensym___216981064) == (null)) ? undefined : gensym___216981064.age)})) ?? (20)); + } + + public __updateStruct(initializers: (__Options_Index | undefined)): void {} + + private __backing_firstName?: ILocalDecoratedVariable; + + public get firstName(): string { + return this.__backing_firstName!.get(); + } + + public set firstName(value: string) { + this.__backing_firstName!.set(value); + } + + private __backing_lastName?: ILocalDecoratedVariable; + + public get lastName(): string { + return this.__backing_lastName!.get(); + } + + public set lastName(value: string) { + this.__backing_lastName!.set(value); + } + + private __backing_age?: number; + + public get age(): number { + return (this.__backing_age as number); + } + + public set age(value: number) { + this.__backing_age = value; + } + + private __computed_fullName = STATE_MGMT_FACTORY.makeComputed((() => { + console.info("---------Computed----------"); + return ((((((this.firstName) + (" "))) + (this.lastName))) + (this.age)); + }), "fullName"); + + @Computed() public get fullName(): string { + return this.__computed_fullName!.get(); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Text(undefined, ((((this.lastName) + (" "))) + (this.firstName)), undefined, undefined); + Text(undefined, ((((this.lastName) + (" "))) + (this.firstName)), undefined, undefined); + Divider(undefined); + Text(undefined, this.fullName, undefined, undefined); + Text(undefined, this.fullName, undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.lastName += "a"; + })); + return; + }), "changed lastName", undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + (this.age++); + })); + return; + }), "changed age", undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Index { + set firstName(firstName: (string | undefined)) + + get firstName(): (string | undefined) + set __backing_firstName(__backing_firstName: (ILocalDecoratedVariable | undefined)) + + get __backing_firstName(): (ILocalDecoratedVariable | undefined) + set lastName(lastName: (string | undefined)) + + get lastName(): (string | undefined) + set __backing_lastName(__backing_lastName: (ILocalDecoratedVariable | undefined)) + + get __backing_lastName(): (ILocalDecoratedVariable | undefined) + set age(age: (number | undefined)) + + get age(): (number | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @Computed decorator in @ComponentV2 struct', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/event/event-initialize.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/event/event-initialize.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..783bcc981d0c1695a1c18d5f1924782e505d2801 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/event/event-initialize.test.ts @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as path from 'path'; +import { PluginTester } from '../../../../utils/plugin-tester'; +import { mockBuildConfig } from '../../../../utils/artkts-config'; +import { getRootPath, MOCK_ENTRY_DIR_PATH } from '../../../../utils/path-config'; +import { parseDumpSrc } from '../../../../utils/parse-string'; +import { recheck, uiNoRecheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const BUILDER_LAMBDA_DIR_PATH: string = 'decorators/event'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, BUILDER_LAMBDA_DIR_PATH, 'event-initialize.ets'), +]; + +const pluginTester = new PluginTester('test @Event decorator transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedParsedScript: string = ` +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, Text as Text } from "@ohos.arkui.component"; + +import { Event as Event, Param as Param, Local as Local } from "@ohos.arkui.stateManagement"; + +@ComponentV2() final struct Child extends CustomComponentV2 { + @Param() public index: number = 0; + + @Event() public changeIndex!: ((val: number)=> void); + + @Event() public testEvent!: ((val: number)=> number); + + @Event() public testEvent2: ((val: number)=> number) = ((val: number) => { + return val; + }); + + public build() { + Column(){ + Text(\`Child index: \${this.index}\`).onClick(((e) => { + this.changeIndex(20); + console.log(\`after changeIndex \${this.index}\`); + })); + }; + } + + public constructor() {} + +} + +@ComponentV2() final struct Index extends CustomComponentV2 { + @Local() public index: number = 0; + + public build() { + Column(){ + Child({ + index: this.index, + changeIndex: ((val: number) => { + this.index = val; + console.log(\`in changeIndex \${this.index}\`); + }), + }); + }; + } + + public constructor() {} + +} + +@ComponentV2() export interface __Options_Child { + index?: number; + @Param() __backing_index?: number; + changeIndex?: ((val: number)=> void); + testEvent?: ((val: number)=> number); + testEvent2?: ((val: number)=> number); + +} + +@ComponentV2() export interface __Options_Index { + index?: number; + @Local() __backing_index?: number; + +} +`; + +const expectedCheckedScript: string = ` +import { ILocalDecoratedVariable as ILocalDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { IParamDecoratedVariable as IParamDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { TextAttribute as TextAttribute } from "arkui.component.text"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, Text as Text } from "@ohos.arkui.component"; + +import { Event as Event, Param as Param, Local as Local } from "@ohos.arkui.stateManagement"; + +function main() {} + +@ComponentV2() final struct Child extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Child | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_index = STATE_MGMT_FACTORY.makeParam(this, "index", ((({let gensym___23942905 = initializers; + (((gensym___23942905) == (null)) ? undefined : gensym___23942905.index)})) ?? (0))); + this.__backing_changeIndex = ((({let gensym___204042774 = initializers; + (((gensym___204042774) == (null)) ? undefined : gensym___204042774.changeIndex)})) ?? (undefined)); + this.__backing_testEvent = ((({let gensym___124585092 = initializers; + (((gensym___124585092) == (null)) ? undefined : gensym___124585092.testEvent)})) ?? (undefined)); + this.__backing_testEvent2 = ((({let gensym___189097286 = initializers; + (((gensym___189097286) == (null)) ? undefined : gensym___189097286.testEvent2)})) ?? (((val: number) => { + return val; + }))); + } + + public __updateStruct(initializers: (__Options_Child | undefined)): void { + if (((({let gensym___91647805 = initializers; + (((gensym___91647805) == (null)) ? undefined : gensym___91647805.index)})) !== (undefined))) { + this.__backing_index!.update((initializers!.index as number)); + } + } + + private __backing_index?: IParamDecoratedVariable; + + public get index(): number { + return this.__backing_index!.get(); + } + + private __backing_changeIndex?: ((val: number)=> void); + + public get changeIndex(): ((val: number)=> void) { + return (this.__backing_changeIndex as ((val: number)=> void)); + } + + public set changeIndex(value: ((val: number)=> void)) { + this.__backing_changeIndex = value; + } + + private __backing_testEvent?: ((val: number)=> number); + + public get testEvent(): ((val: number)=> number) { + return (this.__backing_testEvent as ((val: number)=> number)); + } + + public set testEvent(value: ((val: number)=> number)) { + this.__backing_testEvent = value; + } + + private __backing_testEvent2?: ((val: number)=> number); + + public get testEvent2(): ((val: number)=> number) { + return (this.__backing_testEvent2 as ((val: number)=> number)); + } + + public set testEvent2(value: ((val: number)=> number)) { + this.__backing_testEvent2 = value; + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Text(@memo() ((instance: TextAttribute): void => { + instance.onClick(((e) => { + this.changeIndex(20); + console.log(\`after changeIndex \${this.index}\`); + })); + return; + }), \`Child index: \${this.index}\`, undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() final struct Index extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Index | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_index = STATE_MGMT_FACTORY.makeLocal(this, "index", 0); + } + + public __updateStruct(initializers: (__Options_Index | undefined)): void {} + + private __backing_index?: ILocalDecoratedVariable; + + public get index(): number { + return this.__backing_index!.get(); + } + + public set index(value: number) { + this.__backing_index!.set(value); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Child._instantiateImpl(undefined, (() => { + return new Child(); + }), { + index: this.index, + changeIndex: ((val: number) => { + this.index = val; + console.log(\`in changeIndex \${this.index}\`); + }), + }, undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Child { + set index(index: (number | undefined)) + + get index(): (number | undefined) + set __backing_index(__backing_index: (IParamDecoratedVariable | undefined)) + + get __backing_index(): (IParamDecoratedVariable | undefined) + set changeIndex(changeIndex: (((val: number)=> void) | undefined)) + + get changeIndex(): (((val: number)=> void) | undefined) + set testEvent(testEvent: (((val: number)=> number) | undefined)) + + get testEvent(): (((val: number)=> number) | undefined) + set testEvent2(testEvent2: (((val: number)=> number) | undefined)) + + get testEvent2(): (((val: number)=> number) | undefined) + +} + +@ComponentV2() export interface __Options_Index { + set index(index: (number | undefined)) + + get index(): (number | undefined) + set __backing_index(__backing_index: (ILocalDecoratedVariable | undefined)) + + get __backing_index(): (ILocalDecoratedVariable | undefined) + +} +`; + +function testParsedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedParsedScript)); +} + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test @Event decorator transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'parsed': [testParsedTransformer], + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/local/local-basic-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/local/local-basic-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..eeda2f5dca0c88481da1a85f83b3aacac5e6a1aa --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/local/local-basic-type.test.ts @@ -0,0 +1,170 @@ +/* + * 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 { structNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const STATE_DIR_PATH: string = 'decorators/local'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'local-basic-type.ets'), +]; + +const pluginTester = new PluginTester('test basic type @Local decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ILocalDecoratedVariable as ILocalDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Local as Local } from "@ohos.arkui.stateManagement"; + +function main() {} + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_localVar1 = STATE_MGMT_FACTORY.makeLocal(this, "localVar1", "stateVar1"); + this.__backing_localVar2 = STATE_MGMT_FACTORY.makeLocal(this, "localVar2", 50); + this.__backing_localVar3 = STATE_MGMT_FACTORY.makeLocal(this, "localVar3", true); + this.__backing_localVar4 = STATE_MGMT_FACTORY.makeLocal(this, "localVar4", undefined); + this.__backing_localVar5 = STATE_MGMT_FACTORY.makeLocal(this, "localVar5", null); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_localVar1?: ILocalDecoratedVariable; + + public get localVar1(): string { + return this.__backing_localVar1!.get(); + } + + public set localVar1(value: string) { + this.__backing_localVar1!.set(value); + } + + private __backing_localVar2?: ILocalDecoratedVariable; + + public get localVar2(): number { + return this.__backing_localVar2!.get(); + } + + public set localVar2(value: number) { + this.__backing_localVar2!.set(value); + } + + private __backing_localVar3?: ILocalDecoratedVariable; + + public get localVar3(): boolean { + return this.__backing_localVar3!.get(); + } + + public set localVar3(value: boolean) { + this.__backing_localVar3!.set(value); + } + + private __backing_localVar4?: ILocalDecoratedVariable; + + public get localVar4(): undefined { + return this.__backing_localVar4!.get(); + } + + public set localVar4(value: undefined) { + this.__backing_localVar4!.set(value); + } + + private __backing_localVar5?: ILocalDecoratedVariable; + + public get localVar5(): null { + return this.__backing_localVar5!.get(); + } + + public set localVar5(value: null) { + this.__backing_localVar5!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set localVar1(localVar1: (string | undefined)) + + get localVar1(): (string | undefined) + set __backing_localVar1(__backing_localVar1: (ILocalDecoratedVariable | undefined)) + + get __backing_localVar1(): (ILocalDecoratedVariable | undefined) + set localVar2(localVar2: (number | undefined)) + + get localVar2(): (number | undefined) + set __backing_localVar2(__backing_localVar2: (ILocalDecoratedVariable | undefined)) + + get __backing_localVar2(): (ILocalDecoratedVariable | undefined) + set localVar3(localVar3: (boolean | undefined)) + + get localVar3(): (boolean | undefined) + set __backing_localVar3(__backing_localVar3: (ILocalDecoratedVariable | undefined)) + + get __backing_localVar3(): (ILocalDecoratedVariable | undefined) + set localVar4(localVar4: (undefined | undefined)) + + get localVar4(): (undefined | undefined) + set __backing_localVar4(__backing_localVar4: (ILocalDecoratedVariable | undefined)) + + get __backing_localVar4(): (ILocalDecoratedVariable | undefined) + set localVar5(localVar5: (null | undefined)) + + get localVar5(): (null | undefined) + set __backing_localVar5(__backing_localVar5: (ILocalDecoratedVariable | undefined)) + + get __backing_localVar5(): (ILocalDecoratedVariable | undefined) + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test basic type @Local decorated variables transformation', + [parsedTransform, structNoRecheck, recheck], + { + 'checked:struct-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/local/local-complex-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/local/local-complex-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ccf7f48e757eddc2d8e65fdcf9614d9e9e4bd83 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/local/local-complex-type.test.ts @@ -0,0 +1,349 @@ +/* + * 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 { structNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const STATE_DIR_PATH: string = 'decorators/local'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'local-complex-type.ets'), +]; + +const pluginTester = new PluginTester('test complex type @Local decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ILocalDecoratedVariable as ILocalDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Local as Local } from "@ohos.arkui.stateManagement"; + +function main() {} + +class Per { + public num: number; + + public constructor(num: number) { + this.num = num; + } + +} + +final class StateType extends BaseEnum { + private readonly #ordinal: int; + + private static () {} + + public constructor(ordinal: int, value: int) { + super(value); + this.#ordinal = ordinal; + } + + public static readonly TYPE1: StateType = new StateType(0, 0); + + public static readonly TYPE2: StateType = new StateType(1, 1); + + public static readonly TYPE3: StateType = new StateType(2, 3); + + private static readonly #NamesArray: String[] = ["TYPE1", "TYPE2", "TYPE3"]; + + private static readonly #ValuesArray: int[] = [0, 1, 3]; + + private static readonly #StringValuesArray: String[] = ["0", "1", "3"]; + + private static readonly #ItemsArray: StateType[] = [StateType.TYPE1, StateType.TYPE2, StateType.TYPE3]; + + public getName(): String { + return StateType.#NamesArray[this.#ordinal]; + } + + public static getValueOf(name: String): StateType { + for (let i = 0;((i) < (StateType.#NamesArray.length));(++i)) { + if (((name) == (StateType.#NamesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum constant StateType.") + (name))); + } + + public static fromValue(value: int): StateType { + for (let i = 0;((i) < (StateType.#ValuesArray.length));(++i)) { + if (((value) == (StateType.#ValuesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum StateType with value ") + (value))); + } + + public valueOf(): int { + return StateType.#ValuesArray[this.#ordinal]; + } + + public toString(): String { + return StateType.#StringValuesArray[this.#ordinal]; + } + + public static values(): StateType[] { + return StateType.#ItemsArray; + } + + public getOrdinal(): int { + return this.#ordinal; + } + + public static $_get(e: StateType): String { + return e.getName(); + } + +} + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_localVar1 = STATE_MGMT_FACTORY.makeLocal(this, "localVar1", new Per(6)); + this.__backing_localVar2 = STATE_MGMT_FACTORY.makeLocal>(this, "localVar2", new Array(3, 6, 8)); + this.__backing_localVar3 = STATE_MGMT_FACTORY.makeLocal(this, "localVar3", StateType.TYPE3); + this.__backing_localVar4 = STATE_MGMT_FACTORY.makeLocal>(this, "localVar4", new Set(new Array("aa", "bb"))); + this.__backing_localVar5 = STATE_MGMT_FACTORY.makeLocal>(this, "localVar5", [true, false]); + this.__backing_localVar6 = STATE_MGMT_FACTORY.makeLocal>(this, "localVar6", new Array(new Per(7), new Per(11))); + this.__backing_localVar7 = STATE_MGMT_FACTORY.makeLocal>(this, "localVar7", [new Per(7), new Per(11)]); + this.__backing_localVar9 = STATE_MGMT_FACTORY.makeLocal(this, "localVar9", new Date("2025-4-23")); + this.__backing_localVar10 = STATE_MGMT_FACTORY.makeLocal>(this, "localVar10", new Map([[0, new Per(7)], [1, new Per(10)]])); + this.__backing_localVar11 = STATE_MGMT_FACTORY.makeLocal<(string | number)>(this, "localVar11", 0.0); + this.__backing_localVar12 = STATE_MGMT_FACTORY.makeLocal<(Set | Per)>(this, "localVar12", new Per(6)); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_localVar1?: ILocalDecoratedVariable; + + public get localVar1(): Per { + return this.__backing_localVar1!.get(); + } + + public set localVar1(value: Per) { + this.__backing_localVar1!.set(value); + } + + private __backing_localVar2?: ILocalDecoratedVariable>; + + public get localVar2(): Array { + return this.__backing_localVar2!.get(); + } + + public set localVar2(value: Array) { + this.__backing_localVar2!.set(value); + } + + private __backing_localVar3?: ILocalDecoratedVariable; + + public get localVar3(): StateType { + return this.__backing_localVar3!.get(); + } + + public set localVar3(value: StateType) { + this.__backing_localVar3!.set(value); + } + + private __backing_localVar4?: ILocalDecoratedVariable>; + + public get localVar4(): Set { + return this.__backing_localVar4!.get(); + } + + public set localVar4(value: Set) { + this.__backing_localVar4!.set(value); + } + + private __backing_localVar5?: ILocalDecoratedVariable>; + + public get localVar5(): Array { + return this.__backing_localVar5!.get(); + } + + public set localVar5(value: Array) { + this.__backing_localVar5!.set(value); + } + + private __backing_localVar6?: ILocalDecoratedVariable>; + + public get localVar6(): Array { + return this.__backing_localVar6!.get(); + } + + public set localVar6(value: Array) { + this.__backing_localVar6!.set(value); + } + + private __backing_localVar7?: ILocalDecoratedVariable>; + + public get localVar7(): Array { + return this.__backing_localVar7!.get(); + } + + public set localVar7(value: Array) { + this.__backing_localVar7!.set(value); + } + + private __backing_localVar9?: ILocalDecoratedVariable; + + public get localVar9(): Date { + return this.__backing_localVar9!.get(); + } + + public set localVar9(value: Date) { + this.__backing_localVar9!.set(value); + } + + private __backing_localVar10?: ILocalDecoratedVariable>; + + public get localVar10(): Map { + return this.__backing_localVar10!.get(); + } + + public set localVar10(value: Map) { + this.__backing_localVar10!.set(value); + } + + private __backing_localVar11?: ILocalDecoratedVariable<(string | number)>; + + public get localVar11(): (string | number) { + return this.__backing_localVar11!.get(); + } + + public set localVar11(value: (string | number)) { + this.__backing_localVar11!.set(value); + } + + private __backing_localVar12?: ILocalDecoratedVariable<(Set | Per)>; + + public get localVar12(): (Set | Per) { + return this.__backing_localVar12!.get(); + } + + public set localVar12(value: (Set | Per)) { + this.__backing_localVar12!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set localVar1(localVar1: (Per | undefined)) + + get localVar1(): (Per | undefined) + set __backing_localVar1(__backing_localVar1: (ILocalDecoratedVariable | undefined)) + + get __backing_localVar1(): (ILocalDecoratedVariable | undefined) + set localVar2(localVar2: (Array | undefined)) + + get localVar2(): (Array | undefined) + set __backing_localVar2(__backing_localVar2: (ILocalDecoratedVariable> | undefined)) + + get __backing_localVar2(): (ILocalDecoratedVariable> | undefined) + set localVar3(localVar3: (StateType | undefined)) + + get localVar3(): (StateType | undefined) + set __backing_localVar3(__backing_localVar3: (ILocalDecoratedVariable | undefined)) + + get __backing_localVar3(): (ILocalDecoratedVariable | undefined) + set localVar4(localVar4: (Set | undefined)) + + get localVar4(): (Set | undefined) + set __backing_localVar4(__backing_localVar4: (ILocalDecoratedVariable> | undefined)) + + get __backing_localVar4(): (ILocalDecoratedVariable> | undefined) + set localVar5(localVar5: (Array | undefined)) + + get localVar5(): (Array | undefined) + set __backing_localVar5(__backing_localVar5: (ILocalDecoratedVariable> | undefined)) + + get __backing_localVar5(): (ILocalDecoratedVariable> | undefined) + set localVar6(localVar6: (Array | undefined)) + + get localVar6(): (Array | undefined) + set __backing_localVar6(__backing_localVar6: (ILocalDecoratedVariable> | undefined)) + + get __backing_localVar6(): (ILocalDecoratedVariable> | undefined) + set localVar7(localVar7: (Array | undefined)) + + get localVar7(): (Array | undefined) + set __backing_localVar7(__backing_localVar7: (ILocalDecoratedVariable> | undefined)) + + get __backing_localVar7(): (ILocalDecoratedVariable> | undefined) + set localVar9(localVar9: (Date | undefined)) + + get localVar9(): (Date | undefined) + set __backing_localVar9(__backing_localVar9: (ILocalDecoratedVariable | undefined)) + + get __backing_localVar9(): (ILocalDecoratedVariable | undefined) + set localVar10(localVar10: (Map | undefined)) + + get localVar10(): (Map | undefined) + set __backing_localVar10(__backing_localVar10: (ILocalDecoratedVariable> | undefined)) + + get __backing_localVar10(): (ILocalDecoratedVariable> | undefined) + set localVar11(localVar11: ((string | number) | undefined)) + + get localVar11(): ((string | number) | undefined) + set __backing_localVar11(__backing_localVar11: (ILocalDecoratedVariable<(string | number)> | undefined)) + + get __backing_localVar11(): (ILocalDecoratedVariable<(string | number)> | undefined) + set localVar12(localVar12: ((Set | Per) | undefined)) + + get localVar12(): ((Set | Per) | undefined) + set __backing_localVar12(__backing_localVar12: (ILocalDecoratedVariable<(Set | Per)> | undefined)) + + get __backing_localVar12(): (ILocalDecoratedVariable<(Set | Per)> | undefined) + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test complex type @Local decorated variables transformation', + [parsedTransform, structNoRecheck, recheck], + { + 'checked:struct-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-in-observedv2-class.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-in-observedv2-class.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..35a2ba44fd4cd46436d9ba7ec487208727b80260 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-in-observedv2-class.test.ts @@ -0,0 +1,277 @@ +/* + * 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 STATE_DIR_PATH: string = 'decorators/monitor'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'monitor-in-observedv2-class.ets'), +]; + +const pluginTester = new PluginTester('test @Monitor decorator in @ObservedV2 class transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ButtonAttribute as ButtonAttribute } from "arkui.component.button"; + +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { IMonitorDecoratedVariable as IMonitorDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, Button as Button } from "@ohos.arkui.component"; + +import { Monitor as Monitor, IMonitor as IMonitor, ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@ObservedV2() class Info implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + @JSONRename({newName:"name"}) private __backing_name: string = "Tom"; + + @JSONStringifyIgnore() private __meta_name: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"region"}) private __backing_region: string = "North"; + + @JSONStringifyIgnore() private __meta_region: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"job"}) private __backing_job: string = "Teacher"; + + @JSONStringifyIgnore() private __meta_job: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public age: number = 25; + + private __monitor_onNameChange: (IMonitorDecoratedVariable | undefined); + + private __monitor_onAgeChange: (IMonitorDecoratedVariable | undefined); + + private __monitor_onChange: (IMonitorDecoratedVariable | undefined); + + @Monitor({value:["name"]}) public onNameChange(monitor: IMonitor) { + console.info(\`name change from \${({let gensym%%_43 = monitor.value(); + (((gensym%%_43) == (null)) ? undefined : gensym%%_43.before)})} to \${({let gensym%%_44 = monitor.value(); + (((gensym%%_44) == (null)) ? undefined : gensym%%_44.now)})}\`); + } + + @Monitor({value:["age"]}) public onAgeChange(monitor: IMonitor) { + console.info(\`age change from \${({let gensym%%_45 = monitor.value(); + (((gensym%%_45) == (null)) ? undefined : gensym%%_45.before)})} to \${({let gensym%%_46 = monitor.value(); + (((gensym%%_46) == (null)) ? undefined : gensym%%_46.now)})}\`); + } + + @Monitor({value:["region", "job"]}) public onChange(monitor: IMonitor) { + monitor.dirty.forEach(((path: string) => { + console.info(\`\${path} change from \${({let gensym%%_47 = monitor.value(path); + (((gensym%%_47) == (null)) ? undefined : gensym%%_47.before)})} to \${({let gensym%%_48 = monitor.value(path); + (((gensym%%_48) == (null)) ? undefined : gensym%%_48.now)})}\`); + })); + } + + public get name(): string { + this.conditionalAddRef(this.__meta_name); + return UIUtils.makeObserved(this.__backing_name); + } + + public set name(newValue: string) { + if (((this.__backing_name) !== (newValue))) { + this.__backing_name = newValue; + this.__meta_name.fireChange(); + this.executeOnSubscribingWatches("name"); + } + } + + public get region(): string { + this.conditionalAddRef(this.__meta_region); + return UIUtils.makeObserved(this.__backing_region); + } + + public set region(newValue: string) { + if (((this.__backing_region) !== (newValue))) { + this.__backing_region = newValue; + this.__meta_region.fireChange(); + this.executeOnSubscribingWatches("region"); + } + } + + public get job(): string { + this.conditionalAddRef(this.__meta_job); + return UIUtils.makeObserved(this.__backing_job); + } + + public set job(newValue: string) { + if (((this.__backing_job) !== (newValue))) { + this.__backing_job = newValue; + this.__meta_job.fireChange(); + this.executeOnSubscribingWatches("job"); + } + } + + public constructor() { + this.__monitor_onNameChange = STATE_MGMT_FACTORY.makeMonitor([{ + path: "name", + valueCallback: ((): NullishType => { + return this.name; + }), + }], ((_m: IMonitor) => { + this.onNameChange(_m); + })); + this.__monitor_onAgeChange = STATE_MGMT_FACTORY.makeMonitor([{ + path: "age", + valueCallback: ((): NullishType => { + return this.age; + }), + }], ((_m: IMonitor) => { + this.onAgeChange(_m); + })); + this.__monitor_onChange = STATE_MGMT_FACTORY.makeMonitor([{ + path: "region", + valueCallback: ((): NullishType => { + return this.region; + }), + }, { + path: "job", + valueCallback: ((): NullishType => { + return this.job; + }), + }], ((_m: IMonitor) => { + this.onChange(_m); + })); + } + +} + +@ComponentV2() final struct Index extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Index | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_info = ((({let gensym___130514200 = initializers; + (((gensym___130514200) == (null)) ? undefined : gensym___130514200.info)})) ?? (new Info())); + } + + public __updateStruct(initializers: (__Options_Index | undefined)): void {} + + private __backing_info?: Info; + + public get info(): Info { + return (this.__backing_info as Info); + } + + public set info(value: Info) { + this.__backing_info = value; + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.info.name = "Jack"; + })); + return; + }), "change name", undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.info.age = 26; + })); + return; + }), "change age", undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.info.region = "South"; + })); + return; + }), "change region", undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.info.job = "Driver"; + })); + return; + }), "change job", undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Index { + set info(info: (Info | undefined)) + + get info(): (Info | undefined) + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @Monitor decorator in @ObservedV2 class transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-in-struct.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-in-struct.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..df17928a94392870b457c5ba6e1855a1480b9305 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-in-struct.test.ts @@ -0,0 +1,176 @@ +/* + * 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 STATE_DIR_PATH: string = 'decorators/monitor'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'monitor-in-struct.ets'), +]; + +const pluginTester = new PluginTester('test @Monitor decorator in struct transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { IMonitorDecoratedVariable as IMonitorDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ILocalDecoratedVariable as ILocalDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ButtonAttribute as ButtonAttribute } from "arkui.component.button"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, Button as Button } from "@ohos.arkui.component"; + +import { Monitor as Monitor, Local as Local, IMonitor as IMonitor } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@ComponentV2() final struct Index extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Index | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_message = STATE_MGMT_FACTORY.makeLocal(this, "message", "Hello World"); + this.__backing_name = STATE_MGMT_FACTORY.makeLocal(this, "name", "Tom"); + this.__backing_age = STATE_MGMT_FACTORY.makeLocal(this, "age", 24); + this.__monitor_onStrChange = STATE_MGMT_FACTORY.makeMonitor([{ + path: "message", + valueCallback: ((): NullishType => { + return this.message; + }), + }, { + path: "name", + valueCallback: ((): NullishType => { + return this.name; + }), + }], ((_m: IMonitor) => { + this.onStrChange(_m); + })); + } + + public __updateStruct(initializers: (__Options_Index | undefined)): void {} + + private __backing_message?: ILocalDecoratedVariable; + + public get message(): string { + return this.__backing_message!.get(); + } + + public set message(value: string) { + this.__backing_message!.set(value); + } + + private __backing_name?: ILocalDecoratedVariable; + + public get name(): string { + return this.__backing_name!.get(); + } + + public set name(value: string) { + this.__backing_name!.set(value); + } + + private __backing_age?: ILocalDecoratedVariable; + + public get age(): number { + return this.__backing_age!.get(); + } + + public set age(value: number) { + this.__backing_age!.set(value); + } + + private __monitor_onStrChange: (IMonitorDecoratedVariable | undefined); + + @Monitor({value:["message", "name"]}) public onStrChange(monitor: IMonitor) { + monitor.dirty.forEach(((path: string) => { + console.info(\`\${path} changed from \${({let gensym%%_43 = monitor.value(path); + (((gensym%%_43) == (null)) ? undefined : gensym%%_43.before)})} to \${({let gensym%%_44 = monitor.value(path); + (((gensym%%_44) == (null)) ? undefined : gensym%%_44.now)})}\`); + })); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.message += "!"; + this.name = "Jack"; + })); + return; + }), "change string", undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Index { + set message(message: (string | undefined)) + + get message(): (string | undefined) + set __backing_message(__backing_message: (ILocalDecoratedVariable | undefined)) + + get __backing_message(): (ILocalDecoratedVariable | undefined) + set name(name: (string | undefined)) + + get name(): (string | undefined) + set __backing_name(__backing_name: (ILocalDecoratedVariable | undefined)) + + get __backing_name(): (ILocalDecoratedVariable | undefined) + set age(age: (number | undefined)) + + get age(): (number | undefined) + set __backing_age(__backing_age: (ILocalDecoratedVariable | undefined)) + + get __backing_age(): (ILocalDecoratedVariable | undefined) + +} + +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @Monitor decorator in struct transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-params.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-params.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a078ffe8d8558225a2707f1e3659681afdba815 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/monitor/monitor-params.test.ts @@ -0,0 +1,357 @@ +/* + * 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 STATE_DIR_PATH: string = 'decorators/monitor'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'monitor-params.ets'), +]; + +const pluginTester = new PluginTester('test @Monitor decorator parameters transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ILocalDecoratedVariable as ILocalDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { IMonitorDecoratedVariable as IMonitorDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, Button as Button } from "@ohos.arkui.component"; + +import { Monitor as Monitor, IMonitor as IMonitor, ObservedV2 as ObservedV2, Trace as Trace, Local as Local } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +class AAA { + public aaa: BBB = new BBB(); + + public constructor() {} + +} + +class BBB { + public bbb: CCC = new CCC(); + + public constructor() {} + +} + +class CCC { + public ccc: Array = new Array(new DDD(10), new DDD(12), new DDD(16)); + + public constructor() {} + +} + +class DDD { + public dd: Array; + + public constructor(dd: number) { + this.dd = []; + for (let i = 4;((i) < (dd));(i++)) { + this.dd.push(i); + } + } + +} + +class EEE { + public ee: FFF = new FFF(); + + public constructor() {} + +} + +class FFF { + public ff: GGG = new GGG(); + + public constructor() {} + +} + +class GGG { + public constructor() {} + +} + +@ObservedV2() class Info implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + @JSONRename({newName:"name"}) private __backing_name: string = "Tom"; + + @JSONStringifyIgnore() private __meta_name: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"strArr"}) private __backing_strArr: Array = ["North", "east"]; + + @JSONStringifyIgnore() private __meta_strArr: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"job"}) private __backing_job: AAA = new AAA(); + + @JSONStringifyIgnore() private __meta_job: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public age: number = 25; + + private __monitor_onNameChange: (IMonitorDecoratedVariable | undefined); + + private __monitor_onAgeChange: (IMonitorDecoratedVariable | undefined); + + private __monitor_onJobChange: (IMonitorDecoratedVariable | undefined); + + @Monitor({value:["name"]}) public onNameChange(monitor: IMonitor) {} + + @Monitor({value:["strArr.0", "age"]}) public onAgeChange(monitor: IMonitor) {} + + @Monitor({value:["job.aaa.bbb.ccc.1.dd.0"]}) public onJobChange(monitor: IMonitor) {} + + public get name(): string { + this.conditionalAddRef(this.__meta_name); + return UIUtils.makeObserved(this.__backing_name); + } + + public set name(newValue: string) { + if (((this.__backing_name) !== (newValue))) { + this.__backing_name = newValue; + this.__meta_name.fireChange(); + this.executeOnSubscribingWatches("name"); + } + } + + public get strArr(): Array { + this.conditionalAddRef(this.__meta_strArr); + return UIUtils.makeObserved(this.__backing_strArr); + } + + public set strArr(newValue: Array) { + if (((this.__backing_strArr) !== (newValue))) { + this.__backing_strArr = newValue; + this.__meta_strArr.fireChange(); + this.executeOnSubscribingWatches("strArr"); + } + } + + public get job(): AAA { + this.conditionalAddRef(this.__meta_job); + return UIUtils.makeObserved(this.__backing_job); + } + + public set job(newValue: AAA) { + if (((this.__backing_job) !== (newValue))) { + this.__backing_job = newValue; + this.__meta_job.fireChange(); + this.executeOnSubscribingWatches("job"); + } + } + + public constructor() { + this.__monitor_onNameChange = STATE_MGMT_FACTORY.makeMonitor([{ + path: "name", + valueCallback: ((): NullishType => { + return this.name; + }), + }], ((_m: IMonitor) => { + this.onNameChange(_m); + })); + this.__monitor_onAgeChange = STATE_MGMT_FACTORY.makeMonitor([{ + path: "strArr.0", + valueCallback: ((): NullishType => { + return ({let gensym___127309370 = this.strArr; + (((gensym___127309370) == (null)) ? undefined : gensym___127309370.$_get(0))}); + }), + }, { + path: "age", + valueCallback: ((): NullishType => { + return this.age; + }), + }], ((_m: IMonitor) => { + this.onAgeChange(_m); + })); + this.__monitor_onJobChange = STATE_MGMT_FACTORY.makeMonitor([{ + path: "job.aaa.bbb.ccc.1.dd.0", + valueCallback: ((): NullishType => { + return ({let gensym___1661894 = ({let gensym___87426618 = ({let gensym___223024592 = ({let gensym___215921992 = ({let gensym___22088104 = ({let gensym___245550938 = this.job; + (((gensym___245550938) == (null)) ? undefined : gensym___245550938.aaa)}); + (((gensym___22088104) == (null)) ? undefined : gensym___22088104.bbb)}); + (((gensym___215921992) == (null)) ? undefined : gensym___215921992.ccc)}); + (((gensym___223024592) == (null)) ? undefined : gensym___223024592.$_get(1))}); + (((gensym___87426618) == (null)) ? undefined : gensym___87426618.dd)}); + (((gensym___1661894) == (null)) ? undefined : gensym___1661894.$_get(0))}); + }), + }], ((_m: IMonitor) => { + this.onJobChange(_m); + })); + } + +} + +@ComponentV2() final struct Index extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Index | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_per = STATE_MGMT_FACTORY.makeLocal(this, "per", new EEE()); + this.__backing_v1 = STATE_MGMT_FACTORY.makeLocal(this, "v1", true); + this.__backing_numArr = STATE_MGMT_FACTORY.makeLocal>(this, "numArr", ["1", "3", "5"]); + this.__monitor_onPerChange = STATE_MGMT_FACTORY.makeMonitor([{ + path: "per.ee.ff", + valueCallback: ((): NullishType => { + return ({let gensym___164069504 = ({let gensym___239523226 = this.per; + (((gensym___239523226) == (null)) ? undefined : gensym___239523226.ee)}); + (((gensym___164069504) == (null)) ? undefined : gensym___164069504.ff)}); + }), + }, { + path: "v1", + valueCallback: ((): NullishType => { + return this.v1; + }), + }, { + path: "numArr.1", + valueCallback: ((): NullishType => { + return ({let gensym___124152275 = this.numArr; + (((gensym___124152275) == (null)) ? undefined : gensym___124152275.$_get(1))}); + }), + }], ((_m: IMonitor) => { + this.onPerChange(_m); + })); + } + + public __updateStruct(initializers: (__Options_Index | undefined)): void {} + + private __backing_per?: ILocalDecoratedVariable; + + public get per(): EEE { + return this.__backing_per!.get(); + } + + public set per(value: EEE) { + this.__backing_per!.set(value); + } + + private __backing_v1?: ILocalDecoratedVariable; + + public get v1(): boolean { + return this.__backing_v1!.get(); + } + + public set v1(value: boolean) { + this.__backing_v1!.set(value); + } + + private __backing_numArr?: ILocalDecoratedVariable>; + + public get numArr(): Array { + return this.__backing_numArr!.get(); + } + + public set numArr(value: Array) { + this.__backing_numArr!.set(value); + } + + private __monitor_onPerChange: (IMonitorDecoratedVariable | undefined); + + @Monitor({value:["per.ee.ff", "v1", "numArr.1"]}) public onPerChange(monitor: IMonitor) {} + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Index { + set per(per: (EEE | undefined)) + + get per(): (EEE | undefined) + set __backing_per(__backing_per: (ILocalDecoratedVariable | undefined)) + + get __backing_per(): (ILocalDecoratedVariable | undefined) + set v1(v1: (boolean | undefined)) + + get v1(): (boolean | undefined) + set __backing_v1(__backing_v1: (ILocalDecoratedVariable | undefined)) + + get __backing_v1(): (ILocalDecoratedVariable | undefined) + set numArr(numArr: (Array | undefined)) + + get numArr(): (Array | undefined) + set __backing_numArr(__backing_numArr: (ILocalDecoratedVariable> | undefined)) + + get __backing_numArr(): (ILocalDecoratedVariable> | undefined) + +} + +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @Monitor decorator parameters transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/observed-track/observed-track.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/observed-track/observed-track.test.ts index 9d112e2ef16c7127669d2fe164d4469731ab229e..087ae043d494f2d7f598d791e5ea2f263862ab99 100644 --- a/arkui-plugins/test/ut/ui-plugins/decorators/observed-track/observed-track.test.ts +++ b/arkui-plugins/test/ut/ui-plugins/decorators/observed-track/observed-track.test.ts @@ -63,8 +63,6 @@ import { Observed as Observed, Track as Track } from "@ohos.arkui.stateManagemen function main() {} - - @Observed() class B implements IObservedObject, ISubscribedWatches { @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); @@ -98,7 +96,13 @@ function main() {} @JSONStringifyIgnore() private __meta_trackB: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); - public constructor() {} + @JSONRename({newName:"newProp"}) private __backing_newProp?: boolean; + + @JSONStringifyIgnore() private __meta_newProp: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public constructor(newProp: boolean) { + this.newProp = newProp; + } public get trackB(): number { this.conditionalAddRef(this.__meta_trackB); @@ -113,6 +117,19 @@ function main() {} } } + public get newProp(): boolean { + this.conditionalAddRef(this.__meta_newProp); + return (this.__backing_newProp as boolean); + } + + public set newProp(newValue: boolean) { + if (((this.__backing_newProp) !== (newValue))) { + this.__backing_newProp = newValue; + this.__meta_newProp.fireChange(); + this.executeOnSubscribingWatches("newProp"); + } + } + } @Component() final struct MyStateSample extends CustomComponent { diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/base-observedv2-trace.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/base-observedv2-trace.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..e2ce9d195def6d50a7dfad20950be105dd834f9c --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/base-observedv2-trace.test.ts @@ -0,0 +1,182 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/observedv2-trace'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'base-observedv2-trace.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test basic @ObservedV2 and @Trace case', buildConfig); + +const expectedScript: string = ` +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + +@ObservedV2() class CC implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + public propB: number = 1; + + @JSONRename({newName:"traceB"}) private __backing_traceB: number = 2; + + @JSONStringifyIgnore() private __meta_traceB: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public get traceB(): number { + this.conditionalAddRef(this.__meta_traceB); + return UIUtils.makeObserved(this.__backing_traceB); + } + + public set traceB(newValue: number) { + if (((this.__backing_traceB) !== (newValue))) { + this.__backing_traceB = newValue; + this.__meta_traceB.fireChange(); + this.executeOnSubscribingWatches("traceB"); + } + } + + public constructor() {} + +} + +@ObservedV2() class DD implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + public propB: number = 1; + + @JSONRename({newName:"traceE"}) private __backing_traceE: number = 2; + + @JSONStringifyIgnore() private __meta_traceE: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"tracef"}) private __backing_tracef: number = 2; + + @JSONStringifyIgnore() private __meta_tracef: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public vv: string; + + public get traceE(): number { + this.conditionalAddRef(this.__meta_traceE); + return UIUtils.makeObserved(this.__backing_traceE); + } + + public set traceE(newValue: number) { + if (((this.__backing_traceE) !== (newValue))) { + this.__backing_traceE = newValue; + this.__meta_traceE.fireChange(); + this.executeOnSubscribingWatches("traceE"); + } + } + + public get tracef(): number { + this.conditionalAddRef(this.__meta_tracef); + return UIUtils.makeObserved(this.__backing_tracef); + } + + public set tracef(newValue: number) { + if (((this.__backing_tracef) !== (newValue))) { + this.__backing_tracef = newValue; + this.__meta_tracef.fireChange(); + this.executeOnSubscribingWatches("tracef"); + } + } + + public constructor(vv1: string) { + this.vv = vv1; + } + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test basic @ObservedV2 and @Trace case', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observed-serialization.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observed-serialization.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4091bd26deec6b5a3c0f824c6baf57c4f85234e --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observed-serialization.test.ts @@ -0,0 +1,277 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/observedv2-trace'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'observedv2-serialization.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test @ObservedV2 class serialization', buildConfig); + +const expectedScript: string = ` +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + +@Retention({policy:"SOURCE"}) @interface TestDecor {} + +@ObservedV2() class testJSONStringifyIgnore implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + public var1: number = 1; + + @JSONRename({newName:"var2"}) private __backing_var2: number = 2; + + @JSONStringifyIgnore() private __meta_var2: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONStringifyIgnore() public var3: number = 3; + + @TestDecor() public var4: number = 4; + + @JSONStringifyIgnore() private __backing_var5: number = 5; + + @JSONStringifyIgnore() private __meta_var5: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONStringifyIgnore() @TestDecor() public var6: number = 6; + + @TestDecor() @JSONRename({newName:"var7"}) private __backing_var7: number = 7; + + @JSONStringifyIgnore() private __meta_var7: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONStringifyIgnore() @TestDecor() private __backing_var8: number = 8; + + @JSONStringifyIgnore() private __meta_var8: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public get var2(): number { + this.conditionalAddRef(this.__meta_var2); + return UIUtils.makeObserved(this.__backing_var2); + } + + public set var2(newValue: number) { + if (((this.__backing_var2) !== (newValue))) { + this.__backing_var2 = newValue; + this.__meta_var2.fireChange(); + this.executeOnSubscribingWatches("var2"); + } + } + + public get var5(): number { + this.conditionalAddRef(this.__meta_var5); + return UIUtils.makeObserved(this.__backing_var5); + } + + public set var5(newValue: number) { + if (((this.__backing_var5) !== (newValue))) { + this.__backing_var5 = newValue; + this.__meta_var5.fireChange(); + this.executeOnSubscribingWatches("var5"); + } + } + + public get var7(): number { + this.conditionalAddRef(this.__meta_var7); + return UIUtils.makeObserved(this.__backing_var7); + } + + public set var7(newValue: number) { + if (((this.__backing_var7) !== (newValue))) { + this.__backing_var7 = newValue; + this.__meta_var7.fireChange(); + this.executeOnSubscribingWatches("var7"); + } + } + + public get var8(): number { + this.conditionalAddRef(this.__meta_var8); + return UIUtils.makeObserved(this.__backing_var8); + } + + public set var8(newValue: number) { + if (((this.__backing_var8) !== (newValue))) { + this.__backing_var8 = newValue; + this.__meta_var8.fireChange(); + this.executeOnSubscribingWatches("var8"); + } + } + + public constructor() {} + +} + +@ObservedV2() class testJsonRename implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + public var1: number = 1; + + @JSONRename({newName:"var2"}) private __backing_var2: number = 2; + + @JSONStringifyIgnore() private __meta_var2: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({value:"name3"}) public var3: number = 3; + + @TestDecor() public var4: number = 4; + + @JSONRename({value:"name5"}) private __backing_var5: number = 5; + + @JSONStringifyIgnore() private __meta_var5: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({value:"name6"}) @TestDecor() public var6: number = 6; + + @TestDecor() @JSONRename({newName:"var7"}) private __backing_var7: number = 7; + + @JSONStringifyIgnore() private __meta_var7: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({value:"name8"}) @TestDecor() private __backing_var8: number = 8; + + @JSONStringifyIgnore() private __meta_var8: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public get var2(): number { + this.conditionalAddRef(this.__meta_var2); + return UIUtils.makeObserved(this.__backing_var2); + } + + public set var2(newValue: number) { + if (((this.__backing_var2) !== (newValue))) { + this.__backing_var2 = newValue; + this.__meta_var2.fireChange(); + this.executeOnSubscribingWatches("var2"); + } + } + + public get var5(): number { + this.conditionalAddRef(this.__meta_var5); + return UIUtils.makeObserved(this.__backing_var5); + } + + public set var5(newValue: number) { + if (((this.__backing_var5) !== (newValue))) { + this.__backing_var5 = newValue; + this.__meta_var5.fireChange(); + this.executeOnSubscribingWatches("var5"); + } + } + + public get var7(): number { + this.conditionalAddRef(this.__meta_var7); + return UIUtils.makeObserved(this.__backing_var7); + } + + public set var7(newValue: number) { + if (((this.__backing_var7) !== (newValue))) { + this.__backing_var7 = newValue; + this.__meta_var7.fireChange(); + this.executeOnSubscribingWatches("var7"); + } + } + + public get var8(): number { + this.conditionalAddRef(this.__meta_var8); + return UIUtils.makeObserved(this.__backing_var8); + } + + public set var8(newValue: number) { + if (((this.__backing_var8) !== (newValue))) { + this.__backing_var8 = newValue; + this.__meta_var8.fireChange(); + this.executeOnSubscribingWatches("var8"); + } + } + + public constructor() {} + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @ObservedV2 class serialization', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-extends.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-extends.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..f77d52bc09853a5ad8b4304d5a0b9c6975e98a35 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-extends.test.ts @@ -0,0 +1,166 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/observedv2-trace'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'observedv2-trace-extends.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test @ObservedV2 extends class', buildConfig); + +const expectedScript: string = ` +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + +@ObservedV2() class A implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + public propA: number = 1; + + @JSONRename({newName:"traceA"}) private __backing_traceA: number = 2; + + @JSONStringifyIgnore() private __meta_traceA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public get traceA(): number { + this.conditionalAddRef(this.__meta_traceA); + return UIUtils.makeObserved(this.__backing_traceA); + } + + public set traceA(newValue: number) { + if (((this.__backing_traceA) !== (newValue))) { + this.__backing_traceA = newValue; + this.__meta_traceA.fireChange(); + this.executeOnSubscribingWatches("traceA"); + } + } + + public constructor() {} + +} + +class G extends A { + public propG: number = 1; + + public constructor() {} + +} + +@ObservedV2() class H extends G implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + @JSONRename({newName:"propG"}) private __backing_propG: number = 1; + + @JSONStringifyIgnore() private __meta_propG: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public get propG(): number { + this.conditionalAddRef(this.__meta_propG); + return UIUtils.makeObserved(this.__backing_propG); + } + + public set propG(newValue: number) { + if (((this.__backing_propG) !== (newValue))) { + this.__backing_propG = newValue; + this.__meta_propG.fireChange(); + this.executeOnSubscribingWatches("propG"); + } + } + + public constructor() {} + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @ObservedV2 extends class', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-implements.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-implements.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1e2137ce9dac034dcbd9cead570c543c126880c --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-implements.test.ts @@ -0,0 +1,159 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/observedv2-trace'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'observedv2-trace-implements.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test @ObservedV2 implements interface', buildConfig); + +const expectedScript: string = ` +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + +interface PropInterface { + set propF(propF: number) + + get propF(): number + +} + +interface trackInterface { + set trackF(trackF: number) + + get trackF(): number + +} + +@ObservedV2() class F implements PropInterface, trackInterface, IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + @JSONRename({newName:"bb"}) private __backing_bb?: boolean; + + @JSONStringifyIgnore() private __meta_bb: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"propF"}) private __backing_propF: number = 1; + + @JSONStringifyIgnore() private __meta_propF: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + private _$property$_trackF: number = 2; + + set propF(newValue: number) { + if (((this.__backing_propF) !== (newValue))) { + this.__backing_propF = newValue; + this.__meta_propF.fireChange(); + this.executeOnSubscribingWatches("propF"); + } + } + + public get propF(): number { + this.conditionalAddRef(this.__meta_propF); + return UIUtils.makeObserved(this.__backing_propF); + } + + set trackF(_$property$_trackF: number) { + this._$property$_trackF = _$property$_trackF; + return; + } + + public get trackF(): number { + return this._$property$_trackF; + } + + public get bb(): boolean { + this.conditionalAddRef(this.__meta_bb); + return UIUtils.makeObserved((this.__backing_bb as boolean)); + } + + public set bb(newValue: boolean) { + if (((this.__backing_bb) !== (newValue))) { + this.__backing_bb = newValue; + this.__meta_bb.fireChange(); + this.executeOnSubscribingWatches("bb"); + } + } + + public constructor(bb: boolean) { + this.bb = bb; + } + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @ObservedV2 implements interface', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-types.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-types.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..11b567e4f18cadcd511e2e5bdf067913c0cf2466 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/observedv2-trace-types.test.ts @@ -0,0 +1,380 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/observedv2-trace'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'observedv2-trace-types.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test @ObservedV2 class with different types class property', buildConfig); + +const expectedScript: string = ` +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + +class Person { + public constructor() {} + +} + +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(); + } + +} + +@ObservedV2() class mixed1 implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + @JSONRename({newName:"numA"}) private __backing_numA: number = 33; + + @JSONStringifyIgnore() private __meta_numA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"stringA"}) private __backing_stringA: string = "AA"; + + @JSONStringifyIgnore() private __meta_stringA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"booleanA"}) private __backing_booleanA: boolean = true; + + @JSONStringifyIgnore() private __meta_booleanA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"arrayA"}) private __backing_arrayA: Array = [1, 2, 3]; + + @JSONStringifyIgnore() private __meta_arrayA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"objectA"}) private __backing_objectA: Object = {}; + + @JSONStringifyIgnore() private __meta_objectA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"dateA"}) private __backing_dateA: Date = new Date("2021-08-08"); + + @JSONStringifyIgnore() private __meta_dateA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"setA"}) private __backing_setA: Set = new Set(); + + @JSONStringifyIgnore() private __meta_setA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"mapA"}) private __backing_mapA: Map = new Map(); + + @JSONStringifyIgnore() private __meta_mapA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"unionA"}) private __backing_unionA: (string | undefined) = ""; + + @JSONStringifyIgnore() private __meta_unionA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"classA"}) private __backing_classA: Person = new Person(); + + @JSONStringifyIgnore() private __meta_classA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"enumA"}) private __backing_enumA: Status = Status.NotFound; + + @JSONStringifyIgnore() private __meta_enumA: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public numB: number = 33; + + public stringB: string = "AA"; + + public booleanB: boolean = true; + + public arrayB: Array = [1, 2, 3]; + + public objectB: Object = {}; + + public dateB: Date = new Date("2021-08-08"); + + public setB: Set = new Set(); + + public mapB: Map = new Map(); + + public unionB: (string | undefined) = ""; + + public classB: Person = new Person(); + + public enumB: Status = Status.NotFound; + + public get numA(): number { + this.conditionalAddRef(this.__meta_numA); + return UIUtils.makeObserved(this.__backing_numA); + } + + public set numA(newValue: number) { + if (((this.__backing_numA) !== (newValue))) { + this.__backing_numA = newValue; + this.__meta_numA.fireChange(); + this.executeOnSubscribingWatches("numA"); + } + } + + public get stringA(): string { + this.conditionalAddRef(this.__meta_stringA); + return UIUtils.makeObserved(this.__backing_stringA); + } + + public set stringA(newValue: string) { + if (((this.__backing_stringA) !== (newValue))) { + this.__backing_stringA = newValue; + this.__meta_stringA.fireChange(); + this.executeOnSubscribingWatches("stringA"); + } + } + + public get booleanA(): boolean { + this.conditionalAddRef(this.__meta_booleanA); + return UIUtils.makeObserved(this.__backing_booleanA); + } + + public set booleanA(newValue: boolean) { + if (((this.__backing_booleanA) !== (newValue))) { + this.__backing_booleanA = newValue; + this.__meta_booleanA.fireChange(); + this.executeOnSubscribingWatches("booleanA"); + } + } + + public get arrayA(): Array { + this.conditionalAddRef(this.__meta_arrayA); + return UIUtils.makeObserved(this.__backing_arrayA); + } + + public set arrayA(newValue: Array) { + if (((this.__backing_arrayA) !== (newValue))) { + this.__backing_arrayA = newValue; + this.__meta_arrayA.fireChange(); + this.executeOnSubscribingWatches("arrayA"); + } + } + + public get objectA(): Object { + this.conditionalAddRef(this.__meta_objectA); + return UIUtils.makeObserved(this.__backing_objectA); + } + + public set objectA(newValue: Object) { + if (((this.__backing_objectA) !== (newValue))) { + this.__backing_objectA = newValue; + this.__meta_objectA.fireChange(); + this.executeOnSubscribingWatches("objectA"); + } + } + + public get dateA(): Date { + this.conditionalAddRef(this.__meta_dateA); + return UIUtils.makeObserved(this.__backing_dateA); + } + + public set dateA(newValue: Date) { + if (((this.__backing_dateA) !== (newValue))) { + this.__backing_dateA = newValue; + this.__meta_dateA.fireChange(); + this.executeOnSubscribingWatches("dateA"); + } + } + + public get setA(): Set { + this.conditionalAddRef(this.__meta_setA); + return UIUtils.makeObserved(this.__backing_setA); + } + + public set setA(newValue: Set) { + if (((this.__backing_setA) !== (newValue))) { + this.__backing_setA = newValue; + this.__meta_setA.fireChange(); + this.executeOnSubscribingWatches("setA"); + } + } + + public get mapA(): Map { + this.conditionalAddRef(this.__meta_mapA); + return UIUtils.makeObserved(this.__backing_mapA); + } + + public set mapA(newValue: Map) { + if (((this.__backing_mapA) !== (newValue))) { + this.__backing_mapA = newValue; + this.__meta_mapA.fireChange(); + this.executeOnSubscribingWatches("mapA"); + } + } + + public get unionA(): (string | undefined) { + this.conditionalAddRef(this.__meta_unionA); + return UIUtils.makeObserved(this.__backing_unionA); + } + + public set unionA(newValue: (string | undefined)) { + if (((this.__backing_unionA) !== (newValue))) { + this.__backing_unionA = newValue; + this.__meta_unionA.fireChange(); + this.executeOnSubscribingWatches("unionA"); + } + } + + public get classA(): Person { + this.conditionalAddRef(this.__meta_classA); + return UIUtils.makeObserved(this.__backing_classA); + } + + public set classA(newValue: Person) { + if (((this.__backing_classA) !== (newValue))) { + this.__backing_classA = newValue; + this.__meta_classA.fireChange(); + this.executeOnSubscribingWatches("classA"); + } + } + + public get enumA(): Status { + this.conditionalAddRef(this.__meta_enumA); + return UIUtils.makeObserved(this.__backing_enumA); + } + + public set enumA(newValue: Status) { + if (((this.__backing_enumA) !== (newValue))) { + this.__backing_enumA = newValue; + this.__meta_enumA.fireChange(); + this.executeOnSubscribingWatches("enumA"); + } + } + + public constructor() {} + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test @ObservedV2 class with different types class property', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/static-observedv2-trace.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/static-observedv2-trace.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5205a28ae0b88d4777d4e8bd55fe941f097a452e --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/static-observedv2-trace.test.ts @@ -0,0 +1,123 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/observedv2-trace'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'static-observedv2-trace.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test static @Trace variable case', buildConfig); + +const expectedScript: string = ` +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@ObservedV2() class CC implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + public propB: number = 1; + + public static propA: number = 1; + + @JSONRename({newName:"traceB"}) public static __backing_traceB: number = 2; + + @JSONStringifyIgnore() public static __meta_traceB: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + static { + + } + public static get traceB(): number { + CC.__meta_traceB.addRef(); + return UIUtils.makeObserved(CC.__backing_traceB); + } + + public static set traceB(newValue: number) { + if (((CC.__backing_traceB) !== (newValue))) { + CC.__backing_traceB = newValue; + CC.__meta_traceB.fireChange(); + } + } + + public constructor() {} + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test static @Trace variable case', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/trace-with-constructor.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/trace-with-constructor.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..facbf02cf8a72522e18982f520aaf9b5d5c7d1ee --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/observedv2-trace/trace-with-constructor.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 OBSERVED_DIR_PATH: string = 'decorators/observedv2-trace'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'trace-with-constructor.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test basic @Trace decorated variable initialized with constuctor', buildConfig); + +const expectedScript: string = ` +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +function main() {} + +@ObservedV2() class FF implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + public propB: number = 1; + + @JSONRename({newName:"traceE"}) private __backing_traceE: number = 2; + + @JSONStringifyIgnore() private __meta_traceE: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"vv"}) private __backing_vv?: string; + + @JSONStringifyIgnore() private __meta_vv: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public get traceE(): number { + this.conditionalAddRef(this.__meta_traceE); + return UIUtils.makeObserved(this.__backing_traceE); + } + + public set traceE(newValue: number) { + if (((this.__backing_traceE) !== (newValue))) { + this.__backing_traceE = newValue; + this.__meta_traceE.fireChange(); + this.executeOnSubscribingWatches("traceE"); + } + } + + public get vv(): string { + this.conditionalAddRef(this.__meta_vv); + return UIUtils.makeObserved((this.__backing_vv as string)); + } + + public set vv(newValue: string) { + if (((this.__backing_vv) !== (newValue))) { + this.__backing_vv = newValue; + this.__meta_vv.fireChange(); + this.executeOnSubscribingWatches("vv"); + } + } + + public constructor(vv1: string) { + this.vv = vv1; + } + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test basic @Trace decorated variable initialized with constuctor', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/once/once-basic-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/once/once-basic-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..1b5cc3d482ffeb64469978e0bafb4d98a38de3e3 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/once/once-basic-type.test.ts @@ -0,0 +1,177 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/once'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'once-basic-type.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test basic type @Once decorated variables transformation', 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 { IParamOnceDecoratedVariable as IParamOnceDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Param as Param, Once as Once } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_onceVar1 = STATE_MGMT_FACTORY.makeParamOnce(this, "onceVar1", ((({let gensym___35048285 = initializers; + (((gensym___35048285) == (null)) ? undefined : gensym___35048285.onceVar1)})) ?? ("stateVar1"))); + this.__backing_onceVar2 = STATE_MGMT_FACTORY.makeParamOnce(this, "onceVar2", ((({let gensym___241100287 = initializers; + (((gensym___241100287) == (null)) ? undefined : gensym___241100287.onceVar2)})) ?? (50))); + this.__backing_onceVar3 = STATE_MGMT_FACTORY.makeParamOnce(this, "onceVar3", ((({let gensym___114153851 = initializers; + (((gensym___114153851) == (null)) ? undefined : gensym___114153851.onceVar3)})) ?? (true))); + this.__backing_onceVar4 = STATE_MGMT_FACTORY.makeParamOnce(this, "onceVar4", ((({let gensym___18636164 = initializers; + (((gensym___18636164) == (null)) ? undefined : gensym___18636164.onceVar4)})) ?? (undefined))); + this.__backing_onceVar5 = STATE_MGMT_FACTORY.makeParamOnce(this, "onceVar5", ((({let gensym___60128231 = initializers; + (((gensym___60128231) == (null)) ? undefined : gensym___60128231.onceVar5)})) ?? (null))); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_onceVar1?: IParamOnceDecoratedVariable; + + public get onceVar1(): string { + return this.__backing_onceVar1!.get(); + } + + public set onceVar1(value: string) { + this.__backing_onceVar1!.set(value); + } + + private __backing_onceVar2?: IParamOnceDecoratedVariable; + + public get onceVar2(): number { + return this.__backing_onceVar2!.get(); + } + + public set onceVar2(value: number) { + this.__backing_onceVar2!.set(value); + } + + private __backing_onceVar3?: IParamOnceDecoratedVariable; + + public get onceVar3(): boolean { + return this.__backing_onceVar3!.get(); + } + + public set onceVar3(value: boolean) { + this.__backing_onceVar3!.set(value); + } + + private __backing_onceVar4?: IParamOnceDecoratedVariable; + + public get onceVar4(): undefined { + return this.__backing_onceVar4!.get(); + } + + public set onceVar4(value: undefined) { + this.__backing_onceVar4!.set(value); + } + + private __backing_onceVar5?: IParamOnceDecoratedVariable; + + public get onceVar5(): null { + return this.__backing_onceVar5!.get(); + } + + public set onceVar5(value: null) { + this.__backing_onceVar5!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set onceVar1(onceVar1: (string | undefined)) + + get onceVar1(): (string | undefined) + @Param() set __backing_onceVar1(__backing_onceVar1: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceVar1(): (IParamOnceDecoratedVariable | undefined) + set onceVar2(onceVar2: (number | undefined)) + + get onceVar2(): (number | undefined) + @Param() set __backing_onceVar2(__backing_onceVar2: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceVar2(): (IParamOnceDecoratedVariable | undefined) + set onceVar3(onceVar3: (boolean | undefined)) + + get onceVar3(): (boolean | undefined) + @Param() set __backing_onceVar3(__backing_onceVar3: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceVar3(): (IParamOnceDecoratedVariable | undefined) + set onceVar4(onceVar4: (undefined | undefined)) + + get onceVar4(): (undefined | undefined) + @Param() set __backing_onceVar4(__backing_onceVar4: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceVar4(): (IParamOnceDecoratedVariable | undefined) + set onceVar5(onceVar5: (null | undefined)) + + get onceVar5(): (null | undefined) + @Param() set __backing_onceVar5(__backing_onceVar5: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceVar5(): (IParamOnceDecoratedVariable | undefined) + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test basic type @Once decorated variables transformation', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/once/once-complex-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/once/once-complex-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b9a8561a908c5c8821d3185f5d0b210fd9f67a2 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/once/once-complex-type.test.ts @@ -0,0 +1,378 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/once'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'once-complex-type.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test complex type @Once decorated variables transformation', 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 { IParamOnceDecoratedVariable as IParamOnceDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Param as Param, Once as Once } from "@ohos.arkui.stateManagement"; + +function main() {} + +class Per { + public num: number; + + public constructor(num: number) { + this.num = num; + } + +} + +final class StateType extends BaseEnum { + private readonly #ordinal: int; + + private static () {} + + public constructor(ordinal: int, value: int) { + super(value); + this.#ordinal = ordinal; + } + + public static readonly TYPE1: StateType = new StateType(0, 0); + + public static readonly TYPE2: StateType = new StateType(1, 1); + + public static readonly TYPE3: StateType = new StateType(2, 3); + + private static readonly #NamesArray: String[] = ["TYPE1", "TYPE2", "TYPE3"]; + + private static readonly #ValuesArray: int[] = [0, 1, 3]; + + private static readonly #StringValuesArray: String[] = ["0", "1", "3"]; + + private static readonly #ItemsArray: StateType[] = [StateType.TYPE1, StateType.TYPE2, StateType.TYPE3]; + + public getName(): String { + return StateType.#NamesArray[this.#ordinal]; + } + + public static getValueOf(name: String): StateType { + for (let i = 0;((i) < (StateType.#NamesArray.length));(++i)) { + if (((name) == (StateType.#NamesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum constant StateType.") + (name))); + } + + public static fromValue(value: int): StateType { + for (let i = 0;((i) < (StateType.#ValuesArray.length));(++i)) { + if (((value) == (StateType.#ValuesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum StateType with value ") + (value))); + } + + public valueOf(): int { + return StateType.#ValuesArray[this.#ordinal]; + } + + public toString(): String { + return StateType.#StringValuesArray[this.#ordinal]; + } + + public static values(): StateType[] { + return StateType.#ItemsArray; + } + + public getOrdinal(): int { + return this.#ordinal; + } + + public static $_get(e: StateType): String { + return e.getName(); + } + +} + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_onceVar1 = STATE_MGMT_FACTORY.makeParamOnce(this, "onceVar1", ((({let gensym___35048285 = initializers; + (((gensym___35048285) == (null)) ? undefined : gensym___35048285.onceVar1)})) ?? (new Per(6)))); + this.__backing_onceVar2 = STATE_MGMT_FACTORY.makeParamOnce>(this, "onceVar2", ((({let gensym___241100287 = initializers; + (((gensym___241100287) == (null)) ? undefined : gensym___241100287.onceVar2)})) ?? (new Array(3, 6, 8)))); + this.__backing_onceVar3 = STATE_MGMT_FACTORY.makeParamOnce(this, "onceVar3", ((({let gensym___114153851 = initializers; + (((gensym___114153851) == (null)) ? undefined : gensym___114153851.onceVar3)})) ?? (StateType.TYPE3))); + this.__backing_onceVar4 = STATE_MGMT_FACTORY.makeParamOnce>(this, "onceVar4", ((({let gensym___18636164 = initializers; + (((gensym___18636164) == (null)) ? undefined : gensym___18636164.onceVar4)})) ?? (new Set(new Array("aa", "bb"))))); + this.__backing_onceVar5 = STATE_MGMT_FACTORY.makeParamOnce>(this, "onceVar5", ((({let gensym___60128231 = initializers; + (((gensym___60128231) == (null)) ? undefined : gensym___60128231.onceVar5)})) ?? ([true, false]))); + this.__backing_onceVar6 = STATE_MGMT_FACTORY.makeParamOnce>(this, "onceVar6", ((({let gensym___266500361 = initializers; + (((gensym___266500361) == (null)) ? undefined : gensym___266500361.onceVar6)})) ?? (new Array(new Per(7), new Per(11))))); + this.__backing_onceVar7 = STATE_MGMT_FACTORY.makeParamOnce>(this, "onceVar7", ((({let gensym___265659510 = initializers; + (((gensym___265659510) == (null)) ? undefined : gensym___265659510.onceVar7)})) ?? ([new Per(7), new Per(11)]))); + this.__backing_onceVar8 = STATE_MGMT_FACTORY.makeParamOnce<((sr: string)=> void)>(this, "onceVar8", ((({let gensym___258402603 = initializers; + (((gensym___258402603) == (null)) ? undefined : gensym___258402603.onceVar8)})) ?? (((sr: string) => {})))); + this.__backing_onceVar9 = STATE_MGMT_FACTORY.makeParamOnce(this, "onceVar9", ((({let gensym___90206878 = initializers; + (((gensym___90206878) == (null)) ? undefined : gensym___90206878.onceVar9)})) ?? (new Date("2025-4-23")))); + this.__backing_onceVar10 = STATE_MGMT_FACTORY.makeParamOnce>(this, "onceVar10", ((({let gensym___76706777 = initializers; + (((gensym___76706777) == (null)) ? undefined : gensym___76706777.onceVar10)})) ?? (new Map([[0, new Per(7)], [1, new Per(10)]])))); + this.__backing_onceVar11 = STATE_MGMT_FACTORY.makeParamOnce<(string | number)>(this, "onceVar11", ((({let gensym___188373395 = initializers; + (((gensym___188373395) == (null)) ? undefined : gensym___188373395.onceVar11)})) ?? (0.0))); + this.__backing_onceVar12 = STATE_MGMT_FACTORY.makeParamOnce<(Set | Per)>(this, "onceVar12", ((({let gensym___207702762 = initializers; + (((gensym___207702762) == (null)) ? undefined : gensym___207702762.onceVar12)})) ?? (new Per(6)))); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_onceVar1?: IParamOnceDecoratedVariable; + + public get onceVar1(): Per { + return this.__backing_onceVar1!.get(); + } + + public set onceVar1(value: Per) { + this.__backing_onceVar1!.set(value); + } + + private __backing_onceVar2?: IParamOnceDecoratedVariable>; + + public get onceVar2(): Array { + return this.__backing_onceVar2!.get(); + } + + public set onceVar2(value: Array) { + this.__backing_onceVar2!.set(value); + } + + private __backing_onceVar3?: IParamOnceDecoratedVariable; + + public get onceVar3(): StateType { + return this.__backing_onceVar3!.get(); + } + + public set onceVar3(value: StateType) { + this.__backing_onceVar3!.set(value); + } + + private __backing_onceVar4?: IParamOnceDecoratedVariable>; + + public get onceVar4(): Set { + return this.__backing_onceVar4!.get(); + } + + public set onceVar4(value: Set) { + this.__backing_onceVar4!.set(value); + } + + private __backing_onceVar5?: IParamOnceDecoratedVariable>; + + public get onceVar5(): Array { + return this.__backing_onceVar5!.get(); + } + + public set onceVar5(value: Array) { + this.__backing_onceVar5!.set(value); + } + + private __backing_onceVar6?: IParamOnceDecoratedVariable>; + + public get onceVar6(): Array { + return this.__backing_onceVar6!.get(); + } + + public set onceVar6(value: Array) { + this.__backing_onceVar6!.set(value); + } + + private __backing_onceVar7?: IParamOnceDecoratedVariable>; + + public get onceVar7(): Array { + return this.__backing_onceVar7!.get(); + } + + public set onceVar7(value: Array) { + this.__backing_onceVar7!.set(value); + } + + private __backing_onceVar8?: IParamOnceDecoratedVariable<((sr: string)=> void)>; + + public get onceVar8(): ((sr: string)=> void) { + return this.__backing_onceVar8!.get(); + } + + public set onceVar8(value: ((sr: string)=> void)) { + this.__backing_onceVar8!.set(value); + } + + private __backing_onceVar9?: IParamOnceDecoratedVariable; + + public get onceVar9(): Date { + return this.__backing_onceVar9!.get(); + } + + public set onceVar9(value: Date) { + this.__backing_onceVar9!.set(value); + } + + private __backing_onceVar10?: IParamOnceDecoratedVariable>; + + public get onceVar10(): Map { + return this.__backing_onceVar10!.get(); + } + + public set onceVar10(value: Map) { + this.__backing_onceVar10!.set(value); + } + + private __backing_onceVar11?: IParamOnceDecoratedVariable<(string | number)>; + + public get onceVar11(): (string | number) { + return this.__backing_onceVar11!.get(); + } + + public set onceVar11(value: (string | number)) { + this.__backing_onceVar11!.set(value); + } + + private __backing_onceVar12?: IParamOnceDecoratedVariable<(Set | Per)>; + + public get onceVar12(): (Set | Per) { + return this.__backing_onceVar12!.get(); + } + + public set onceVar12(value: (Set | Per)) { + this.__backing_onceVar12!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set onceVar1(onceVar1: (Per | undefined)) + + get onceVar1(): (Per | undefined) + @Param() set __backing_onceVar1(__backing_onceVar1: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceVar1(): (IParamOnceDecoratedVariable | undefined) + set onceVar2(onceVar2: (Array | undefined)) + + get onceVar2(): (Array | undefined) + @Param() set __backing_onceVar2(__backing_onceVar2: (IParamOnceDecoratedVariable> | undefined)) + + @Param() get __backing_onceVar2(): (IParamOnceDecoratedVariable> | undefined) + set onceVar3(onceVar3: (StateType | undefined)) + + get onceVar3(): (StateType | undefined) + @Param() set __backing_onceVar3(__backing_onceVar3: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceVar3(): (IParamOnceDecoratedVariable | undefined) + set onceVar4(onceVar4: (Set | undefined)) + + get onceVar4(): (Set | undefined) + @Param() set __backing_onceVar4(__backing_onceVar4: (IParamOnceDecoratedVariable> | undefined)) + + @Param() get __backing_onceVar4(): (IParamOnceDecoratedVariable> | undefined) + set onceVar5(onceVar5: (Array | undefined)) + + get onceVar5(): (Array | undefined) + @Param() set __backing_onceVar5(__backing_onceVar5: (IParamOnceDecoratedVariable> | undefined)) + + @Param() get __backing_onceVar5(): (IParamOnceDecoratedVariable> | undefined) + set onceVar6(onceVar6: (Array | undefined)) + + get onceVar6(): (Array | undefined) + @Param() set __backing_onceVar6(__backing_onceVar6: (IParamOnceDecoratedVariable> | undefined)) + + @Param() get __backing_onceVar6(): (IParamOnceDecoratedVariable> | undefined) + set onceVar7(onceVar7: (Array | undefined)) + + get onceVar7(): (Array | undefined) + @Param() set __backing_onceVar7(__backing_onceVar7: (IParamOnceDecoratedVariable> | undefined)) + + @Param() get __backing_onceVar7(): (IParamOnceDecoratedVariable> | undefined) + set onceVar8(onceVar8: (((sr: string)=> void) | undefined)) + + get onceVar8(): (((sr: string)=> void) | undefined) + @Param() set __backing_onceVar8(__backing_onceVar8: (IParamOnceDecoratedVariable<((sr: string)=> void)> | undefined)) + + @Param() get __backing_onceVar8(): (IParamOnceDecoratedVariable<((sr: string)=> void)> | undefined) + set onceVar9(onceVar9: (Date | undefined)) + + get onceVar9(): (Date | undefined) + @Param() set __backing_onceVar9(__backing_onceVar9: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceVar9(): (IParamOnceDecoratedVariable | undefined) + set onceVar10(onceVar10: (Map | undefined)) + + get onceVar10(): (Map | undefined) + @Param() set __backing_onceVar10(__backing_onceVar10: (IParamOnceDecoratedVariable> | undefined)) + + @Param() get __backing_onceVar10(): (IParamOnceDecoratedVariable> | undefined) + set onceVar11(onceVar11: ((string | number) | undefined)) + + get onceVar11(): ((string | number) | undefined) + @Param() set __backing_onceVar11(__backing_onceVar11: (IParamOnceDecoratedVariable<(string | number)> | undefined)) + + @Param() get __backing_onceVar11(): (IParamOnceDecoratedVariable<(string | number)> | undefined) + set onceVar12(onceVar12: ((Set | Per) | undefined)) + + get onceVar12(): ((Set | Per) | undefined) + @Param() set __backing_onceVar12(__backing_onceVar12: (IParamOnceDecoratedVariable<(Set | Per)> | undefined)) + + @Param() get __backing_onceVar12(): (IParamOnceDecoratedVariable<(Set | Per)> | undefined) + +} +`; + +function testParsedAndCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedScript)); +} + +pluginTester.run( + 'test complex type @Once decorated variables transformation', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testParsedAndCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/once/once-with-require.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/once/once-with-require.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..98dd8f1286ec7e1b17eb676f87c0e8760139ebd5 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/once/once-with-require.test.ts @@ -0,0 +1,327 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/once'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'once-with-require.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test @Once Decorator with @Require', buildConfig); + +const expectedParsedScript: string = ` +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, Text as Text, Button as Button } from "@ohos.arkui.component"; + +import { Param as Param, Once as Once, ObservedV2 as ObservedV2, Trace as Trace, Require as Require, Local as Local } from "@ohos.arkui.stateManagement"; + +@ObservedV2() class Info { + @Trace() public name: string = "info"; + + public constructor() {} + +} + +@ComponentV2() final struct Child extends CustomComponentV2 { + @Param() @Once() public onceParamNum: number = 0; + + @Param() @Once() @Require() public onceParamInfo!: Info; + + public build() { + Column(){ + Text(\`Child onceParamNum: \${this.onceParamNum}\`); + Text(\`Child onceParamInfo: \${this.onceParamInfo.name}\`); + Button("changeOnceParamNum").onClick(((e) => { + (this.onceParamNum++); + })); + }; + } + + public constructor() {} + +} + +@ComponentV2() final struct Index extends CustomComponentV2 { + @Local() public localNum: number = 10; + + @Local() public localInfo: Info = new Info(); + + public build() { + Column(){ + Text(\`Parent localNum: \${this.localNum}\`); + Text(\`Parent localInfo: \${this.localInfo.name}\`); + Child({ + onceParamNum: this.localNum, + onceParamInfo: this.localInfo, + }); + }; + } + + public constructor() {} + +} + +@ComponentV2() export interface __Options_Child { + onceParamNum?: number; + @Once() @Param() __backing_onceParamNum?: number; + onceParamInfo?: Info; + @Once() @Param() __backing_onceParamInfo?: Info; + +} + +@ComponentV2() export interface __Options_Index { + localNum?: number; + @Local() __backing_localNum?: number; + localInfo?: Info; + @Local() __backing_localInfo?: Info; + +} +`; + +const expectedCheckedScript: string = ` +import { ILocalDecoratedVariable as ILocalDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { IParamOnceDecoratedVariable as IParamOnceDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ButtonAttribute as ButtonAttribute } from "arkui.component.button"; + +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, Text as Text, Button as Button } from "@ohos.arkui.component"; + +import { Param as Param, Once as Once, ObservedV2 as ObservedV2, Trace as Trace, Require as Require, Local as Local } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@ObservedV2() class Info implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + @JSONRename({newName:"name"}) private __backing_name: string = "info"; + + @JSONStringifyIgnore() private __meta_name: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public get name(): string { + this.conditionalAddRef(this.__meta_name); + return UIUtils.makeObserved(this.__backing_name); + } + + public set name(newValue: string) { + if (((this.__backing_name) !== (newValue))) { + this.__backing_name = newValue; + this.__meta_name.fireChange(); + this.executeOnSubscribingWatches("name"); + } + } + + public constructor() {} + +} + +@ComponentV2() final struct Child extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Child | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_onceParamNum = STATE_MGMT_FACTORY.makeParamOnce(this, "onceParamNum", ((({let gensym___118919021 = initializers; + (((gensym___118919021) == (null)) ? undefined : gensym___118919021.onceParamNum)})) ?? (0))); + this.__backing_onceParamInfo = STATE_MGMT_FACTORY.makeParamOnce(this, "onceParamInfo", (initializers!.onceParamInfo as Info)); + } + + public __updateStruct(initializers: (__Options_Child | undefined)): void {} + + private __backing_onceParamNum?: IParamOnceDecoratedVariable; + + public get onceParamNum(): number { + return this.__backing_onceParamNum!.get(); + } + + public set onceParamNum(value: number) { + this.__backing_onceParamNum!.set(value); + } + + private __backing_onceParamInfo?: IParamOnceDecoratedVariable; + + public get onceParamInfo(): Info { + return this.__backing_onceParamInfo!.get(); + } + + public set onceParamInfo(value: Info) { + this.__backing_onceParamInfo!.set(value); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Text(undefined, \`Child onceParamNum: \${this.onceParamNum}\`, undefined, undefined); + Text(undefined, \`Child onceParamInfo: \${this.onceParamInfo.name}\`, undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + (this.onceParamNum++); + })); + return; + }), "changeOnceParamNum", undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() final struct Index extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Index | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_localNum = STATE_MGMT_FACTORY.makeLocal(this, "localNum", 10); + this.__backing_localInfo = STATE_MGMT_FACTORY.makeLocal(this, "localInfo", new Info()); + } + + public __updateStruct(initializers: (__Options_Index | undefined)): void {} + + private __backing_localNum?: ILocalDecoratedVariable; + + public get localNum(): number { + return this.__backing_localNum!.get(); + } + + public set localNum(value: number) { + this.__backing_localNum!.set(value); + } + + private __backing_localInfo?: ILocalDecoratedVariable; + + public get localInfo(): Info { + return this.__backing_localInfo!.get(); + } + + public set localInfo(value: Info) { + this.__backing_localInfo!.set(value); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Text(undefined, \`Parent localNum: \${this.localNum}\`, undefined, undefined); + Text(undefined, \`Parent localInfo: \${this.localInfo.name}\`, undefined, undefined); + Child._instantiateImpl(undefined, (() => { + return new Child(); + }), { + onceParamNum: this.localNum, + onceParamInfo: this.localInfo, + }, undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Child { + set onceParamNum(onceParamNum: (number | undefined)) + + get onceParamNum(): (number | undefined) + @Param() set __backing_onceParamNum(__backing_onceParamNum: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceParamNum(): (IParamOnceDecoratedVariable | undefined) + set onceParamInfo(onceParamInfo: (Info | undefined)) + + get onceParamInfo(): (Info | undefined) + @Param() set __backing_onceParamInfo(__backing_onceParamInfo: (IParamOnceDecoratedVariable | undefined)) + + @Param() get __backing_onceParamInfo(): (IParamOnceDecoratedVariable | undefined) + +} + +@ComponentV2() export interface __Options_Index { + set localNum(localNum: (number | undefined)) + + get localNum(): (number | undefined) + set __backing_localNum(__backing_localNum: (ILocalDecoratedVariable | undefined)) + + get __backing_localNum(): (ILocalDecoratedVariable | undefined) + set localInfo(localInfo: (Info | undefined)) + + get localInfo(): (Info | undefined) + set __backing_localInfo(__backing_localInfo: (ILocalDecoratedVariable | undefined)) + + get __backing_localInfo(): (ILocalDecoratedVariable | undefined) + +} +`; + +function testParsedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedParsedScript)); +} + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test @Once Decorator with @Require', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'parsed': [testParsedTransformer], + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/param/param-basic-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/param/param-basic-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..c53a65425498e372da06564039bb275670587b70 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/param/param-basic-type.test.ts @@ -0,0 +1,176 @@ +/* + * 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 STATE_DIR_PATH: string = 'decorators/param'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'param-basic-type.ets'), +]; + +const pluginTester = new PluginTester('test basic type @Param decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { IParamDecoratedVariable as IParamDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Param as Param } from "@ohos.arkui.stateManagement"; + +function main() {} + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_paramVar1 = STATE_MGMT_FACTORY.makeParam(this, "paramVar1", ((({let gensym___264789668 = initializers; + (((gensym___264789668) == (null)) ? undefined : gensym___264789668.paramVar1)})) ?? ("stateVar1"))); + this.__backing_paramVar2 = STATE_MGMT_FACTORY.makeParam(this, "paramVar2", ((({let gensym___171906071 = initializers; + (((gensym___171906071) == (null)) ? undefined : gensym___171906071.paramVar2)})) ?? (50))); + this.__backing_paramVar3 = STATE_MGMT_FACTORY.makeParam(this, "paramVar3", ((({let gensym___241535547 = initializers; + (((gensym___241535547) == (null)) ? undefined : gensym___241535547.paramVar3)})) ?? (true))); + this.__backing_paramVar4 = STATE_MGMT_FACTORY.makeParam(this, "paramVar4", ((({let gensym___49490075 = initializers; + (((gensym___49490075) == (null)) ? undefined : gensym___49490075.paramVar4)})) ?? (undefined))); + this.__backing_paramVar5 = STATE_MGMT_FACTORY.makeParam(this, "paramVar5", ((({let gensym___17164613 = initializers; + (((gensym___17164613) == (null)) ? undefined : gensym___17164613.paramVar5)})) ?? (null))); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void { + if (((({let gensym___99629634 = initializers; + (((gensym___99629634) == (null)) ? undefined : gensym___99629634.paramVar1)})) !== (undefined))) { + this.__backing_paramVar1!.update((initializers!.paramVar1 as string)); + } + if (((({let gensym___68516859 = initializers; + (((gensym___68516859) == (null)) ? undefined : gensym___68516859.paramVar2)})) !== (undefined))) { + this.__backing_paramVar2!.update((initializers!.paramVar2 as number)); + } + if (((({let gensym___96937083 = initializers; + (((gensym___96937083) == (null)) ? undefined : gensym___96937083.paramVar3)})) !== (undefined))) { + this.__backing_paramVar3!.update((initializers!.paramVar3 as boolean)); + } + if (((({let gensym___151087626 = initializers; + (((gensym___151087626) == (null)) ? undefined : gensym___151087626.paramVar4)})) !== (undefined))) { + this.__backing_paramVar4!.update((initializers!.paramVar4 as undefined)); + } + if (((({let gensym___127163363 = initializers; + (((gensym___127163363) == (null)) ? undefined : gensym___127163363.paramVar5)})) !== (undefined))) { + this.__backing_paramVar5!.update((initializers!.paramVar5 as null)); + } + } + + private __backing_paramVar1?: IParamDecoratedVariable; + + public get paramVar1(): string { + return this.__backing_paramVar1!.get(); + } + + private __backing_paramVar2?: IParamDecoratedVariable; + + public get paramVar2(): number { + return this.__backing_paramVar2!.get(); + } + + private __backing_paramVar3?: IParamDecoratedVariable; + + public get paramVar3(): boolean { + return this.__backing_paramVar3!.get(); + } + + private __backing_paramVar4?: IParamDecoratedVariable; + + public get paramVar4(): undefined { + return this.__backing_paramVar4!.get(); + } + + private __backing_paramVar5?: IParamDecoratedVariable; + + public get paramVar5(): null { + return this.__backing_paramVar5!.get(); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set paramVar1(paramVar1: (string | undefined)) + + get paramVar1(): (string | undefined) + set __backing_paramVar1(__backing_paramVar1: (IParamDecoratedVariable | undefined)) + + get __backing_paramVar1(): (IParamDecoratedVariable | undefined) + set paramVar2(paramVar2: (number | undefined)) + + get paramVar2(): (number | undefined) + set __backing_paramVar2(__backing_paramVar2: (IParamDecoratedVariable | undefined)) + + get __backing_paramVar2(): (IParamDecoratedVariable | undefined) + set paramVar3(paramVar3: (boolean | undefined)) + + get paramVar3(): (boolean | undefined) + set __backing_paramVar3(__backing_paramVar3: (IParamDecoratedVariable | undefined)) + + get __backing_paramVar3(): (IParamDecoratedVariable | undefined) + set paramVar4(paramVar4: (undefined | undefined)) + + get paramVar4(): (undefined | undefined) + set __backing_paramVar4(__backing_paramVar4: (IParamDecoratedVariable | undefined)) + + get __backing_paramVar4(): (IParamDecoratedVariable | undefined) + set paramVar5(paramVar5: (null | undefined)) + + get paramVar5(): (null | undefined) + set __backing_paramVar5(__backing_paramVar5: (IParamDecoratedVariable | undefined)) + + get __backing_paramVar5(): (IParamDecoratedVariable | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test basic type @Param decorated variables transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/param/param-complex-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/param/param-complex-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0209f191de22d940407cff017f9a43c3cc5d5dc --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/param/param-complex-type.test.ts @@ -0,0 +1,381 @@ +/* + * 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 STATE_DIR_PATH: string = 'decorators/param'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'param-complex-type.ets'), +]; + +const pluginTester = new PluginTester('test complex type @Param decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { IParamDecoratedVariable as IParamDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Param as Param } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +class Per { + public num: number; + + public constructor(num: number) { + this.num = num; + } + +} + +final class StateType extends BaseEnum { + private readonly #ordinal: int; + + private static () {} + + public constructor(ordinal: int, value: int) { + super(value); + this.#ordinal = ordinal; + } + + public static readonly TYPE1: StateType = new StateType(0, 0); + + public static readonly TYPE2: StateType = new StateType(1, 1); + + public static readonly TYPE3: StateType = new StateType(2, 3); + + private static readonly #NamesArray: String[] = ["TYPE1", "TYPE2", "TYPE3"]; + + private static readonly #ValuesArray: int[] = [0, 1, 3]; + + private static readonly #StringValuesArray: String[] = ["0", "1", "3"]; + + private static readonly #ItemsArray: StateType[] = [StateType.TYPE1, StateType.TYPE2, StateType.TYPE3]; + + public getName(): String { + return StateType.#NamesArray[this.#ordinal]; + } + + public static getValueOf(name: String): StateType { + for (let i = 0;((i) < (StateType.#NamesArray.length));(++i)) { + if (((name) == (StateType.#NamesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum constant StateType.") + (name))); + } + + public static fromValue(value: int): StateType { + for (let i = 0;((i) < (StateType.#ValuesArray.length));(++i)) { + if (((value) == (StateType.#ValuesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum StateType with value ") + (value))); + } + + public valueOf(): int { + return StateType.#ValuesArray[this.#ordinal]; + } + + public toString(): String { + return StateType.#StringValuesArray[this.#ordinal]; + } + + public static values(): StateType[] { + return StateType.#ItemsArray; + } + + public getOrdinal(): int { + return this.#ordinal; + } + + public static $_get(e: StateType): String { + return e.getName(); + } + +} + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_paramVar1 = STATE_MGMT_FACTORY.makeParam(this, "paramVar1", ((({let gensym___264789668 = initializers; + (((gensym___264789668) == (null)) ? undefined : gensym___264789668.paramVar1)})) ?? (new Per(6)))); + this.__backing_paramVar2 = STATE_MGMT_FACTORY.makeParam>(this, "paramVar2", ((({let gensym___171906071 = initializers; + (((gensym___171906071) == (null)) ? undefined : gensym___171906071.paramVar2)})) ?? (new Array(3, 6, 8)))); + this.__backing_paramVar3 = STATE_MGMT_FACTORY.makeParam(this, "paramVar3", ((({let gensym___241535547 = initializers; + (((gensym___241535547) == (null)) ? undefined : gensym___241535547.paramVar3)})) ?? (StateType.TYPE3))); + this.__backing_paramVar4 = STATE_MGMT_FACTORY.makeParam>(this, "paramVar4", ((({let gensym___49490075 = initializers; + (((gensym___49490075) == (null)) ? undefined : gensym___49490075.paramVar4)})) ?? (new Set(new Array("aa", "bb"))))); + this.__backing_paramVar5 = STATE_MGMT_FACTORY.makeParam>(this, "paramVar5", ((({let gensym___17164613 = initializers; + (((gensym___17164613) == (null)) ? undefined : gensym___17164613.paramVar5)})) ?? ([true, false]))); + this.__backing_paramVar6 = STATE_MGMT_FACTORY.makeParam>(this, "paramVar6", ((({let gensym___84871771 = initializers; + (((gensym___84871771) == (null)) ? undefined : gensym___84871771.paramVar6)})) ?? (new Array(new Per(7), new Per(11))))); + this.__backing_paramVar7 = STATE_MGMT_FACTORY.makeParam>(this, "paramVar7", ((({let gensym___263986833 = initializers; + (((gensym___263986833) == (null)) ? undefined : gensym___263986833.paramVar7)})) ?? ([new Per(7), new Per(11)]))); + this.__backing_paramVar8 = STATE_MGMT_FACTORY.makeParam<((sr: string)=> void)>(this, "paramVar8", ((({let gensym___6968121 = initializers; + (((gensym___6968121) == (null)) ? undefined : gensym___6968121.paramVar8)})) ?? (((sr: string) => {})))); + this.__backing_paramVar9 = STATE_MGMT_FACTORY.makeParam(this, "paramVar9", ((({let gensym___63984493 = initializers; + (((gensym___63984493) == (null)) ? undefined : gensym___63984493.paramVar9)})) ?? (new Date("2025-4-23")))); + this.__backing_paramVar10 = STATE_MGMT_FACTORY.makeParam>(this, "paramVar10", ((({let gensym___260234648 = initializers; + (((gensym___260234648) == (null)) ? undefined : gensym___260234648.paramVar10)})) ?? (new Map([[0, new Per(7)], [1, new Per(10)]])))); + this.__backing_paramVar11 = STATE_MGMT_FACTORY.makeParam<(string | number)>(this, "paramVar11", ((({let gensym___144998584 = initializers; + (((gensym___144998584) == (null)) ? undefined : gensym___144998584.paramVar11)})) ?? (0.0))); + this.__backing_paramVar12 = STATE_MGMT_FACTORY.makeParam<(Set | Per)>(this, "paramVar12", ((({let gensym___237878674 = initializers; + (((gensym___237878674) == (null)) ? undefined : gensym___237878674.paramVar12)})) ?? (new Per(6)))); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void { + if (((({let gensym___99629634 = initializers; + (((gensym___99629634) == (null)) ? undefined : gensym___99629634.paramVar1)})) !== (undefined))) { + this.__backing_paramVar1!.update((initializers!.paramVar1 as Per)); + } + if (((({let gensym___68516859 = initializers; + (((gensym___68516859) == (null)) ? undefined : gensym___68516859.paramVar2)})) !== (undefined))) { + this.__backing_paramVar2!.update((initializers!.paramVar2 as Array)); + } + if (((({let gensym___96937083 = initializers; + (((gensym___96937083) == (null)) ? undefined : gensym___96937083.paramVar3)})) !== (undefined))) { + this.__backing_paramVar3!.update((initializers!.paramVar3 as StateType)); + } + if (((({let gensym___151087626 = initializers; + (((gensym___151087626) == (null)) ? undefined : gensym___151087626.paramVar4)})) !== (undefined))) { + this.__backing_paramVar4!.update((initializers!.paramVar4 as Set)); + } + if (((({let gensym___127163363 = initializers; + (((gensym___127163363) == (null)) ? undefined : gensym___127163363.paramVar5)})) !== (undefined))) { + this.__backing_paramVar5!.update((initializers!.paramVar5 as Array)); + } + if (((({let gensym___67758341 = initializers; + (((gensym___67758341) == (null)) ? undefined : gensym___67758341.paramVar6)})) !== (undefined))) { + this.__backing_paramVar6!.update((initializers!.paramVar6 as Array)); + } + if (((({let gensym___248313276 = initializers; + (((gensym___248313276) == (null)) ? undefined : gensym___248313276.paramVar7)})) !== (undefined))) { + this.__backing_paramVar7!.update((initializers!.paramVar7 as Array)); + } + if (((({let gensym___215432014 = initializers; + (((gensym___215432014) == (null)) ? undefined : gensym___215432014.paramVar8)})) !== (undefined))) { + this.__backing_paramVar8!.update((initializers!.paramVar8 as ((sr: string)=> void))); + } + if (((({let gensym___69570417 = initializers; + (((gensym___69570417) == (null)) ? undefined : gensym___69570417.paramVar9)})) !== (undefined))) { + this.__backing_paramVar9!.update((initializers!.paramVar9 as Date)); + } + if (((({let gensym___121882834 = initializers; + (((gensym___121882834) == (null)) ? undefined : gensym___121882834.paramVar10)})) !== (undefined))) { + this.__backing_paramVar10!.update((initializers!.paramVar10 as Map)); + } + if (((({let gensym___56641620 = initializers; + (((gensym___56641620) == (null)) ? undefined : gensym___56641620.paramVar11)})) !== (undefined))) { + this.__backing_paramVar11!.update((initializers!.paramVar11 as (string | number))); + } + if (((({let gensym___51325313 = initializers; + (((gensym___51325313) == (null)) ? undefined : gensym___51325313.paramVar12)})) !== (undefined))) { + this.__backing_paramVar12!.update((initializers!.paramVar12 as (Set | Per))); + } + } + + private __backing_paramVar1?: IParamDecoratedVariable; + + public get paramVar1(): Per { + return this.__backing_paramVar1!.get(); + } + + private __backing_paramVar2?: IParamDecoratedVariable>; + + public get paramVar2(): Array { + return this.__backing_paramVar2!.get(); + } + + private __backing_paramVar3?: IParamDecoratedVariable; + + public get paramVar3(): StateType { + return this.__backing_paramVar3!.get(); + } + + private __backing_paramVar4?: IParamDecoratedVariable>; + + public get paramVar4(): Set { + return this.__backing_paramVar4!.get(); + } + + private __backing_paramVar5?: IParamDecoratedVariable>; + + public get paramVar5(): Array { + return this.__backing_paramVar5!.get(); + } + + private __backing_paramVar6?: IParamDecoratedVariable>; + + public get paramVar6(): Array { + return this.__backing_paramVar6!.get(); + } + + private __backing_paramVar7?: IParamDecoratedVariable>; + + public get paramVar7(): Array { + return this.__backing_paramVar7!.get(); + } + + private __backing_paramVar8?: IParamDecoratedVariable<((sr: string)=> void)>; + + public get paramVar8(): ((sr: string)=> void) { + return this.__backing_paramVar8!.get(); + } + + private __backing_paramVar9?: IParamDecoratedVariable; + + public get paramVar9(): Date { + return this.__backing_paramVar9!.get(); + } + + private __backing_paramVar10?: IParamDecoratedVariable>; + + public get paramVar10(): Map { + return this.__backing_paramVar10!.get(); + } + + private __backing_paramVar11?: IParamDecoratedVariable<(string | number)>; + + public get paramVar11(): (string | number) { + return this.__backing_paramVar11!.get(); + } + + private __backing_paramVar12?: IParamDecoratedVariable<(Set | Per)>; + + public get paramVar12(): (Set | Per) { + return this.__backing_paramVar12!.get(); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set paramVar1(paramVar1: (Per | undefined)) + + get paramVar1(): (Per | undefined) + set __backing_paramVar1(__backing_paramVar1: (IParamDecoratedVariable | undefined)) + + get __backing_paramVar1(): (IParamDecoratedVariable | undefined) + set paramVar2(paramVar2: (Array | undefined)) + + get paramVar2(): (Array | undefined) + set __backing_paramVar2(__backing_paramVar2: (IParamDecoratedVariable> | undefined)) + + get __backing_paramVar2(): (IParamDecoratedVariable> | undefined) + set paramVar3(paramVar3: (StateType | undefined)) + + get paramVar3(): (StateType | undefined) + set __backing_paramVar3(__backing_paramVar3: (IParamDecoratedVariable | undefined)) + + get __backing_paramVar3(): (IParamDecoratedVariable | undefined) + set paramVar4(paramVar4: (Set | undefined)) + + get paramVar4(): (Set | undefined) + set __backing_paramVar4(__backing_paramVar4: (IParamDecoratedVariable> | undefined)) + + get __backing_paramVar4(): (IParamDecoratedVariable> | undefined) + set paramVar5(paramVar5: (Array | undefined)) + + get paramVar5(): (Array | undefined) + set __backing_paramVar5(__backing_paramVar5: (IParamDecoratedVariable> | undefined)) + + get __backing_paramVar5(): (IParamDecoratedVariable> | undefined) + set paramVar6(paramVar6: (Array | undefined)) + + get paramVar6(): (Array | undefined) + set __backing_paramVar6(__backing_paramVar6: (IParamDecoratedVariable> | undefined)) + + get __backing_paramVar6(): (IParamDecoratedVariable> | undefined) + set paramVar7(paramVar7: (Array | undefined)) + + get paramVar7(): (Array | undefined) + set __backing_paramVar7(__backing_paramVar7: (IParamDecoratedVariable> | undefined)) + + get __backing_paramVar7(): (IParamDecoratedVariable> | undefined) + set paramVar8(paramVar8: (((sr: string)=> void) | undefined)) + + get paramVar8(): (((sr: string)=> void) | undefined) + set __backing_paramVar8(__backing_paramVar8: (IParamDecoratedVariable<((sr: string)=> void)> | undefined)) + + get __backing_paramVar8(): (IParamDecoratedVariable<((sr: string)=> void)> | undefined) + set paramVar9(paramVar9: (Date | undefined)) + + get paramVar9(): (Date | undefined) + set __backing_paramVar9(__backing_paramVar9: (IParamDecoratedVariable | undefined)) + + get __backing_paramVar9(): (IParamDecoratedVariable | undefined) + set paramVar10(paramVar10: (Map | undefined)) + + get paramVar10(): (Map | undefined) + set __backing_paramVar10(__backing_paramVar10: (IParamDecoratedVariable> | undefined)) + + get __backing_paramVar10(): (IParamDecoratedVariable> | undefined) + set paramVar11(paramVar11: ((string | number) | undefined)) + + get paramVar11(): ((string | number) | undefined) + set __backing_paramVar11(__backing_paramVar11: (IParamDecoratedVariable<(string | number)> | undefined)) + + get __backing_paramVar11(): (IParamDecoratedVariable<(string | number)> | undefined) + set paramVar12(paramVar12: ((Set | Per) | undefined)) + + get paramVar12(): ((Set | Per) | undefined) + set __backing_paramVar12(__backing_paramVar12: (IParamDecoratedVariable<(Set | Per)> | undefined)) + + get __backing_paramVar12(): (IParamDecoratedVariable<(Set | Per)> | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test complex type @Param decorated variables transformation', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/param/param-with-require.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/param/param-with-require.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..5833664982d56b03e2717f0e0a971e72f962c9bc --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/param/param-with-require.test.ts @@ -0,0 +1,347 @@ +/* + * 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 OBSERVED_DIR_PATH: string = 'decorators/param'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, OBSERVED_DIR_PATH, 'param-with-require.ets'), +]; + +const observedTrackTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed, +} + +const pluginTester = new PluginTester('test @Param Decorator with @Require', buildConfig); + +const expectedParsedScript: string = ` +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, ForEach as ForEach, Button as Button, Text as Text } from "@ohos.arkui.component"; + +import { Param as Param, Require as Require, Local as Local } from "@ohos.arkui.stateManagement"; + +class Region { + public x: number; + + public y: number; + + public constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + +} + +class Info { + public name: string; + + public age: number; + + public region: Region; + + public constructor(name: string, age: number, x: number, y: number) { + this.name = name; + this.age = age; + this.region = new Region(x, y); + } + +} + +@ComponentV2() final struct Index extends CustomComponentV2 { + @Local() public infoList: Info[] = [new Info("Alice", 8, 0, 0), new Info("Barry", 10, 1, 20), new Info("Cindy", 18, 24, 40)]; + + public build() { + Column(){ + ForEach(this.infoList, ((info: Info) => { + MiddleComponent({ + info: info, + }); + })); + Button("change").onClick(((e) => { + this.infoList[0] = new Info("Atom", 40, 27, 90); + this.infoList[1].name = "Bob"; + this.infoList[2].region = new Region(7, 9); + })); + }; + } + + public constructor() {} + +} + +@ComponentV2() final struct MiddleComponent extends CustomComponentV2 { + @Require() @Param() public info!: Info; + + public build() { + Column(){ + Text(\`name: \${this.info.name}\`); + Text(\`age: \${this.info.age}\`); + SubComponent({ + region: this.info.region, + }); + }; + } + + public constructor() {} + +} + +@ComponentV2() final struct SubComponent extends CustomComponentV2 { + @Require() @Param() public region!: Region; + + public build() { + Column(){ + Text(\`region: \${this.region.x}-\${this.region.y}\`); + }; + } + + public constructor() {} + +} + +@ComponentV2() export interface __Options_Index { + infoList?: Info[]; + @Local() __backing_infoList?: Info[]; + +} + +@ComponentV2() export interface __Options_MiddleComponent { + info?: Info; + @Param() __backing_info?: Info; + +} + +@ComponentV2() export interface __Options_SubComponent { + region?: Region; + @Param() __backing_region?: Region; + +} +`; + +const expectedCheckedScript: string = ` +import { IParamDecoratedVariable as IParamDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { ILocalDecoratedVariable as ILocalDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ButtonAttribute as ButtonAttribute } from "arkui.component.button"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, Column as Column, ForEach as ForEach, Button as Button, Text as Text } from "@ohos.arkui.component"; + +import { Param as Param, Require as Require, Local as Local } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +class Region { + public x: number; + + public y: number; + + public constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + +} + +class Info { + public name: string; + + public age: number; + + public region: Region; + + public constructor(name: string, age: number, x: number, y: number) { + this.name = name; + this.age = age; + this.region = new Region(x, y); + } + +} + +@ComponentV2() final struct Index extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Index | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_infoList = STATE_MGMT_FACTORY.makeLocal>(this, "infoList", [new Info("Alice", 8, 0, 0), new Info("Barry", 10, 1, 20), new Info("Cindy", 18, 24, 40)]); + } + + public __updateStruct(initializers: (__Options_Index | undefined)): void {} + + private __backing_infoList?: ILocalDecoratedVariable>; + + public get infoList(): Array { + return this.__backing_infoList!.get(); + } + + public set infoList(value: Array) { + this.__backing_infoList!.set(value); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + ForEach(((): Array => { + return this.infoList; + }), ((info: Info) => { + MiddleComponent._instantiateImpl(undefined, (() => { + return new MiddleComponent(); + }), { + info: info, + }, undefined, undefined); + })); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.infoList[0] = new Info("Atom", 40, 27, 90); + this.infoList[1].name = "Bob"; + this.infoList[2].region = new Region(7, 9); + })); + return; + }), "change", undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() final struct MiddleComponent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_MiddleComponent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_info = STATE_MGMT_FACTORY.makeParam(this, "info", (initializers!.info as Info)); + } + + public __updateStruct(initializers: (__Options_MiddleComponent | undefined)): void { + if (((({let gensym___152615498 = initializers; + (((gensym___152615498) == (null)) ? undefined : gensym___152615498.info)})) !== (undefined))) { + this.__backing_info!.update((initializers!.info as Info)); + } + } + + private __backing_info?: IParamDecoratedVariable; + + public get info(): Info { + return this.__backing_info!.get(); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Text(undefined, \`name: \${this.info.name}\`, undefined, undefined); + Text(undefined, \`age: \${this.info.age}\`, undefined, undefined); + SubComponent._instantiateImpl(undefined, (() => { + return new SubComponent(); + }), { + region: this.info.region, + }, undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() final struct SubComponent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_SubComponent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_region = STATE_MGMT_FACTORY.makeParam(this, "region", (initializers!.region as Region)); + } + + public __updateStruct(initializers: (__Options_SubComponent | undefined)): void { + if (((({let gensym___240509478 = initializers; + (((gensym___240509478) == (null)) ? undefined : gensym___240509478.region)})) !== (undefined))) { + this.__backing_region!.update((initializers!.region as Region)); + } + } + + private __backing_region?: IParamDecoratedVariable; + + public get region(): Region { + return this.__backing_region!.get(); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Text(undefined, \`region: \${this.region.x}-\${this.region.y}\`, undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Index { + set infoList(infoList: (Array | undefined)) + + get infoList(): (Array | undefined) + set __backing_infoList(__backing_infoList: (ILocalDecoratedVariable> | undefined)) + + get __backing_infoList(): (ILocalDecoratedVariable> | undefined) + +} + +@ComponentV2() export interface __Options_MiddleComponent { + set info(info: (Info | undefined)) + + get info(): (Info | undefined) + set __backing_info(__backing_info: (IParamDecoratedVariable | undefined)) + + get __backing_info(): (IParamDecoratedVariable | undefined) + +} + +@ComponentV2() export interface __Options_SubComponent { + set region(region: (Region | undefined)) + + get region(): (Region | undefined) + set __backing_region(__backing_region: (IParamDecoratedVariable | undefined)) + + get __backing_region(): (IParamDecoratedVariable | undefined) + +} +`; + +function testParsedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedParsedScript)); +} + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test @Param Decorator with @Require', + [observedTrackTransform, uiNoRecheck, recheck], + { + 'parsed': [testParsedTransformer], + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/consume-basic-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/consume-basic-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8245372fbca0097ae8215e1c4ba22717524966e --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/consume-basic-type.test.ts @@ -0,0 +1,216 @@ +/* + * 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 { structNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const STATE_DIR_PATH: string = 'decorators/provide-and-consume'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'consume-basic-type.ets'), +]; + +const pluginTester = new PluginTester('test basic type @Consume decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedParsedScript: string = ` +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component } from "@ohos.arkui.component"; + +import { Consume as Consume } from "@ohos.arkui.stateManagement"; + +@Component() final struct PropParent extends CustomComponent { + @Consume() public conVar1!: string; + + @Consume() public conVar2!: number; + + @Consume() public conVar3!: boolean; + + @Consume() public conVar4?: undefined; + + @Consume() public conVar5?: null; + + public build() {} + + public constructor() {} + +} + +@Component() export interface __Options_PropParent { + conVar1?: string; + @Consume() __backing_conVar1?: string; + conVar2?: number; + @Consume() __backing_conVar2?: number; + conVar3?: boolean; + @Consume() __backing_conVar3?: boolean; + conVar4?: undefined; + @Consume() __backing_conVar4?: undefined; + conVar5?: null; + @Consume() __backing_conVar5?: null; + +} +`; + +const expectedCheckedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { IConsumeDecoratedVariable as IConsumeDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component } from "@ohos.arkui.component"; + +import { Consume as Consume } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@Component() final struct PropParent extends CustomComponent { + public __initializeStruct(initializers: (__Options_PropParent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_conVar1 = STATE_MGMT_FACTORY.makeConsume(this, "conVar1", "conVar1"); + this.__backing_conVar2 = STATE_MGMT_FACTORY.makeConsume(this, "conVar2", "conVar2"); + this.__backing_conVar3 = STATE_MGMT_FACTORY.makeConsume(this, "conVar3", "conVar3"); + this.__backing_conVar4 = STATE_MGMT_FACTORY.makeConsume(this, "conVar4", "conVar4"); + this.__backing_conVar5 = STATE_MGMT_FACTORY.makeConsume(this, "conVar5", "conVar5"); + } + + public __updateStruct(initializers: (__Options_PropParent | undefined)): void {} + + private __backing_conVar1?: IConsumeDecoratedVariable; + + public get conVar1(): string { + return this.__backing_conVar1!.get(); + } + + public set conVar1(value: string) { + this.__backing_conVar1!.set(value); + } + + private __backing_conVar2?: IConsumeDecoratedVariable; + + public get conVar2(): number { + return this.__backing_conVar2!.get(); + } + + public set conVar2(value: number) { + this.__backing_conVar2!.set(value); + } + + private __backing_conVar3?: IConsumeDecoratedVariable; + + public get conVar3(): boolean { + return this.__backing_conVar3!.get(); + } + + public set conVar3(value: boolean) { + this.__backing_conVar3!.set(value); + } + + private __backing_conVar4?: IConsumeDecoratedVariable; + + public get conVar4(): undefined { + return this.__backing_conVar4!.get(); + } + + public set conVar4(value: undefined) { + this.__backing_conVar4!.set(value); + } + + private __backing_conVar5?: IConsumeDecoratedVariable; + + public get conVar5(): null { + return this.__backing_conVar5!.get(); + } + + public set conVar5(value: null) { + this.__backing_conVar5!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@Component() export interface __Options_PropParent { + set conVar1(conVar1: (string | undefined)) + + get conVar1(): (string | undefined) + set __backing_conVar1(__backing_conVar1: (IConsumeDecoratedVariable | undefined)) + + get __backing_conVar1(): (IConsumeDecoratedVariable | undefined) + set conVar2(conVar2: (number | undefined)) + + get conVar2(): (number | undefined) + set __backing_conVar2(__backing_conVar2: (IConsumeDecoratedVariable | undefined)) + + get __backing_conVar2(): (IConsumeDecoratedVariable | undefined) + set conVar3(conVar3: (boolean | undefined)) + + get conVar3(): (boolean | undefined) + set __backing_conVar3(__backing_conVar3: (IConsumeDecoratedVariable | undefined)) + + get __backing_conVar3(): (IConsumeDecoratedVariable | undefined) + set conVar4(conVar4: (undefined | undefined)) + + get conVar4(): (undefined | undefined) + set __backing_conVar4(__backing_conVar4: (IConsumeDecoratedVariable | undefined)) + + get __backing_conVar4(): (IConsumeDecoratedVariable | undefined) + set conVar5(conVar5: (null | undefined)) + + get conVar5(): (null | undefined) + set __backing_conVar5(__backing_conVar5: (IConsumeDecoratedVariable | undefined)) + + get __backing_conVar5(): (IConsumeDecoratedVariable | undefined) + +} +`; + +function testParsedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedParsedScript)); +} + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test basic type @Consume decorated variables transformation', + [parsedTransform, structNoRecheck, recheck], + { + 'parsed': [testParsedTransformer], + 'checked:struct-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/consume-complex-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/consume-complex-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..b226ecbbdcb2482b046a9763fb64ebbe5399a02b --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/consume-complex-type.test.ts @@ -0,0 +1,476 @@ +/* + * 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 { structNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const STATE_DIR_PATH: string = 'decorators/provide-and-consume'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'consume-complex-type.ets'), +]; + +const pluginTester = new PluginTester('test complex type @Consume decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedParsedScript: string = ` +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component } from "@ohos.arkui.component"; + +import { Consume as Consume } from "@ohos.arkui.stateManagement"; + +class Per { + public num: number; + + public constructor(num: number) { + this.num = num; + } + +} + +enum PropType { + TYPE1 = 0, + TYPE2 = 1, + TYPE3 = 3 +} + +@Component() final struct Parent extends CustomComponent { + @Consume() public conVar1!: Per; + + @Consume() public conVar2!: Array; + + @Consume() public conVar3!: PropType; + + @Consume() public conVar4!: Set; + + @Consume() public conVar5!: boolean[]; + + @Consume() public conVar6!: Array; + + @Consume() public conVar7!: Per[]; + + @Consume() public conVar8!: ((sr: string)=> void); + + @Consume() public conVar9!: Date; + + @Consume() public conVar10!: Map; + + @Consume() public conVar11!: (string | number); + + @Consume() public conVar12!: (Set | Per); + + @Consume() public conVar13?: (Set | null); + + public build() {} + + public constructor() {} + +} + +@Component() export interface __Options_Parent { + conVar1?: Per; + @Consume() __backing_conVar1?: Per; + conVar2?: Array; + @Consume() __backing_conVar2?: Array; + conVar3?: PropType; + @Consume() __backing_conVar3?: PropType; + conVar4?: Set; + @Consume() __backing_conVar4?: Set; + conVar5?: boolean[]; + @Consume() __backing_conVar5?: boolean[]; + conVar6?: Array; + @Consume() __backing_conVar6?: Array; + conVar7?: Per[]; + @Consume() __backing_conVar7?: Per[]; + conVar8?: ((sr: string)=> void); + @Consume() __backing_conVar8?: ((sr: string)=> void); + conVar9?: Date; + @Consume() __backing_conVar9?: Date; + conVar10?: Map; + @Consume() __backing_conVar10?: Map; + conVar11?: (string | number); + @Consume() __backing_conVar11?: (string | number); + conVar12?: (Set | Per); + @Consume() __backing_conVar12?: (Set | Per); + conVar13?: (Set | null); + @Consume() __backing_conVar13?: (Set | null); + +} +`; + +const expectedCheckedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { IConsumeDecoratedVariable as IConsumeDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component } from "@ohos.arkui.component"; + +import { Consume as Consume } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +class Per { + public num: number; + + public constructor(num: number) { + this.num = num; + } + +} + +final class PropType extends BaseEnum { + private readonly #ordinal: int; + + private static () {} + + public constructor(ordinal: int, value: int) { + super(value); + this.#ordinal = ordinal; + } + + public static readonly TYPE1: PropType = new PropType(0, 0); + + public static readonly TYPE2: PropType = new PropType(1, 1); + + public static readonly TYPE3: PropType = new PropType(2, 3); + + private static readonly #NamesArray: String[] = ["TYPE1", "TYPE2", "TYPE3"]; + + private static readonly #ValuesArray: int[] = [0, 1, 3]; + + private static readonly #StringValuesArray: String[] = ["0", "1", "3"]; + + private static readonly #ItemsArray: PropType[] = [PropType.TYPE1, PropType.TYPE2, PropType.TYPE3]; + + public getName(): String { + return PropType.#NamesArray[this.#ordinal]; + } + + public static getValueOf(name: String): PropType { + for (let i = 0;((i) < (PropType.#NamesArray.length));(++i)) { + if (((name) == (PropType.#NamesArray[i]))) { + return PropType.#ItemsArray[i]; + } + } + throw new Error((("No enum constant PropType.") + (name))); + } + + public static fromValue(value: int): PropType { + for (let i = 0;((i) < (PropType.#ValuesArray.length));(++i)) { + if (((value) == (PropType.#ValuesArray[i]))) { + return PropType.#ItemsArray[i]; + } + } + throw new Error((("No enum PropType with value ") + (value))); + } + + public valueOf(): int { + return PropType.#ValuesArray[this.#ordinal]; + } + + public toString(): String { + return PropType.#StringValuesArray[this.#ordinal]; + } + + public static values(): PropType[] { + return PropType.#ItemsArray; + } + + public getOrdinal(): int { + return this.#ordinal; + } + + public static $_get(e: PropType): String { + return e.getName(); + } + +} + +@Component() final struct Parent extends CustomComponent { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_conVar1 = STATE_MGMT_FACTORY.makeConsume(this, "conVar1", "conVar1"); + this.__backing_conVar2 = STATE_MGMT_FACTORY.makeConsume>(this, "conVar2", "conVar2"); + this.__backing_conVar3 = STATE_MGMT_FACTORY.makeConsume(this, "conVar3", "conVar3"); + this.__backing_conVar4 = STATE_MGMT_FACTORY.makeConsume>(this, "conVar4", "conVar4"); + this.__backing_conVar5 = STATE_MGMT_FACTORY.makeConsume>(this, "conVar5", "conVar5"); + this.__backing_conVar6 = STATE_MGMT_FACTORY.makeConsume>(this, "conVar6", "conVar6"); + this.__backing_conVar7 = STATE_MGMT_FACTORY.makeConsume>(this, "conVar7", "conVar7"); + this.__backing_conVar8 = STATE_MGMT_FACTORY.makeConsume<((sr: string)=> void)>(this, "conVar8", "conVar8"); + this.__backing_conVar9 = STATE_MGMT_FACTORY.makeConsume(this, "conVar9", "conVar9"); + this.__backing_conVar10 = STATE_MGMT_FACTORY.makeConsume>(this, "conVar10", "conVar10"); + this.__backing_conVar11 = STATE_MGMT_FACTORY.makeConsume<(string | number)>(this, "conVar11", "conVar11"); + this.__backing_conVar12 = STATE_MGMT_FACTORY.makeConsume<(Set | Per)>(this, "conVar12", "conVar12"); + this.__backing_conVar13 = STATE_MGMT_FACTORY.makeConsume<(Set | null)>(this, "conVar13", "conVar13"); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_conVar1?: IConsumeDecoratedVariable; + + public get conVar1(): Per { + return this.__backing_conVar1!.get(); + } + + public set conVar1(value: Per) { + this.__backing_conVar1!.set(value); + } + + private __backing_conVar2?: IConsumeDecoratedVariable>; + + public get conVar2(): Array { + return this.__backing_conVar2!.get(); + } + + public set conVar2(value: Array) { + this.__backing_conVar2!.set(value); + } + + private __backing_conVar3?: IConsumeDecoratedVariable; + + public get conVar3(): PropType { + return this.__backing_conVar3!.get(); + } + + public set conVar3(value: PropType) { + this.__backing_conVar3!.set(value); + } + + private __backing_conVar4?: IConsumeDecoratedVariable>; + + public get conVar4(): Set { + return this.__backing_conVar4!.get(); + } + + public set conVar4(value: Set) { + this.__backing_conVar4!.set(value); + } + + private __backing_conVar5?: IConsumeDecoratedVariable>; + + public get conVar5(): Array { + return this.__backing_conVar5!.get(); + } + + public set conVar5(value: Array) { + this.__backing_conVar5!.set(value); + } + + private __backing_conVar6?: IConsumeDecoratedVariable>; + + public get conVar6(): Array { + return this.__backing_conVar6!.get(); + } + + public set conVar6(value: Array) { + this.__backing_conVar6!.set(value); + } + + private __backing_conVar7?: IConsumeDecoratedVariable>; + + public get conVar7(): Array { + return this.__backing_conVar7!.get(); + } + + public set conVar7(value: Array) { + this.__backing_conVar7!.set(value); + } + + private __backing_conVar8?: IConsumeDecoratedVariable<((sr: string)=> void)>; + + public get conVar8(): ((sr: string)=> void) { + return this.__backing_conVar8!.get(); + } + + public set conVar8(value: ((sr: string)=> void)) { + this.__backing_conVar8!.set(value); + } + + private __backing_conVar9?: IConsumeDecoratedVariable; + + public get conVar9(): Date { + return this.__backing_conVar9!.get(); + } + + public set conVar9(value: Date) { + this.__backing_conVar9!.set(value); + } + + private __backing_conVar10?: IConsumeDecoratedVariable>; + + public get conVar10(): Map { + return this.__backing_conVar10!.get(); + } + + public set conVar10(value: Map) { + this.__backing_conVar10!.set(value); + } + + private __backing_conVar11?: IConsumeDecoratedVariable<(string | number)>; + + public get conVar11(): (string | number) { + return this.__backing_conVar11!.get(); + } + + public set conVar11(value: (string | number)) { + this.__backing_conVar11!.set(value); + } + + private __backing_conVar12?: IConsumeDecoratedVariable<(Set | Per)>; + + public get conVar12(): (Set | Per) { + return this.__backing_conVar12!.get(); + } + + public set conVar12(value: (Set | Per)) { + this.__backing_conVar12!.set(value); + } + + private __backing_conVar13?: IConsumeDecoratedVariable<(Set | null)>; + + public get conVar13(): (Set | null) { + return this.__backing_conVar13!.get(); + } + + public set conVar13(value: (Set | null)) { + this.__backing_conVar13!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@Component() export interface __Options_Parent { + set conVar1(conVar1: (Per | undefined)) + + get conVar1(): (Per | undefined) + set __backing_conVar1(__backing_conVar1: (IConsumeDecoratedVariable | undefined)) + + get __backing_conVar1(): (IConsumeDecoratedVariable | undefined) + set conVar2(conVar2: (Array | undefined)) + + get conVar2(): (Array | undefined) + set __backing_conVar2(__backing_conVar2: (IConsumeDecoratedVariable> | undefined)) + + get __backing_conVar2(): (IConsumeDecoratedVariable> | undefined) + set conVar3(conVar3: (PropType | undefined)) + + get conVar3(): (PropType | undefined) + set __backing_conVar3(__backing_conVar3: (IConsumeDecoratedVariable | undefined)) + + get __backing_conVar3(): (IConsumeDecoratedVariable | undefined) + set conVar4(conVar4: (Set | undefined)) + + get conVar4(): (Set | undefined) + set __backing_conVar4(__backing_conVar4: (IConsumeDecoratedVariable> | undefined)) + + get __backing_conVar4(): (IConsumeDecoratedVariable> | undefined) + set conVar5(conVar5: (Array | undefined)) + + get conVar5(): (Array | undefined) + set __backing_conVar5(__backing_conVar5: (IConsumeDecoratedVariable> | undefined)) + + get __backing_conVar5(): (IConsumeDecoratedVariable> | undefined) + set conVar6(conVar6: (Array | undefined)) + + get conVar6(): (Array | undefined) + set __backing_conVar6(__backing_conVar6: (IConsumeDecoratedVariable> | undefined)) + + get __backing_conVar6(): (IConsumeDecoratedVariable> | undefined) + set conVar7(conVar7: (Array | undefined)) + + get conVar7(): (Array | undefined) + set __backing_conVar7(__backing_conVar7: (IConsumeDecoratedVariable> | undefined)) + + get __backing_conVar7(): (IConsumeDecoratedVariable> | undefined) + set conVar8(conVar8: (((sr: string)=> void) | undefined)) + + get conVar8(): (((sr: string)=> void) | undefined) + set __backing_conVar8(__backing_conVar8: (IConsumeDecoratedVariable<((sr: string)=> void)> | undefined)) + + get __backing_conVar8(): (IConsumeDecoratedVariable<((sr: string)=> void)> | undefined) + set conVar9(conVar9: (Date | undefined)) + + get conVar9(): (Date | undefined) + set __backing_conVar9(__backing_conVar9: (IConsumeDecoratedVariable | undefined)) + + get __backing_conVar9(): (IConsumeDecoratedVariable | undefined) + set conVar10(conVar10: (Map | undefined)) + + get conVar10(): (Map | undefined) + set __backing_conVar10(__backing_conVar10: (IConsumeDecoratedVariable> | undefined)) + + get __backing_conVar10(): (IConsumeDecoratedVariable> | undefined) + set conVar11(conVar11: ((string | number) | undefined)) + + get conVar11(): ((string | number) | undefined) + set __backing_conVar11(__backing_conVar11: (IConsumeDecoratedVariable<(string | number)> | undefined)) + + get __backing_conVar11(): (IConsumeDecoratedVariable<(string | number)> | undefined) + set conVar12(conVar12: ((Set | Per) | undefined)) + + get conVar12(): ((Set | Per) | undefined) + set __backing_conVar12(__backing_conVar12: (IConsumeDecoratedVariable<(Set | Per)> | undefined)) + + get __backing_conVar12(): (IConsumeDecoratedVariable<(Set | Per)> | undefined) + set conVar13(conVar13: ((Set | null) | undefined)) + + get conVar13(): ((Set | null) | undefined) + set __backing_conVar13(__backing_conVar13: (IConsumeDecoratedVariable<(Set | null)> | undefined)) + + get __backing_conVar13(): (IConsumeDecoratedVariable<(Set | null)> | undefined) + +} +`; + +function testParsedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedParsedScript)); +} + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test complex type @Consume decorated variables transformation', + [parsedTransform, structNoRecheck, recheck], + { + 'parsed': [testParsedTransformer], + 'checked:struct-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/provide-to-consume.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/provide-to-consume.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..e8293b0850962e81768187c46e8618535430bf13 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/provide-and-consume/provide-to-consume.test.ts @@ -0,0 +1,250 @@ +/* + * 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 STATE_DIR_PATH: string = 'decorators/provide-and-consume'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'provide-to-consume.ets'), +]; + +const pluginTester = new PluginTester('test usage of @Provide and @Consume decorator', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedParsedScript: string = ` +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Column as Column, Text as Text } from "@ohos.arkui.component"; + +import { Consume as Consume, Provide as Provide } from "@ohos.arkui.stateManagement"; + +@Component() final struct Child extends CustomComponent { + @Consume() public num!: number; + + @Consume({value:"ss"}) public str!: string; + + public build() { + Column(){ + Text(\`Child num: \${this.num}\`); + Text(\`Child str: \${this.str}\`); + }; + } + + public constructor() {} + +} + +@Component() final struct Parent extends CustomComponent { + @Provide({alias:"num"}) public num: number = 10; + + @Provide({alias:"ss"}) public str: string = "hello"; + + public build() { + Column(){ + Text(\`Parent num: \${this.num}\`); + Text(\`Parent str: \${this.str}\`); + Child(); + }; + } + + public constructor() {} + +} + +@Component() export interface __Options_Child { + num?: number; + @Consume() __backing_num?: number; + str?: string; + @Consume({value:"ss"}) __backing_str?: string; + +} + +@Component() export interface __Options_Parent { + num?: number; + @Provide({alias:"num"}) __backing_num?: number; + str?: string; + @Provide({alias:"ss"}) __backing_str?: string; + +} +`; + +const expectedCheckedScript: string = ` +import { IProvideDecoratedVariable as IProvideDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { IConsumeDecoratedVariable as IConsumeDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { CustomComponent as CustomComponent } from "arkui.component.customComponent"; + +import { Component as Component, Column as Column, Text as Text } from "@ohos.arkui.component"; + +import { Consume as Consume, Provide as Provide } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@Component() final struct Child extends CustomComponent { + public __initializeStruct(initializers: (__Options_Child | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_num = STATE_MGMT_FACTORY.makeConsume(this, "num", "num"); + this.__backing_str = STATE_MGMT_FACTORY.makeConsume(this, "str", "ss"); + } + + public __updateStruct(initializers: (__Options_Child | undefined)): void {} + + private __backing_num?: IConsumeDecoratedVariable; + + public get num(): number { + return this.__backing_num!.get(); + } + + public set num(value: number) { + this.__backing_num!.set(value); + } + + private __backing_str?: IConsumeDecoratedVariable; + + public get str(): string { + return this.__backing_str!.get(); + } + + public set str(value: string) { + this.__backing_str!.set(value); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Text(undefined, \`Child num: \${this.num}\`, undefined, undefined); + Text(undefined, \`Child str: \${this.str}\`, undefined, undefined); + })); + } + + private constructor() {} + +} + +@Component() final struct Parent extends CustomComponent { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_num = STATE_MGMT_FACTORY.makeProvide(this, "num", "num", ((({let gensym___83257243 = initializers; + (((gensym___83257243) == (null)) ? undefined : gensym___83257243.num)})) ?? (10)), false); + this.__backing_str = STATE_MGMT_FACTORY.makeProvide(this, "str", "ss", ((({let gensym___249074315 = initializers; + (((gensym___249074315) == (null)) ? undefined : gensym___249074315.str)})) ?? ("hello")), false); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_num?: IProvideDecoratedVariable; + + public get num(): number { + return this.__backing_num!.get(); + } + + public set num(value: number) { + this.__backing_num!.set(value); + } + + private __backing_str?: IProvideDecoratedVariable; + + public get str(): string { + return this.__backing_str!.get(); + } + + public set str(value: string) { + this.__backing_str!.set(value); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Text(undefined, \`Parent num: \${this.num}\`, undefined, undefined); + Text(undefined, \`Parent str: \${this.str}\`, undefined, undefined); + Child._instantiateImpl(undefined, (() => { + return new Child(); + }), undefined, undefined, undefined); + })); + } + + private constructor() {} + +} + +@Component() export interface __Options_Child { + set num(num: (number | undefined)) + + get num(): (number | undefined) + set __backing_num(__backing_num: (IConsumeDecoratedVariable | undefined)) + + get __backing_num(): (IConsumeDecoratedVariable | undefined) + set str(str: (string | undefined)) + + get str(): (string | undefined) + set __backing_str(__backing_str: (IConsumeDecoratedVariable | undefined)) + + get __backing_str(): (IConsumeDecoratedVariable | undefined) + +} + +@Component() export interface __Options_Parent { + set num(num: (number | undefined)) + + get num(): (number | undefined) + set __backing_num(__backing_num: (IProvideDecoratedVariable | undefined)) + + get __backing_num(): (IProvideDecoratedVariable | undefined) + set str(str: (string | undefined)) + + get str(): (string | undefined) + set __backing_str(__backing_str: (IProvideDecoratedVariable | undefined)) + + get __backing_str(): (IProvideDecoratedVariable | undefined) + +} +`; + +function testParsedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedParsedScript)); +} + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test usage of @Provide and @Consume decorator', + [parsedTransform, uiNoRecheck, recheck], + { + 'parsed': [testParsedTransformer], + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/consumer-basic-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/consumer-basic-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..477d67cb112c0504d4cf73ecbe86755ce66bf3d0 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/consumer-basic-type.test.ts @@ -0,0 +1,172 @@ +/* + * 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 { structNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const STATE_DIR_PATH: string = 'decorators/provider-and-consumer'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'consumer-basic-type.ets'), +]; + +const pluginTester = new PluginTester('test basic type @Consumer decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { IConsumerDecoratedVariable as IConsumerDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Consumer as Consumer } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_consumerVar1 = STATE_MGMT_FACTORY.makeConsumer(this, "consumerVar1", "consumerVar1", "propVar1"); + this.__backing_consumerVar2 = STATE_MGMT_FACTORY.makeConsumer(this, "consumerVar2", "consumerVar2", 50); + this.__backing_consumerVar3 = STATE_MGMT_FACTORY.makeConsumer(this, "consumerVar3", "consumerVar3", true); + this.__backing_consumerVar4 = STATE_MGMT_FACTORY.makeConsumer(this, "consumerVar4", "consumerVar4", undefined); + this.__backing_consumerVar5 = STATE_MGMT_FACTORY.makeConsumer(this, "consumerVar5", "consumerVar5", null); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_consumerVar1?: IConsumerDecoratedVariable; + + public get consumerVar1(): string { + return this.__backing_consumerVar1!.get(); + } + + public set consumerVar1(value: string) { + this.__backing_consumerVar1!.set(value); + } + + private __backing_consumerVar2?: IConsumerDecoratedVariable; + + public get consumerVar2(): number { + return this.__backing_consumerVar2!.get(); + } + + public set consumerVar2(value: number) { + this.__backing_consumerVar2!.set(value); + } + + private __backing_consumerVar3?: IConsumerDecoratedVariable; + + public get consumerVar3(): boolean { + return this.__backing_consumerVar3!.get(); + } + + public set consumerVar3(value: boolean) { + this.__backing_consumerVar3!.set(value); + } + + private __backing_consumerVar4?: IConsumerDecoratedVariable; + + public get consumerVar4(): undefined { + return this.__backing_consumerVar4!.get(); + } + + public set consumerVar4(value: undefined) { + this.__backing_consumerVar4!.set(value); + } + + private __backing_consumerVar5?: IConsumerDecoratedVariable; + + public get consumerVar5(): null { + return this.__backing_consumerVar5!.get(); + } + + public set consumerVar5(value: null) { + this.__backing_consumerVar5!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set consumerVar1(consumerVar1: (string | undefined)) + + get consumerVar1(): (string | undefined) + set __backing_consumerVar1(__backing_consumerVar1: (IConsumerDecoratedVariable | undefined)) + + get __backing_consumerVar1(): (IConsumerDecoratedVariable | undefined) + set consumerVar2(consumerVar2: (number | undefined)) + + get consumerVar2(): (number | undefined) + set __backing_consumerVar2(__backing_consumerVar2: (IConsumerDecoratedVariable | undefined)) + + get __backing_consumerVar2(): (IConsumerDecoratedVariable | undefined) + set consumerVar3(consumerVar3: (boolean | undefined)) + + get consumerVar3(): (boolean | undefined) + set __backing_consumerVar3(__backing_consumerVar3: (IConsumerDecoratedVariable | undefined)) + + get __backing_consumerVar3(): (IConsumerDecoratedVariable | undefined) + set consumerVar4(consumerVar4: (undefined | undefined)) + + get consumerVar4(): (undefined | undefined) + set __backing_consumerVar4(__backing_consumerVar4: (IConsumerDecoratedVariable | undefined)) + + get __backing_consumerVar4(): (IConsumerDecoratedVariable | undefined) + set consumerVar5(consumerVar5: (null | undefined)) + + get consumerVar5(): (null | undefined) + set __backing_consumerVar5(__backing_consumerVar5: (IConsumerDecoratedVariable | undefined)) + + get __backing_consumerVar5(): (IConsumerDecoratedVariable | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test basic type @Consumer decorated variables transformation', + [parsedTransform, structNoRecheck, recheck], + { + 'checked:struct-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/consumer-complex-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/consumer-complex-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..559a912d3e382bc61456fbe21571365a0f7c7e42 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/consumer-complex-type.test.ts @@ -0,0 +1,351 @@ +/* + * 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 { structNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const STATE_DIR_PATH: string = 'decorators/provider-and-consumer'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'consumer-complex-type.ets'), +]; + +const pluginTester = new PluginTester('test complex type @Consumer decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { IConsumerDecoratedVariable as IConsumerDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Consumer as Consumer } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +class Per { + public num: number; + + public constructor(num: number) { + this.num = num; + } + +} + +final class StateType extends BaseEnum { + private readonly #ordinal: int; + + private static () {} + + public constructor(ordinal: int, value: int) { + super(value); + this.#ordinal = ordinal; + } + + public static readonly TYPE1: StateType = new StateType(0, 0); + + public static readonly TYPE2: StateType = new StateType(1, 1); + + public static readonly TYPE3: StateType = new StateType(2, 3); + + private static readonly #NamesArray: String[] = ["TYPE1", "TYPE2", "TYPE3"]; + + private static readonly #ValuesArray: int[] = [0, 1, 3]; + + private static readonly #StringValuesArray: String[] = ["0", "1", "3"]; + + private static readonly #ItemsArray: StateType[] = [StateType.TYPE1, StateType.TYPE2, StateType.TYPE3]; + + public getName(): String { + return StateType.#NamesArray[this.#ordinal]; + } + + public static getValueOf(name: String): StateType { + for (let i = 0;((i) < (StateType.#NamesArray.length));(++i)) { + if (((name) == (StateType.#NamesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum constant StateType.") + (name))); + } + + public static fromValue(value: int): StateType { + for (let i = 0;((i) < (StateType.#ValuesArray.length));(++i)) { + if (((value) == (StateType.#ValuesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum StateType with value ") + (value))); + } + + public valueOf(): int { + return StateType.#ValuesArray[this.#ordinal]; + } + + public toString(): String { + return StateType.#StringValuesArray[this.#ordinal]; + } + + public static values(): StateType[] { + return StateType.#ItemsArray; + } + + public getOrdinal(): int { + return this.#ordinal; + } + + public static $_get(e: StateType): String { + return e.getName(); + } + +} + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_paramVar1 = STATE_MGMT_FACTORY.makeConsumer(this, "paramVar1", "paramVar1", new Per(6)); + this.__backing_paramVar2 = STATE_MGMT_FACTORY.makeConsumer>(this, "paramVar2", "paramVar2", new Array(3, 6, 8)); + this.__backing_paramVar3 = STATE_MGMT_FACTORY.makeConsumer(this, "paramVar3", "paramVar3", StateType.TYPE3); + this.__backing_paramVar4 = STATE_MGMT_FACTORY.makeConsumer>(this, "paramVar4", "paramVar4", new Set(new Array("aa", "bb"))); + this.__backing_paramVar5 = STATE_MGMT_FACTORY.makeConsumer>(this, "paramVar5", "paramVar5", [true, false]); + this.__backing_paramVar6 = STATE_MGMT_FACTORY.makeConsumer>(this, "paramVar6", "paramVar6", new Array(new Per(7), new Per(11))); + this.__backing_paramVar7 = STATE_MGMT_FACTORY.makeConsumer>(this, "paramVar7", "paramVar7", [new Per(7), new Per(11)]); + this.__backing_paramVar9 = STATE_MGMT_FACTORY.makeConsumer(this, "paramVar9", "paramVar9", new Date("2025-4-23")); + this.__backing_paramVar10 = STATE_MGMT_FACTORY.makeConsumer>(this, "paramVar10", "paramVar10", new Map([[0, new Per(7)], [1, new Per(10)]])); + this.__backing_paramVar11 = STATE_MGMT_FACTORY.makeConsumer<(string | number)>(this, "paramVar11", "paramVar11", 0.0); + this.__backing_paramVar12 = STATE_MGMT_FACTORY.makeConsumer<(Set | Per)>(this, "paramVar12", "paramVar12", new Per(6)); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_paramVar1?: IConsumerDecoratedVariable; + + public get paramVar1(): Per { + return this.__backing_paramVar1!.get(); + } + + public set paramVar1(value: Per) { + this.__backing_paramVar1!.set(value); + } + + private __backing_paramVar2?: IConsumerDecoratedVariable>; + + public get paramVar2(): Array { + return this.__backing_paramVar2!.get(); + } + + public set paramVar2(value: Array) { + this.__backing_paramVar2!.set(value); + } + + private __backing_paramVar3?: IConsumerDecoratedVariable; + + public get paramVar3(): StateType { + return this.__backing_paramVar3!.get(); + } + + public set paramVar3(value: StateType) { + this.__backing_paramVar3!.set(value); + } + + private __backing_paramVar4?: IConsumerDecoratedVariable>; + + public get paramVar4(): Set { + return this.__backing_paramVar4!.get(); + } + + public set paramVar4(value: Set) { + this.__backing_paramVar4!.set(value); + } + + private __backing_paramVar5?: IConsumerDecoratedVariable>; + + public get paramVar5(): Array { + return this.__backing_paramVar5!.get(); + } + + public set paramVar5(value: Array) { + this.__backing_paramVar5!.set(value); + } + + private __backing_paramVar6?: IConsumerDecoratedVariable>; + + public get paramVar6(): Array { + return this.__backing_paramVar6!.get(); + } + + public set paramVar6(value: Array) { + this.__backing_paramVar6!.set(value); + } + + private __backing_paramVar7?: IConsumerDecoratedVariable>; + + public get paramVar7(): Array { + return this.__backing_paramVar7!.get(); + } + + public set paramVar7(value: Array) { + this.__backing_paramVar7!.set(value); + } + + private __backing_paramVar9?: IConsumerDecoratedVariable; + + public get paramVar9(): Date { + return this.__backing_paramVar9!.get(); + } + + public set paramVar9(value: Date) { + this.__backing_paramVar9!.set(value); + } + + private __backing_paramVar10?: IConsumerDecoratedVariable>; + + public get paramVar10(): Map { + return this.__backing_paramVar10!.get(); + } + + public set paramVar10(value: Map) { + this.__backing_paramVar10!.set(value); + } + + private __backing_paramVar11?: IConsumerDecoratedVariable<(string | number)>; + + public get paramVar11(): (string | number) { + return this.__backing_paramVar11!.get(); + } + + public set paramVar11(value: (string | number)) { + this.__backing_paramVar11!.set(value); + } + + private __backing_paramVar12?: IConsumerDecoratedVariable<(Set | Per)>; + + public get paramVar12(): (Set | Per) { + return this.__backing_paramVar12!.get(); + } + + public set paramVar12(value: (Set | Per)) { + this.__backing_paramVar12!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set paramVar1(paramVar1: (Per | undefined)) + + get paramVar1(): (Per | undefined) + set __backing_paramVar1(__backing_paramVar1: (IConsumerDecoratedVariable | undefined)) + + get __backing_paramVar1(): (IConsumerDecoratedVariable | undefined) + set paramVar2(paramVar2: (Array | undefined)) + + get paramVar2(): (Array | undefined) + set __backing_paramVar2(__backing_paramVar2: (IConsumerDecoratedVariable> | undefined)) + + get __backing_paramVar2(): (IConsumerDecoratedVariable> | undefined) + set paramVar3(paramVar3: (StateType | undefined)) + + get paramVar3(): (StateType | undefined) + set __backing_paramVar3(__backing_paramVar3: (IConsumerDecoratedVariable | undefined)) + + get __backing_paramVar3(): (IConsumerDecoratedVariable | undefined) + set paramVar4(paramVar4: (Set | undefined)) + + get paramVar4(): (Set | undefined) + set __backing_paramVar4(__backing_paramVar4: (IConsumerDecoratedVariable> | undefined)) + + get __backing_paramVar4(): (IConsumerDecoratedVariable> | undefined) + set paramVar5(paramVar5: (Array | undefined)) + + get paramVar5(): (Array | undefined) + set __backing_paramVar5(__backing_paramVar5: (IConsumerDecoratedVariable> | undefined)) + + get __backing_paramVar5(): (IConsumerDecoratedVariable> | undefined) + set paramVar6(paramVar6: (Array | undefined)) + + get paramVar6(): (Array | undefined) + set __backing_paramVar6(__backing_paramVar6: (IConsumerDecoratedVariable> | undefined)) + + get __backing_paramVar6(): (IConsumerDecoratedVariable> | undefined) + set paramVar7(paramVar7: (Array | undefined)) + + get paramVar7(): (Array | undefined) + set __backing_paramVar7(__backing_paramVar7: (IConsumerDecoratedVariable> | undefined)) + + get __backing_paramVar7(): (IConsumerDecoratedVariable> | undefined) + set paramVar9(paramVar9: (Date | undefined)) + + get paramVar9(): (Date | undefined) + set __backing_paramVar9(__backing_paramVar9: (IConsumerDecoratedVariable | undefined)) + + get __backing_paramVar9(): (IConsumerDecoratedVariable | undefined) + set paramVar10(paramVar10: (Map | undefined)) + + get paramVar10(): (Map | undefined) + set __backing_paramVar10(__backing_paramVar10: (IConsumerDecoratedVariable> | undefined)) + + get __backing_paramVar10(): (IConsumerDecoratedVariable> | undefined) + set paramVar11(paramVar11: ((string | number) | undefined)) + + get paramVar11(): ((string | number) | undefined) + set __backing_paramVar11(__backing_paramVar11: (IConsumerDecoratedVariable<(string | number)> | undefined)) + + get __backing_paramVar11(): (IConsumerDecoratedVariable<(string | number)> | undefined) + set paramVar12(paramVar12: ((Set | Per) | undefined)) + + get paramVar12(): ((Set | Per) | undefined) + set __backing_paramVar12(__backing_paramVar12: (IConsumerDecoratedVariable<(Set | Per)> | undefined)) + + get __backing_paramVar12(): (IConsumerDecoratedVariable<(Set | Per)> | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test complex type @Consumer decorated variables transformation', + [parsedTransform, structNoRecheck, recheck], + { + 'checked:struct-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-basic-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-basic-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..4727c0342ca2b0e9cb759c181b1119986235b63e --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-basic-type.test.ts @@ -0,0 +1,172 @@ +/* + * 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 { structNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const STATE_DIR_PATH: string = 'decorators/provider-and-consumer'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'provider-basic-type.ets'), +]; + +const pluginTester = new PluginTester('test basic type @Provider decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { IProviderDecoratedVariable as IProviderDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Provider as Provider } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_providerVar1 = STATE_MGMT_FACTORY.makeProvider(this, "providerVar1", "providerVar1", "propVar1"); + this.__backing_providerVar2 = STATE_MGMT_FACTORY.makeProvider(this, "providerVar2", "providerVar2", 50); + this.__backing_providerVar3 = STATE_MGMT_FACTORY.makeProvider(this, "providerVar3", "providerVar3", true); + this.__backing_providerVar4 = STATE_MGMT_FACTORY.makeProvider(this, "providerVar4", "providerVar4", undefined); + this.__backing_providerVar5 = STATE_MGMT_FACTORY.makeProvider(this, "providerVar5", "providerVar5", null); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_providerVar1?: IProviderDecoratedVariable; + + public get providerVar1(): string { + return this.__backing_providerVar1!.get(); + } + + public set providerVar1(value: string) { + this.__backing_providerVar1!.set(value); + } + + private __backing_providerVar2?: IProviderDecoratedVariable; + + public get providerVar2(): number { + return this.__backing_providerVar2!.get(); + } + + public set providerVar2(value: number) { + this.__backing_providerVar2!.set(value); + } + + private __backing_providerVar3?: IProviderDecoratedVariable; + + public get providerVar3(): boolean { + return this.__backing_providerVar3!.get(); + } + + public set providerVar3(value: boolean) { + this.__backing_providerVar3!.set(value); + } + + private __backing_providerVar4?: IProviderDecoratedVariable; + + public get providerVar4(): undefined { + return this.__backing_providerVar4!.get(); + } + + public set providerVar4(value: undefined) { + this.__backing_providerVar4!.set(value); + } + + private __backing_providerVar5?: IProviderDecoratedVariable; + + public get providerVar5(): null { + return this.__backing_providerVar5!.get(); + } + + public set providerVar5(value: null) { + this.__backing_providerVar5!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set providerVar1(providerVar1: (string | undefined)) + + get providerVar1(): (string | undefined) + set __backing_providerVar1(__backing_providerVar1: (IProviderDecoratedVariable | undefined)) + + get __backing_providerVar1(): (IProviderDecoratedVariable | undefined) + set providerVar2(providerVar2: (number | undefined)) + + get providerVar2(): (number | undefined) + set __backing_providerVar2(__backing_providerVar2: (IProviderDecoratedVariable | undefined)) + + get __backing_providerVar2(): (IProviderDecoratedVariable | undefined) + set providerVar3(providerVar3: (boolean | undefined)) + + get providerVar3(): (boolean | undefined) + set __backing_providerVar3(__backing_providerVar3: (IProviderDecoratedVariable | undefined)) + + get __backing_providerVar3(): (IProviderDecoratedVariable | undefined) + set providerVar4(providerVar4: (undefined | undefined)) + + get providerVar4(): (undefined | undefined) + set __backing_providerVar4(__backing_providerVar4: (IProviderDecoratedVariable | undefined)) + + get __backing_providerVar4(): (IProviderDecoratedVariable | undefined) + set providerVar5(providerVar5: (null | undefined)) + + get providerVar5(): (null | undefined) + set __backing_providerVar5(__backing_providerVar5: (IProviderDecoratedVariable | undefined)) + + get __backing_providerVar5(): (IProviderDecoratedVariable | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test basic type @Provider decorated variables transformation', + [parsedTransform, structNoRecheck, recheck], + { + 'checked:struct-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-complex-type.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-complex-type.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..8365360d0eb2f64cb1b1af3afcb77772658176c4 --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-complex-type.test.ts @@ -0,0 +1,351 @@ +/* + * 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 { structNoRecheck, recheck } from '../../../../utils/plugins'; +import { BuildConfig, PluginTestContext } from '../../../../utils/shared-types'; +import { uiTransform } from '../../../../../ui-plugins'; +import { Plugins } from '../../../../../common/plugin-context'; + +const STATE_DIR_PATH: string = 'decorators/provider-and-consumer'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'provider-complex-type.ets'), +]; + +const pluginTester = new PluginTester('test complex type @Provider decorated variables transformation', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { IProviderDecoratedVariable as IProviderDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2 } from "@ohos.arkui.component"; + +import { Provider as Provider } from "@ohos.arkui.stateManagement"; + +function main() {} + + + +class Per { + public num: number; + + public constructor(num: number) { + this.num = num; + } + +} + +final class StateType extends BaseEnum { + private readonly #ordinal: int; + + private static () {} + + public constructor(ordinal: int, value: int) { + super(value); + this.#ordinal = ordinal; + } + + public static readonly TYPE1: StateType = new StateType(0, 0); + + public static readonly TYPE2: StateType = new StateType(1, 1); + + public static readonly TYPE3: StateType = new StateType(2, 3); + + private static readonly #NamesArray: String[] = ["TYPE1", "TYPE2", "TYPE3"]; + + private static readonly #ValuesArray: int[] = [0, 1, 3]; + + private static readonly #StringValuesArray: String[] = ["0", "1", "3"]; + + private static readonly #ItemsArray: StateType[] = [StateType.TYPE1, StateType.TYPE2, StateType.TYPE3]; + + public getName(): String { + return StateType.#NamesArray[this.#ordinal]; + } + + public static getValueOf(name: String): StateType { + for (let i = 0;((i) < (StateType.#NamesArray.length));(++i)) { + if (((name) == (StateType.#NamesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum constant StateType.") + (name))); + } + + public static fromValue(value: int): StateType { + for (let i = 0;((i) < (StateType.#ValuesArray.length));(++i)) { + if (((value) == (StateType.#ValuesArray[i]))) { + return StateType.#ItemsArray[i]; + } + } + throw new Error((("No enum StateType with value ") + (value))); + } + + public valueOf(): int { + return StateType.#ValuesArray[this.#ordinal]; + } + + public toString(): String { + return StateType.#StringValuesArray[this.#ordinal]; + } + + public static values(): StateType[] { + return StateType.#ItemsArray; + } + + public getOrdinal(): int { + return this.#ordinal; + } + + public static $_get(e: StateType): String { + return e.getName(); + } + +} + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_paramVar1 = STATE_MGMT_FACTORY.makeProvider(this, "paramVar1", "paramVar1", new Per(6)); + this.__backing_paramVar2 = STATE_MGMT_FACTORY.makeProvider>(this, "paramVar2", "paramVar2", new Array(3, 6, 8)); + this.__backing_paramVar3 = STATE_MGMT_FACTORY.makeProvider(this, "paramVar3", "paramVar3", StateType.TYPE3); + this.__backing_paramVar4 = STATE_MGMT_FACTORY.makeProvider>(this, "paramVar4", "paramVar4", new Set(new Array("aa", "bb"))); + this.__backing_paramVar5 = STATE_MGMT_FACTORY.makeProvider>(this, "paramVar5", "paramVar5", [true, false]); + this.__backing_paramVar6 = STATE_MGMT_FACTORY.makeProvider>(this, "paramVar6", "paramVar6", new Array(new Per(7), new Per(11))); + this.__backing_paramVar7 = STATE_MGMT_FACTORY.makeProvider>(this, "paramVar7", "paramVar7", [new Per(7), new Per(11)]); + this.__backing_paramVar9 = STATE_MGMT_FACTORY.makeProvider(this, "paramVar9", "paramVar9", new Date("2025-4-23")); + this.__backing_paramVar10 = STATE_MGMT_FACTORY.makeProvider>(this, "paramVar10", "paramVar10", new Map([[0, new Per(7)], [1, new Per(10)]])); + this.__backing_paramVar11 = STATE_MGMT_FACTORY.makeProvider<(string | number)>(this, "paramVar11", "paramVar11", 0.0); + this.__backing_paramVar12 = STATE_MGMT_FACTORY.makeProvider<(Set | Per)>(this, "paramVar12", "paramVar12", new Per(6)); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_paramVar1?: IProviderDecoratedVariable; + + public get paramVar1(): Per { + return this.__backing_paramVar1!.get(); + } + + public set paramVar1(value: Per) { + this.__backing_paramVar1!.set(value); + } + + private __backing_paramVar2?: IProviderDecoratedVariable>; + + public get paramVar2(): Array { + return this.__backing_paramVar2!.get(); + } + + public set paramVar2(value: Array) { + this.__backing_paramVar2!.set(value); + } + + private __backing_paramVar3?: IProviderDecoratedVariable; + + public get paramVar3(): StateType { + return this.__backing_paramVar3!.get(); + } + + public set paramVar3(value: StateType) { + this.__backing_paramVar3!.set(value); + } + + private __backing_paramVar4?: IProviderDecoratedVariable>; + + public get paramVar4(): Set { + return this.__backing_paramVar4!.get(); + } + + public set paramVar4(value: Set) { + this.__backing_paramVar4!.set(value); + } + + private __backing_paramVar5?: IProviderDecoratedVariable>; + + public get paramVar5(): Array { + return this.__backing_paramVar5!.get(); + } + + public set paramVar5(value: Array) { + this.__backing_paramVar5!.set(value); + } + + private __backing_paramVar6?: IProviderDecoratedVariable>; + + public get paramVar6(): Array { + return this.__backing_paramVar6!.get(); + } + + public set paramVar6(value: Array) { + this.__backing_paramVar6!.set(value); + } + + private __backing_paramVar7?: IProviderDecoratedVariable>; + + public get paramVar7(): Array { + return this.__backing_paramVar7!.get(); + } + + public set paramVar7(value: Array) { + this.__backing_paramVar7!.set(value); + } + + private __backing_paramVar9?: IProviderDecoratedVariable; + + public get paramVar9(): Date { + return this.__backing_paramVar9!.get(); + } + + public set paramVar9(value: Date) { + this.__backing_paramVar9!.set(value); + } + + private __backing_paramVar10?: IProviderDecoratedVariable>; + + public get paramVar10(): Map { + return this.__backing_paramVar10!.get(); + } + + public set paramVar10(value: Map) { + this.__backing_paramVar10!.set(value); + } + + private __backing_paramVar11?: IProviderDecoratedVariable<(string | number)>; + + public get paramVar11(): (string | number) { + return this.__backing_paramVar11!.get(); + } + + public set paramVar11(value: (string | number)) { + this.__backing_paramVar11!.set(value); + } + + private __backing_paramVar12?: IProviderDecoratedVariable<(Set | Per)>; + + public get paramVar12(): (Set | Per) { + return this.__backing_paramVar12!.get(); + } + + public set paramVar12(value: (Set | Per)) { + this.__backing_paramVar12!.set(value); + } + + @memo() public build() {} + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set paramVar1(paramVar1: (Per | undefined)) + + get paramVar1(): (Per | undefined) + set __backing_paramVar1(__backing_paramVar1: (IProviderDecoratedVariable | undefined)) + + get __backing_paramVar1(): (IProviderDecoratedVariable | undefined) + set paramVar2(paramVar2: (Array | undefined)) + + get paramVar2(): (Array | undefined) + set __backing_paramVar2(__backing_paramVar2: (IProviderDecoratedVariable> | undefined)) + + get __backing_paramVar2(): (IProviderDecoratedVariable> | undefined) + set paramVar3(paramVar3: (StateType | undefined)) + + get paramVar3(): (StateType | undefined) + set __backing_paramVar3(__backing_paramVar3: (IProviderDecoratedVariable | undefined)) + + get __backing_paramVar3(): (IProviderDecoratedVariable | undefined) + set paramVar4(paramVar4: (Set | undefined)) + + get paramVar4(): (Set | undefined) + set __backing_paramVar4(__backing_paramVar4: (IProviderDecoratedVariable> | undefined)) + + get __backing_paramVar4(): (IProviderDecoratedVariable> | undefined) + set paramVar5(paramVar5: (Array | undefined)) + + get paramVar5(): (Array | undefined) + set __backing_paramVar5(__backing_paramVar5: (IProviderDecoratedVariable> | undefined)) + + get __backing_paramVar5(): (IProviderDecoratedVariable> | undefined) + set paramVar6(paramVar6: (Array | undefined)) + + get paramVar6(): (Array | undefined) + set __backing_paramVar6(__backing_paramVar6: (IProviderDecoratedVariable> | undefined)) + + get __backing_paramVar6(): (IProviderDecoratedVariable> | undefined) + set paramVar7(paramVar7: (Array | undefined)) + + get paramVar7(): (Array | undefined) + set __backing_paramVar7(__backing_paramVar7: (IProviderDecoratedVariable> | undefined)) + + get __backing_paramVar7(): (IProviderDecoratedVariable> | undefined) + set paramVar9(paramVar9: (Date | undefined)) + + get paramVar9(): (Date | undefined) + set __backing_paramVar9(__backing_paramVar9: (IProviderDecoratedVariable | undefined)) + + get __backing_paramVar9(): (IProviderDecoratedVariable | undefined) + set paramVar10(paramVar10: (Map | undefined)) + + get paramVar10(): (Map | undefined) + set __backing_paramVar10(__backing_paramVar10: (IProviderDecoratedVariable> | undefined)) + + get __backing_paramVar10(): (IProviderDecoratedVariable> | undefined) + set paramVar11(paramVar11: ((string | number) | undefined)) + + get paramVar11(): ((string | number) | undefined) + set __backing_paramVar11(__backing_paramVar11: (IProviderDecoratedVariable<(string | number)> | undefined)) + + get __backing_paramVar11(): (IProviderDecoratedVariable<(string | number)> | undefined) + set paramVar12(paramVar12: ((Set | Per) | undefined)) + + get paramVar12(): ((Set | Per) | undefined) + set __backing_paramVar12(__backing_paramVar12: (IProviderDecoratedVariable<(Set | Per)> | undefined)) + + get __backing_paramVar12(): (IProviderDecoratedVariable<(Set | Per)> | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test complex type @Provider decorated variables transformation', + [parsedTransform, structNoRecheck, recheck], + { + 'checked:struct-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-to-consumer.test.ts b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-to-consumer.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d6e40550d2e43a4eac62bed3c970e85cea5f203d --- /dev/null +++ b/arkui-plugins/test/ut/ui-plugins/decorators/provider-and-consumer/provider-to-consumer.test.ts @@ -0,0 +1,261 @@ +/* + * 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 STATE_DIR_PATH: string = 'decorators/provider-and-consumer'; + +const buildConfig: BuildConfig = mockBuildConfig(); +buildConfig.compileFiles = [ + path.resolve(getRootPath(), MOCK_ENTRY_DIR_PATH, STATE_DIR_PATH, 'provider-to-consumer.ets'), +]; + +const pluginTester = new PluginTester('test usage of @Provider and @Consumer decorator', buildConfig); + +const parsedTransform: Plugins = { + name: 'parsedTrans', + parsed: uiTransform().parsed +}; + +const expectedCheckedScript: string = ` +import { IConsumerDecoratedVariable as IConsumerDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { TextAttribute as TextAttribute } from "arkui.component.text"; + +import { IProviderDecoratedVariable as IProviderDecoratedVariable } from "arkui.stateManagement.decorator"; + +import { memo as memo } from "arkui.stateManagement.runtime"; + +import { ButtonAttribute as ButtonAttribute } from "arkui.component.button"; + +import { IObservedObject as IObservedObject } from "arkui.stateManagement.decorator"; + +import { UIUtils as UIUtils } from "arkui.stateManagement.utils"; + +import { IMutableStateMeta as IMutableStateMeta } from "arkui.stateManagement.decorator"; + +import { RenderIdType as RenderIdType } from "arkui.stateManagement.decorator"; + +import { WatchIdType as WatchIdType } from "arkui.stateManagement.decorator"; + +import { ISubscribedWatches as ISubscribedWatches } from "arkui.stateManagement.decorator"; + +import { STATE_MGMT_FACTORY as STATE_MGMT_FACTORY } from "arkui.stateManagement.decorator"; + +import { CustomComponentV2 as CustomComponentV2 } from "arkui.component.customComponent"; + +import { ComponentV2 as ComponentV2, DragEvent as DragEvent, Button as Button, Column as Column, Text as Text, ForEach as ForEach, Divider as Divider } from "@ohos.arkui.component"; + +import { Provider as Provider, Consumer as Consumer, Local as Local, ObservedV2 as ObservedV2, Trace as Trace } from "@ohos.arkui.stateManagement"; + +const data: Array = [new User("Json", 10), new User("Eric", 15)]; + +function main() {} + + + +@ObservedV2() class User implements IObservedObject, ISubscribedWatches { + @JSONStringifyIgnore() private subscribedWatches: ISubscribedWatches = STATE_MGMT_FACTORY.makeSubscribedWatches(); + + public addWatchSubscriber(watchId: WatchIdType): void { + this.subscribedWatches.addWatchSubscriber(watchId); + } + + public removeWatchSubscriber(watchId: WatchIdType): boolean { + return this.subscribedWatches.removeWatchSubscriber(watchId); + } + + public executeOnSubscribingWatches(propertyName: string): void { + this.subscribedWatches.executeOnSubscribingWatches(propertyName); + } + + public setV1RenderId(renderId: RenderIdType): void {} + + protected conditionalAddRef(meta: IMutableStateMeta): void { + meta.addRef(); + } + + @JSONRename({newName:"name"}) private __backing_name?: string; + + @JSONStringifyIgnore() private __meta_name: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + @JSONRename({newName:"age"}) private __backing_age?: number; + + @JSONStringifyIgnore() private __meta_age: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); + + public get name(): string { + this.conditionalAddRef(this.__meta_name); + return UIUtils.makeObserved((this.__backing_name as string)); + } + + public set name(newValue: string) { + if (((this.__backing_name) !== (newValue))) { + this.__backing_name = newValue; + this.__meta_name.fireChange(); + this.executeOnSubscribingWatches("name"); + } + } + + public get age(): number { + this.conditionalAddRef(this.__meta_age); + return UIUtils.makeObserved((this.__backing_age as number)); + } + + public set age(newValue: number) { + if (((this.__backing_age) !== (newValue))) { + this.__backing_age = newValue; + this.__meta_age.fireChange(); + this.executeOnSubscribingWatches("age"); + } + } + + public constructor(name: string, age: number) { + this.name = name; + this.age = age; + } + +} + +@ComponentV2() final struct Parent extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Parent | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_users = STATE_MGMT_FACTORY.makeProvider>(this, "users", "data", data); + } + + public __updateStruct(initializers: (__Options_Parent | undefined)): void {} + + private __backing_users?: IProviderDecoratedVariable>; + + public get users(): Array { + return this.__backing_users!.get(); + } + + public set users(value: Array) { + this.__backing_users!.set(value); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + Child._instantiateImpl(undefined, (() => { + return new Child(); + }), undefined, undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.users.push(new User("Molly", 18)); + })); + return; + }), "add new user", undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + (this.users[0].age++); + })); + return; + }), "age++", undefined, undefined); + Button(@memo() ((instance: ButtonAttribute): void => { + instance.onClick(((e) => { + this.users[0].name = "Shelly"; + })); + return; + }), "change name", undefined, undefined); + })); + } + + private constructor() {} + +} + +@ComponentV2() final struct Child extends CustomComponentV2 { + public __initializeStruct(initializers: (__Options_Child | undefined), @memo() content: ((()=> void) | undefined)): void { + this.__backing_users = STATE_MGMT_FACTORY.makeConsumer>(this, "users", "data", []); + } + + public __updateStruct(initializers: (__Options_Child | undefined)): void {} + + private __backing_users?: IConsumerDecoratedVariable>; + + public get users(): Array { + return this.__backing_users!.get(); + } + + public set users(value: Array) { + this.__backing_users!.set(value); + } + + @memo() public build() { + Column(undefined, undefined, @memo() (() => { + ForEach(((): Array => { + return this.users; + }), ((item: User) => { + Column(undefined, undefined, @memo() (() => { + Text(@memo() ((instance: TextAttribute): void => { + instance.fontSize(30); + return; + }), \`name: \${item.name}\`, undefined, undefined); + Text(@memo() ((instance: TextAttribute): void => { + instance.fontSize(30); + return; + }), \`age: \${item.age}\`, undefined, undefined); + Divider(undefined); + })); + })); + })); + } + + private constructor() {} + +} + +@ComponentV2() export interface __Options_Parent { + set users(users: (Array | undefined)) + + get users(): (Array | undefined) + set __backing_users(__backing_users: (IProviderDecoratedVariable> | undefined)) + + get __backing_users(): (IProviderDecoratedVariable> | undefined) + +} + +@ComponentV2() export interface __Options_Child { + set users(users: (Array | undefined)) + + get users(): (Array | undefined) + set __backing_users(__backing_users: (IConsumerDecoratedVariable> | undefined)) + + get __backing_users(): (IConsumerDecoratedVariable> | undefined) + +} +`; + +function testCheckedTransformer(this: PluginTestContext): void { + expect(parseDumpSrc(this.scriptSnapshot ?? '')).toBe(parseDumpSrc(expectedCheckedScript)); +} + +pluginTester.run( + 'test usage of @Provider and @Consumer decorator', + [parsedTransform, uiNoRecheck, recheck], + { + 'checked:ui-no-recheck': [testCheckedTransformer], + }, + { + stopAfter: 'checked', + } +); diff --git a/arkui-plugins/ui-plugins/component-transformer.ts b/arkui-plugins/ui-plugins/component-transformer.ts index 83661d6876a8d2d26568e1492f0f598f9943bdf3..835cb283e407bfff9cf445944a96dab7bc51a035 100644 --- a/arkui-plugins/ui-plugins/component-transformer.ts +++ b/arkui-plugins/ui-plugins/component-transformer.ts @@ -401,7 +401,9 @@ export class ComponentTransformer extends AbstractVisitor { ), [ ...newDefinitionBody, - ...definition.body.map((st: arkts.AstNode) => factory.PreprocessClassPropertyModifier(st, scopeInfo.isDecl)), + ...definition.body.map((st: arkts.AstNode) => + factory.PreprocessClassPropertyModifier(st, scopeInfo.isDecl) + ), ...staticMethodBody, ], definition.modifiers, @@ -459,17 +461,20 @@ export class ComponentTransformer extends AbstractVisitor { originMember.setAnnotations([buildParamInfo.annotation.clone()]); return [originMember]; } + const OnceInfo = infos.find((it) => it.name === DecoratorNames.ONCE); const targetInfo = infos.find((it) => DECORATOR_TYPE_MAP.has(it.name)); if (!!targetInfo) { const newName: string = backingField(originalName); - const newMember: arkts.ClassProperty = propertyFactory - .createOptionalClassProperty( - newName, - member, - undefined, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC - ) - .setAnnotations([targetInfo.annotation.clone()]); + const newMember: arkts.ClassProperty = propertyFactory.createOptionalClassProperty( + newName, + member, + undefined, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC + ); + const annos = !!OnceInfo + ? [OnceInfo.annotation.clone(), targetInfo.annotation.clone()] + : [targetInfo.annotation.clone()]; + newMember.setAnnotations(annos); if (isDecoratorAnnotation(targetInfo.annotation, DecoratorNames.LINK, true)) { this.shouldAddLinkIntrinsic = true; originMember.setAnnotations([annotation(DecoratorIntrinsicNames.LINK)]); diff --git a/arkui-plugins/ui-plugins/property-translators/base.ts b/arkui-plugins/ui-plugins/property-translators/base.ts index a553aaa7f79d95ae4714a6235e5d69d0701cbdb9..ba0c0bd4119624e87d781fed8c5a81e6eeca60f8 100644 --- a/arkui-plugins/ui-plugins/property-translators/base.ts +++ b/arkui-plugins/ui-plugins/property-translators/base.ts @@ -14,13 +14,10 @@ */ import * as arkts from '@koalaui/libarkts'; -import { - collectStateManagementTypeImport, - createGetter, - createSetter, -} from './utils'; -import { CustomComponentInfo } from '../utils'; +import { collectStateManagementTypeImport, createGetter, createSetter } from './utils'; +import { CustomComponentInfo, ClassInfo } from '../utils'; import { StateManagementTypes } from '../../common/predefines'; +import { ClassScopeInfo } from '../struct-translators/utils'; export interface PropertyTranslatorOptions { property: arkts.ClassProperty; @@ -61,6 +58,31 @@ export abstract class PropertyTranslator { } } +export abstract class MethodTranslator { + protected method: arkts.MethodDefinition; + protected classInfo: ClassInfo; + + constructor(method: arkts.MethodDefinition, classInfo: ClassInfo) { + this.method = method; + this.classInfo = classInfo; + } + + abstract translateMember(): arkts.AstNode[]; +} + +export abstract class ObservedPropertyTranslator { + protected property: arkts.ClassProperty; + protected classScopeInfo: ClassScopeInfo; + + constructor(property: arkts.ClassProperty, classScopeInfo: ClassScopeInfo) { + this.property = property; + this.classScopeInfo = classScopeInfo; + } + + abstract translateMember(): arkts.AstNode[]; + abstract createField(originalName: string, newName: string): arkts.ClassProperty[]; +} + export type InterfacePropertyTypes = arkts.MethodDefinition | arkts.ClassProperty; export interface InterfacePropertyTranslatorOptions { diff --git a/arkui-plugins/ui-plugins/property-translators/computed.ts b/arkui-plugins/ui-plugins/property-translators/computed.ts new file mode 100644 index 0000000000000000000000000000000000000000..82daf847d2b2442ed57053ed0641c67ded719a28 --- /dev/null +++ b/arkui-plugins/ui-plugins/property-translators/computed.ts @@ -0,0 +1,75 @@ +/* + * 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 arkts from '@koalaui/libarkts'; + +import { expectName } from '../../common/arkts-utils'; +import { GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { computedField } from '../utils'; +import { generateThisBacking, PropertyCache, generateGetOrSetCall, collectStateManagementTypeImport } from './utils'; +import { MethodTranslator } from './base'; +import { InitializerConstructor } from './types'; +import { factory as UIFactory } from '../ui-factory'; +import { factory } from './factory'; + +export class ComputedTranslator extends MethodTranslator implements InitializerConstructor { + translateMember(): arkts.AstNode[] { + const originalName: string = expectName(this.method.name); + const newName: string = computedField(originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string): void {} + + translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { + const field: arkts.ClassProperty = arkts.factory.createClassProperty( + arkts.factory.createIdentifier(newName), + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_COMPUTED, + this.method.scriptFunction.returnTypeAnnotation?.clone(), + [ + arkts.factory.createArrowFunction( + UIFactory.createScriptFunction({ + body: this.method.scriptFunction.body?.clone(), + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + }) + ), + arkts.factory.createStringLiteral(originalName), + ], + false + ), + undefined, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + + const originGetter: arkts.MethodDefinition = UIFactory.updateMethodDefinition(this.method, { + function: { + returnTypeAnnotation: this.method.scriptFunction.returnTypeAnnotation?.clone(), + body: arkts.factory.createBlock([ + arkts.factory.createReturnStatement(this.generateComputedGet(newName)), + ]), + }, + }); + + return [field, originGetter]; + } + + generateComputedGet(newName: string): arkts.CallExpression { + const thisValue: arkts.Expression = generateThisBacking(newName, false, true); + return generateGetOrSetCall(thisValue, GetSetTypes.GET); + } +} diff --git a/arkui-plugins/ui-plugins/property-translators/consumer.ts b/arkui-plugins/ui-plugins/property-translators/consumer.ts new file mode 100644 index 0000000000000000000000000000000000000000..368e45ccd7ef4ea9a342828d3e5a78fafc57c541 --- /dev/null +++ b/arkui-plugins/ui-plugins/property-translators/consumer.ts @@ -0,0 +1,150 @@ +/* + * 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 arkts from '@koalaui/libarkts'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + getValueInAnnotation, + hasDecorator, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class ConsumerTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): arkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: arkts.AstNode = this.generateInitializeStruct(originalName, newName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + } + + translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { + const field: arkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.CONSUMER_DECORATED, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + 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 getter: arkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: arkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + returnValue: arkts.Expression + ): arkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + statement: arkts.AstNode + ): arkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } + + generateInitializeStruct(originalName: string, newName: string): arkts.AstNode { + const args: arkts.Expression[] = [ + arkts.factory.create1StringLiteral(originalName), + arkts.factory.create1StringLiteral( + getValueInAnnotation(this.property, DecoratorNames.CONSUMER) ?? originalName + ), + this.property.value! + ]; + const assign: arkts.AssignmentExpression = arkts.factory.createAssignmentExpression( + generateThisBacking(newName), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_CONSUMER, + this.property.typeAnnotation, + args, + true + ) + ); + return arkts.factory.createExpressionStatement(assign); + } +} + +export class ConsumerInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (arkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (arkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: arkts.AstNode): node is InterfacePropertyTypes { + if (arkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.CONSUMER)) { + return true; + } else if (arkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.CONSUMER)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `IConsumerDecoratedVariable | undefined`. + * + * @param method expecting getter with `@Consumer` and a setter with `@Consumer` in the overloads. + */ + private updateStateMethodInInterface(method: arkts.MethodDefinition): arkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.CONSUMER); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `IConsumerDecoratedVariable | undefined`. + * + * @param property expecting property with `@Consumer`. + */ + private updateStatePropertyInInterface(property: arkts.ClassProperty): arkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.CONSUMER); + } +} diff --git a/arkui-plugins/ui-plugins/property-translators/factory.ts b/arkui-plugins/ui-plugins/property-translators/factory.ts index 1843bc77a44c42f5e4fa0a7fc1949d5c417c6a12..af331b60fb79df1fbaf80240de114fdc186d419c 100644 --- a/arkui-plugins/ui-plugins/property-translators/factory.ts +++ b/arkui-plugins/ui-plugins/property-translators/factory.ts @@ -15,12 +15,26 @@ import * as arkts from '@koalaui/libarkts'; import { GenSymGenerator } from '../../common/gensym-generator'; -import { DecoratorNames, DECORATOR_TYPE_MAP, StateManagementTypes } from '../../common/predefines'; +import { + DecoratorNames, + DECORATOR_TYPE_MAP, + StateManagementTypes, + ObservedNames, + MonitorNames, + TypeNames, +} from '../../common/predefines'; import { factory as UIFactory } from '../ui-factory'; -import { collectStateManagementTypeImport, getValueInAnnotation, hasDecorator, removeDecorator, generateThisBacking } from './utils'; +import { + collectStateManagementTypeImport, + generateThisBacking, + getValueInAnnotation, + hasDecorator, + OptionalMemberInfo, + removeDecorator, +} from './utils'; import { CustomComponentNames } from '../utils'; import { addMemoAnnotation, findCanAddMemoFromTypeAnnotation } from '../../collectors/memo-collectors/utils'; -import { annotation } from '../../common/arkts-utils'; +import { annotation, isNumeric } from '../../common/arkts-utils'; export class factory { /** @@ -28,16 +42,17 @@ export class factory { * * @param object item before ?.. * @param key item after ?.. + * @param info optional member information */ static createBlockStatementForOptionalExpression( object: arkts.AstNode, key: string, - isCall: boolean = false + info?: OptionalMemberInfo ): arkts.Expression { let id = GenSymGenerator.getInstance().id(key); const statements: arkts.Statement[] = [ factory.generateLetVariableDecl(arkts.factory.createIdentifier(id), object), - factory.generateTernaryExpression(id, key, isCall), + factory.generateTernaryExpression(id, key, info), ]; return arkts.factory.createBlockExpression(statements); } @@ -71,7 +86,7 @@ export class factory { static generateTernaryExpression( testLeft: string, key: string, - isCall: boolean = false + info?: OptionalMemberInfo ): arkts.ExpressionStatement { const test = arkts.factory.createBinaryExpression( arkts.factory.createIdentifier(testLeft), @@ -79,22 +94,30 @@ export class factory { arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_EQUAL ); const consequent: arkts.Expression = arkts.factory.createUndefinedLiteral(); - const alternate: arkts.MemberExpression = arkts.factory.createMemberExpression( - arkts.factory.createIdentifier(testLeft), - arkts.factory.createIdentifier(key), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ); return arkts.factory.createExpressionStatement( arkts.factory.createConditionalExpression( test, consequent, - isCall ? arkts.factory.createCallExpression(alternate, undefined, undefined) : alternate + this.generateConditionalAlternate(testLeft, key, info) ) ); } + static generateConditionalAlternate(testLeft: string, key: string, info?: OptionalMemberInfo): arkts.Expression { + const leftIdent: arkts.Identifier = arkts.factory.createIdentifier(testLeft); + const alternate: arkts.MemberExpression = UIFactory.generateMemberExpression( + leftIdent, + info?.isNumeric ? '$_get' : key + ); + return info?.isCall + ? arkts.factory.createCallExpression(alternate, undefined, undefined) + : info?.isNumeric + ? arkts.factory.createCallExpression(alternate, undefined, [ + arkts.factory.createNumericLiteral(Number(key)), + ]) + : alternate; + } + /** * generate an substitution for two optional expression ?., e.g. a?.b?.c. * @@ -233,12 +256,9 @@ export class factory { ): arkts.CallExpression { collectStateManagementTypeImport(StateManagementTypes.STATE_MANAGEMENT_FACTORY); return arkts.factory.createCallExpression( - arkts.factory.createMemberExpression( + UIFactory.generateMemberExpression( arkts.factory.createIdentifier(StateManagementTypes.STATE_MANAGEMENT_FACTORY), - arkts.factory.createIdentifier(makeType), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false + makeType ), typeArguments ? [typeArguments] : undefined, [...(argsContainsThis ? [arkts.factory.createThisExpression()] : []), ...args] @@ -436,9 +456,9 @@ export class factory { /* * create ____V1RenderId related members in Observed/Track classes */ - static createV1RenderIdMembers(): arkts.AstNode[] { + static createV1RenderIdMembers(isObservedV2: boolean): arkts.AstNode[] { const v1RenderId: arkts.ClassProperty = arkts.factory.createClassProperty( - arkts.factory.createIdentifier('____V1RenderId'), + arkts.factory.createIdentifier(ObservedNames.V1_RERENDER_ID), arkts.factory.createNumericLiteral(0), arkts.factory.createTypeReference( arkts.factory.createTypeReferencePart( @@ -450,136 +470,98 @@ export class factory { ); v1RenderId.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); collectStateManagementTypeImport(StateManagementTypes.RENDER_ID_TYPE); - const setV1RenderId: arkts.MethodDefinition = factory.setV1RenderId(); - return [v1RenderId, setV1RenderId]; + const setV1RenderId: arkts.MethodDefinition = factory.setV1RenderId(isObservedV2); + return isObservedV2 ? [setV1RenderId] : [v1RenderId, setV1RenderId]; } /* * helper for createV1RenderIdMembers to generate setV1RenderId method */ - static setV1RenderId(): arkts.MethodDefinition { + static setV1RenderId(isObservedV2: boolean): arkts.MethodDefinition { const assignRenderId: arkts.ExpressionStatement = arkts.factory.createExpressionStatement( arkts.factory.createAssignmentExpression( - arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier('____V1RenderId'), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ), + generateThisBacking(ObservedNames.V1_RERENDER_ID), arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, - arkts.factory.createIdentifier('renderId') + arkts.factory.createIdentifier(ObservedNames.RERENDER_ID) ) ); - const funcSig: arkts.FunctionSignature = arkts.factory.createFunctionSignature( - undefined, - [ - arkts.factory.createParameterDeclaration( - arkts.factory.createIdentifier( - 'renderId', - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart( - arkts.factory.createIdentifier(StateManagementTypes.RENDER_ID_TYPE) - ) - ) + return UIFactory.createMethodDefinition({ + key: arkts.factory.createIdentifier(ObservedNames.SET_V1_RERENDER_ID), + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + function: { + body: arkts.factory.createBlock(isObservedV2 ? [] : [assignRenderId]), + params: [ + arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier( + ObservedNames.RERENDER_ID, + UIFactory.createTypeReferenceFromString(StateManagementTypes.RENDER_ID_TYPE) + ), + undefined ), - undefined + ], + returnTypeAnnotation: arkts.factory.createPrimitiveType( + arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID ), - ], - arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), - false - ); - return arkts.factory.createMethodDefinition( - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, - arkts.factory.createIdentifier('setV1RenderId'), - arkts.factory.createScriptFunction( - arkts.factory.createBlock([assignRenderId]), - funcSig, - arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC - ), - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - false - ); + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + }, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + }); } /* * create conditionalAddRef method in Observed/Track classes */ - static conditionalAddRef(): arkts.MethodDefinition { - const funcSig: arkts.FunctionSignature = arkts.factory.createFunctionSignature( - undefined, - [ - arkts.factory.createParameterDeclaration( - arkts.factory.createIdentifier( - 'meta', - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart( - arkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) - ) - ) - ), - undefined + static conditionalAddRef(isObservedV2: boolean): arkts.MethodDefinition { + const metaAddRef: arkts.ExpressionStatement = arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression( + UIFactory.generateMemberExpression( + arkts.factory.createIdentifier(ObservedNames.META), + ObservedNames.ADD_REF ), - ], - arkts.factory.createPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), - false + undefined, + undefined + ) ); collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); - const shouldAddRef: arkts.IfStatement = factory.shouldAddRef(); - return arkts.factory.createMethodDefinition( - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, - arkts.factory.createIdentifier('conditionalAddRef'), - arkts.factory.createScriptFunction( - arkts.factory.createBlock([shouldAddRef]), - funcSig, - arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED - ), - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED, - false - ); + return UIFactory.createMethodDefinition({ + key: arkts.factory.createIdentifier(ObservedNames.CONDITIONAL_ADD_REF), + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_METHOD, + function: { + body: arkts.factory.createBlock(isObservedV2 ? [metaAddRef] : [factory.shouldAddRef(metaAddRef)]), + params: [ + arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier( + ObservedNames.META, + UIFactory.createTypeReferenceFromString(StateManagementTypes.MUTABLE_STATE_META) + ), + undefined + ), + ], + returnTypeAnnotation: arkts.factory.createPrimitiveType( + arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID + ), + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED, + }, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PROTECTED, + }); } /* * helper for conditionalAddRef to generate shouldAddRef method */ - static shouldAddRef(): arkts.IfStatement { + static shouldAddRef(metaAddRef: arkts.ExpressionStatement): arkts.IfStatement { const test: arkts.CallExpression = arkts.factory.createCallExpression( - arkts.factory.createMemberExpression( + UIFactory.generateMemberExpression( arkts.factory.createIdentifier(StateManagementTypes.OBSERVE), - arkts.factory.createIdentifier('shouldAddRef'), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false + ObservedNames.SHOULD_ADD_REF ), undefined, - [ - arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier('____V1RenderId'), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ), - ] + [generateThisBacking(ObservedNames.V1_RERENDER_ID)] ); collectStateManagementTypeImport(StateManagementTypes.OBSERVE); - const consequent: arkts.BlockStatement = arkts.factory.createBlock([ - arkts.factory.createExpressionStatement( - arkts.factory.createCallExpression( - arkts.factory.createMemberExpression( - arkts.factory.createIdentifier('meta'), - arkts.factory.createIdentifier('addRef'), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ), - undefined, - undefined - ) - ), - ]); + const consequent: arkts.BlockStatement = arkts.factory.createBlock([metaAddRef]); return arkts.factory.createIfStatement(test, consequent); } @@ -591,11 +573,7 @@ export class factory { const meta = arkts.factory.createClassProperty( arkts.factory.createIdentifier(StateManagementTypes.META), factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_MUTABLESTATE_META, undefined, [], false), - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart( - arkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) - ) - ), + UIFactory.createTypeReferenceFromString(StateManagementTypes.MUTABLE_STATE_META), arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, false ); @@ -786,4 +764,68 @@ export class factory { undefined ); } + + static generateMonitorVariable(itemNameSplit: string[]): arkts.Expression { + const objectFirst: arkts.Expression = generateThisBacking(itemNameSplit[0]); + if (itemNameSplit.length === 1) { + return objectFirst; + } + itemNameSplit.shift(); + return this.recursiveCreateOptionalMember(objectFirst, itemNameSplit); + } + + /** + * recursively create member expression with node and property name in . + * + * @param typeAnnotation expecting property's original type annotation. + */ + static recursiveCreateOptionalMember(object: arkts.Expression, resNameArr: string[]): arkts.Expression { + if (resNameArr.length <= 0) { + return object; + } + const optionalInfo: OptionalMemberInfo = { isNumeric: false }; + if (isNumeric(resNameArr[0])) { + optionalInfo.isNumeric = true; + } + const newMember: arkts.Expression = this.createBlockStatementForOptionalExpression( + object, + resNameArr[0], + optionalInfo + ); + resNameArr.shift(); + return this.recursiveCreateOptionalMember(newMember, resNameArr); + } + + /** + * create IMonitorPathsInfo type parameter `{ path: "", lambda: () => { return this. } }`. + * + * @param monitorItem monitored property name. + */ + static createMonitorPathsInfoParameter(monitorItem: string): arkts.ObjectExpression { + const itemNameSplit: string[] = monitorItem.split('.'); + let monitorVariable: arkts.Expression = arkts.factory.createUndefinedLiteral(); + if (itemNameSplit.length > 0) { + monitorVariable = this.generateMonitorVariable(itemNameSplit); + } + return arkts.factory.createObjectExpression( + arkts.Es2pandaAstNodeType.AST_NODE_TYPE_OBJECT_EXPRESSION, + [ + arkts.factory.createProperty( + arkts.factory.createIdentifier(MonitorNames.PATH), + arkts.factory.create1StringLiteral(monitorItem) + ), + arkts.factory.createProperty( + arkts.factory.createIdentifier(MonitorNames.VALUE_CALL_CACK), + arkts.factory.createArrowFunction( + UIFactory.createScriptFunction({ + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + body: arkts.factory.createBlock([arkts.factory.createReturnStatement(monitorVariable)]), + returnTypeAnnotation: UIFactory.createTypeReferenceFromString(TypeNames.NULLISH_TYPE), + }) + ) + ), + ], + false + ); + } } diff --git a/arkui-plugins/ui-plugins/property-translators/index.ts b/arkui-plugins/ui-plugins/property-translators/index.ts index 133d35329776f65b806b408d703cfea026117a22..4aaf834360bdb4f165ef34f559842d698c002242 100644 --- a/arkui-plugins/ui-plugins/property-translators/index.ts +++ b/arkui-plugins/ui-plugins/property-translators/index.ts @@ -16,7 +16,7 @@ import * as arkts from '@koalaui/libarkts'; import { DecoratorNames } from '../../common/predefines'; -import { InterfacePropertyTranslator, PropertyTranslator } from './base'; +import { InterfacePropertyTranslator, MethodTranslator, PropertyTranslator } from './base'; import { hasDecorator } from './utils'; import { StateInterfaceTranslator, StateTranslator } from './state'; import { PropInterfaceTranslator, PropTranslator } from './prop'; @@ -34,14 +34,33 @@ import { ProvideInterfaceTranslator, ProvideTranslator } from './provide'; import { BuilderParamInterfaceTranslator, BuilderParamTranslator } from './builderParam'; import { PropRefInterfaceTranslator, PropRefTranslator } from './propRef'; import { ObservedTrackTranslator } from './observedTrack'; -import { ClassScopeInfo } from './types'; +import { ClassScopeInfo } from '../struct-translators/utils'; import { LocalInterfaceTranslator, LocalTranslator } from './local'; import { StoragePropRefInterfaceTranslator, StoragePropRefTranslator } from './storagePropRef'; import { LocalStoragePropRefInterfaceTranslator, LocalStoragePropRefTranslator } from './localStoragePropRef'; +import { ObservedV2TraceTranslator } from './observedV2Trace'; +import { ParamInterfaceTranslator, ParamTranslator } from './param'; +import { OnceInterfaceTranslator, OnceTranslator } from './once'; +import { ProviderInterfaceTranslator, ProviderTranslator } from './provider'; +import { ConsumerInterfaceTranslator, ConsumerTranslator } from './consumer'; +import { ComputedTranslator } from './computed'; +import { MonitorTranslator } from './monitor'; export { PropertyTranslator, InterfacePropertyTranslator }; export type { ClassScopeInfo }; +export function classifyStructMembers( + member: arkts.AstNode, + structInfo: CustomComponentInfo +): PropertyTranslator | MethodTranslator | undefined { + if (arkts.isClassProperty(member)) { + return classifyProperty(member, structInfo); + } else if (arkts.isMethodDefinition(member)) { + return classifyMethod(member, true, structInfo.name); + } + return undefined; +} + export function classifyProperty( property: arkts.AstNode, structInfo: CustomComponentInfo @@ -49,6 +68,25 @@ export function classifyProperty( if (!arkts.isClassProperty(property)) return undefined; if (isStatic(property)) return new staticPropertyTranslator({ property, structInfo }); + let propertyTranslator: PropertyTranslator | undefined = undefined; + + propertyTranslator = classifyV1Property(property, structInfo); + if (!!propertyTranslator) { + return propertyTranslator; + } + + propertyTranslator = classifyV2Property(property, structInfo); + if (!!propertyTranslator) { + return propertyTranslator; + } + + return new RegularPropertyTranslator({ property, structInfo }); +} + +export function classifyV1Property( + property: arkts.ClassProperty, + structInfo: CustomComponentInfo +): PropertyTranslator | undefined { if (hasDecorator(property, DecoratorNames.STATE)) { return new StateTranslator({ property, structInfo }); } @@ -92,10 +130,52 @@ export function classifyProperty( return new BuilderParamTranslator({ property, structInfo }); } - return new RegularPropertyTranslator({ property, structInfo }); + return undefined; +} + +export function classifyV2Property( + property: arkts.ClassProperty, + structInfo: CustomComponentInfo +): PropertyTranslator | undefined { + if (hasDecorator(property, DecoratorNames.LOCAL)) { + return new LocalTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.ONCE)) { + return new OnceTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.PARAM)) { + return new ParamTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.PROVIDER)) { + return new ProviderTranslator({ property, structInfo }); + } + if (hasDecorator(property, DecoratorNames.CONSUMER)) { + return new ConsumerTranslator({ property, structInfo }); + } + + return undefined; } export function classifyPropertyInInterface(property: arkts.AstNode): InterfacePropertyTranslator | undefined { + let interfacePropertyTranslater: InterfacePropertyTranslator | undefined = undefined; + + interfacePropertyTranslater = classifyV1PropertyInInterface(property); + if (!!interfacePropertyTranslater) { + return interfacePropertyTranslater; + } + + interfacePropertyTranslater = classifyV2PropertyInInterface(property); + if (!!interfacePropertyTranslater) { + return interfacePropertyTranslater; + } + + if (RegularInterfaceTranslator.canBeTranslated(property)) { + return new RegularInterfaceTranslator({ property }); + } + return undefined; +} + +export function classifyV1PropertyInInterface(property: arkts.AstNode): InterfacePropertyTranslator | undefined { if (StateInterfaceTranslator.canBeTranslated(property)) { return new StateInterfaceTranslator({ property }); } @@ -138,18 +218,68 @@ export function classifyPropertyInInterface(property: arkts.AstNode): InterfaceP if (ObjectLinkInterfaceTranslator.canBeTranslated(property)) { return new ObjectLinkInterfaceTranslator({ property }); } - if (RegularInterfaceTranslator.canBeTranslated(property)) { - return new RegularInterfaceTranslator({ property }); + return undefined; +} + +export function classifyV2PropertyInInterface(property: arkts.AstNode): InterfacePropertyTranslator | undefined { + if (LocalInterfaceTranslator.canBeTranslated(property)) { + return new LocalInterfaceTranslator({ property }); + } + if (OnceInterfaceTranslator.canBeTranslated(property)) { + return new OnceInterfaceTranslator({ property }); + } + if (ParamInterfaceTranslator.canBeTranslated(property)) { + return new ParamInterfaceTranslator({ property }); + } + if (ProviderInterfaceTranslator.canBeTranslated(property)) { + return new ProviderInterfaceTranslator({ property }); + } + if (ConsumerInterfaceTranslator.canBeTranslated(property)) { + return new ConsumerInterfaceTranslator({ property }); } return undefined; } -export function classifyObservedTrack( - member: arkts.AstNode, +export type ObservedTranslator = ObservedV2TraceTranslator | ObservedTrackTranslator; + +export function classifyObservedClassProperty( + member: arkts.ClassProperty, classScopeInfo: ClassScopeInfo -): ObservedTrackTranslator | undefined { - if (!arkts.isClassProperty(member)) { +): ObservedTranslator | undefined { + if (classScopeInfo.isObservedV2) { + return new ObservedV2TraceTranslator(member, classScopeInfo); + } + if (classScopeInfo.isObserved || classScopeInfo.classHasTrack) { + return new ObservedTrackTranslator(member, classScopeInfo); + } + return undefined; +} + +export function classifyMethod( + member: arkts.AstNode, + isFromStruct: boolean, + className: string +): MethodTranslator | undefined { + if (!arkts.isMethodDefinition(member)) { return undefined; } - return new ObservedTrackTranslator(member, classScopeInfo); + if (hasDecorator(member, DecoratorNames.COMPUTED)) { + return new ComputedTranslator(member, { isFromStruct, className }); + } + if (hasDecorator(member, DecoratorNames.MONITOR)) { + return new MonitorTranslator(member, { isFromStruct, className }); + } + return undefined; +} + +export function classifyInObservedClass( + member: arkts.AstNode, + classScopeInfo: ClassScopeInfo +): ObservedTranslator | MethodTranslator | undefined { + if (arkts.isClassProperty(member)) { + return classifyObservedClassProperty(member, classScopeInfo); + } else if (arkts.isMethodDefinition(member)) { + return classifyMethod(member, false, classScopeInfo.className); + } + return undefined; } diff --git a/arkui-plugins/ui-plugins/property-translators/local.ts b/arkui-plugins/ui-plugins/property-translators/local.ts index e61a06d257e531c064a04d60c0e5e35180dae83e..c1b7bdb58411d4c60f87faec0c61e5b7ae41276b 100644 --- a/arkui-plugins/ui-plugins/property-translators/local.ts +++ b/arkui-plugins/ui-plugins/property-translators/local.ts @@ -18,7 +18,6 @@ import * as arkts from '@koalaui/libarkts'; import { backingField, expectName } from '../../common/arkts-utils'; import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; import { - generateToRecord, createGetter, createSetter2, generateThisBacking, @@ -42,10 +41,6 @@ export class LocalTranslator extends PropertyTranslator implements InitializerCo cacheTranslatedInitializer(newName: string, originalName: string): void { const initializeStruct: arkts.AstNode = this.generateInitializeStruct(newName, originalName); PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); - if (!!this.structInfo.annotations?.reusable) { - const toRecord = generateToRecord(newName, originalName); - PropertyCache.getInstance().collectToRecord(this.structInfo.name, [toRecord]); - } } translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { diff --git a/arkui-plugins/ui-plugins/property-translators/monitor.ts b/arkui-plugins/ui-plugins/property-translators/monitor.ts new file mode 100644 index 0000000000000000000000000000000000000000..9dffc1ad8586177e7ec7805a3b8304387af5f11f --- /dev/null +++ b/arkui-plugins/ui-plugins/property-translators/monitor.ts @@ -0,0 +1,132 @@ +/* + * 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 arkts from '@koalaui/libarkts'; + +import { expectName } from '../../common/arkts-utils'; +import { DecoratorNames, MonitorNames, StateManagementTypes } from '../../common/predefines'; +import { monitorField } from '../utils'; +import { generateThisBacking, PropertyCache, collectStateManagementTypeImport } from './utils'; +import { MethodTranslator } from './base'; +import { InitializerConstructor } from './types'; +import { factory as UIFactory } from '../ui-factory'; +import { factory } from './factory'; + +export class MonitorTranslator extends MethodTranslator implements InitializerConstructor { + translateMember(): arkts.AstNode[] { + const originalName: string = expectName(this.method.name); + const newName: string = monitorField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const monitorAssign: arkts.AstNode = this.generateinitAssignment(newName, originalName); + if (this.classInfo.isFromStruct) { + PropertyCache.getInstance().collectInitializeStruct(this.classInfo.className, [monitorAssign]); + } else { + PropertyCache.getInstance().collectContructor(this.classInfo.className, [monitorAssign]); + } + } + + translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { + collectStateManagementTypeImport(StateManagementTypes.MONITOR_DECORATED); + const field: arkts.ClassProperty = arkts.factory.createClassProperty( + arkts.factory.createIdentifier(newName), + undefined, + arkts.factory.createUnionType([ + UIFactory.createTypeReferenceFromString(StateManagementTypes.MONITOR_DECORATED), + arkts.factory.createETSUndefinedType(), + ]), + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + + return [field]; + } + + generateinitAssignment(newName: string, originalName: string): arkts.ExpressionStatement { + const monitorItem: string[] | undefined = this.getValueInMonitorAnnotation(); + const thisValue: arkts.Expression = generateThisBacking(newName, false, false); + const right: arkts.CallExpression = factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_MONITOR, + undefined, + [this.generatePathArg(monitorItem), this.generateLambdaArg(originalName)], + false + ); + return arkts.factory.createExpressionStatement( + arkts.factory.createAssignmentExpression( + thisValue, + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + right + ) + ); + } + + generatePathArg(monitorItem: string[] | undefined): arkts.ArrayExpression { + if (!monitorItem || monitorItem.length <= 0) { + return arkts.factory.createArrayExpression([]); + } + const params = monitorItem.map((itemName: string) => { + return factory.createMonitorPathsInfoParameter(itemName); + }); + return arkts.factory.createArrayExpression(params); + } + + generateLambdaArg(originalName: string): arkts.ArrowFunctionExpression { + return arkts.factory.createArrowFunction( + UIFactory.createScriptFunction({ + params: [UIFactory.createParameterDeclaration(MonitorNames.M_PARAM, MonitorNames.I_MONITOR)], + body: arkts.factory.createBlock([ + arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression(generateThisBacking(originalName), undefined, [ + arkts.factory.createIdentifier(MonitorNames.M_PARAM), + ]) + ), + ]), + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW, + }) + ); + } + + getValueInMonitorAnnotation(): string[] | undefined { + const annotations: readonly arkts.AnnotationUsage[] = this.method.scriptFunction.annotations; + for (let i = 0; i < annotations.length; i++) { + const anno: arkts.AnnotationUsage = annotations[i]; + if ( + anno.expr && + arkts.isIdentifier(anno.expr) && + anno.expr.name === DecoratorNames.MONITOR && + anno.properties.length === 1 + ) { + return this.getArrayFromAnnoProperty(anno.properties[0]); + } + } + return undefined; + } + + getArrayFromAnnoProperty(property: arkts.AstNode): string[] | undefined { + if (arkts.isClassProperty(property) && property.value && arkts.isArrayExpression(property.value)) { + const resArr: string[] = []; + property.value.elements.forEach((item: arkts.Expression) => { + if (arkts.isStringLiteral(item)) { + resArr.push(item.str); + } + }); + return resArr; + } + return undefined; + } +} diff --git a/arkui-plugins/ui-plugins/property-translators/observedTrack.ts b/arkui-plugins/ui-plugins/property-translators/observedTrack.ts index 28d0762e4cf5c86cf6941f186b4f3db1cd69ed09..33b78a02805ef425fd0db9641d22e850ccd3418e 100644 --- a/arkui-plugins/ui-plugins/property-translators/observedTrack.ts +++ b/arkui-plugins/ui-plugins/property-translators/observedTrack.ts @@ -15,21 +15,27 @@ import * as arkts from '@koalaui/libarkts'; import { annotation, backingField, expectName } from '../../common/arkts-utils'; -import { DecoratorNames, StateManagementTypes } from '../../common/predefines'; -import { collectStateManagementTypeImport, hasDecorator, hasDecoratorName, removeDecorator } from './utils'; -import { ClassScopeInfo } from './types'; +import { DecoratorNames, StateManagementTypes, ObservedNames } from '../../common/predefines'; +import { ObservedPropertyTranslator } from "./base"; +import { + collectStateManagementTypeImport, + generateThisBacking, + hasDecorator, + hasDecoratorName, + removeDecorator, + removeImplementProperty, +} from './utils'; +import { ClassScopeInfo } from '../struct-translators/utils'; import { factory } from './factory'; +import { factory as uiFactory } from '../ui-factory'; -export class ObservedTrackTranslator { - protected property: arkts.ClassProperty; - protected classScopeInfo: ClassScopeInfo; +export class ObservedTrackTranslator extends ObservedPropertyTranslator { private hasImplement: boolean; private isTracked: boolean; constructor(property: arkts.ClassProperty, classScopeInfo: ClassScopeInfo) { - this.property = property; - this.classScopeInfo = classScopeInfo; - this.hasImplement = expectName(this.property.key).startsWith(''); + super(property, classScopeInfo); + this.hasImplement = expectName(this.property.key).startsWith(ObservedNames.PROPERTY_PREFIX); this.isTracked = hasDecorator(this.property, DecoratorNames.TRACK); } @@ -38,7 +44,7 @@ export class ObservedTrackTranslator { return [this.property]; } const originalName: string = this.hasImplement - ? this.removeImplementProperty(expectName(this.property.key)) + ? removeImplementProperty(expectName(this.property.key)) : expectName(this.property.key); const newName: string = backingField(originalName); const field = this.createField(originalName, newName); @@ -54,14 +60,18 @@ export class ObservedTrackTranslator { arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, false ); + if (!this.property.value) { + backingField.modifiers |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL; + } const annotations: arkts.AnnotationUsage[] = [...this.property.annotations]; if ( !hasDecoratorName(this.property, DecoratorNames.JSONSTRINGIFYIGNORE) && - !hasDecoratorName(this.property, DecoratorNames.JSONRENAME)) { + !hasDecoratorName(this.property, DecoratorNames.JSONRENAME) + ) { annotations.push( annotation(DecoratorNames.JSONRENAME).addProperty( arkts.factory.createClassProperty( - arkts.factory.createIdentifier('newName'), + arkts.factory.createIdentifier(ObservedNames.NEW_NAME), arkts.factory.createStringLiteral(originalName), undefined, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, @@ -82,78 +92,55 @@ export class ObservedTrackTranslator { createGetter(originalName: string, newName: string): arkts.MethodDefinition { const conditionalAddRef = arkts.factory.createExpressionStatement( - arkts.factory.createCallExpression( + arkts.factory.createCallExpression(generateThisBacking(ObservedNames.CONDITIONAL_ADD_REF), undefined, [ arkts.factory.createMemberExpression( arkts.factory.createThisExpression(), - arkts.factory.createIdentifier('conditionalAddRef'), + this.metaIdentifier(originalName), arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, false, false ), - undefined, - [ - arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - this.metaIdentifier(originalName), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ), - ] - ) - ); - const returnMember: arkts.ReturnStatement = arkts.factory.createReturnStatement(this.genThisBacking(newName)); - - const body = arkts.factory.createBlock([conditionalAddRef, returnMember]); - - const scriptFunction = arkts.factory.createScriptFunction( - body, - arkts.FunctionSignature.createFunctionSignature(undefined, [], this.property.typeAnnotation, false), - arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC + ]) ); - - return arkts.factory.createMethodDefinition( - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, - arkts.factory.createIdentifier(originalName), - scriptFunction, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - false + const backingMember: arkts.Expression = generateThisBacking(newName); + const returnMember: arkts.ReturnStatement = arkts.factory.createReturnStatement( + this.property.value + ? backingMember + : arkts.factory.createTSAsExpression(backingMember, this.property.typeAnnotation, false) ); + const body = arkts.factory.createBlock([conditionalAddRef, returnMember]); + return uiFactory.createMethodDefinition({ + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, + key: arkts.factory.createIdentifier(originalName), + function: { + body: body, + returnTypeAnnotation: this.property.typeAnnotation, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER, + }, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + }); } createSetter(originalName: string, newName: string): arkts.MethodDefinition { const ifEqualsNewValue: arkts.IfStatement = this.setterIfEqualsNewValue(originalName, newName); const body = arkts.factory.createBlock([ifEqualsNewValue]); const param = arkts.factory.createParameterDeclaration( - arkts.factory.createIdentifier('newValue', this.property.typeAnnotation), + arkts.factory.createIdentifier(ObservedNames.NEW_VALUE, this.property.typeAnnotation), undefined ); - const scriptFunction = arkts.factory.createScriptFunction( - body, - arkts.FunctionSignature.createFunctionSignature(undefined, [param], undefined, false), - arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC - ); - - return arkts.factory.createMethodDefinition( - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, - arkts.factory.createIdentifier(originalName), - scriptFunction, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - false - ); - } - - genThisBacking(newName: string): arkts.MemberExpression { - return arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier(newName), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ); + return uiFactory.createMethodDefinition({ + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, + key: arkts.factory.createIdentifier(originalName), + function: { + body: body, + params: [param], + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, + }, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + }); } metaIdentifier(originalName: string): arkts.Identifier { @@ -162,11 +149,6 @@ export class ObservedTrackTranslator { : arkts.factory.createIdentifier(StateManagementTypes.META); } - removeImplementProperty(originalName: string): string { - const prefix = ''; - return originalName.substring(prefix.length); - } - transformGetterSetter(originalName: string, newName: string): void { const newGetter = this.createGetter(originalName, newName); const newSetter = this.createSetter(originalName, newName); @@ -207,24 +189,20 @@ export class ObservedTrackTranslator { return arkts.factory.createClassProperty( arkts.factory.createIdentifier(`${StateManagementTypes.META}_${originalName}`), factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_MUTABLESTATE_META, undefined, [], false), - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart( - arkts.factory.createIdentifier(StateManagementTypes.MUTABLE_STATE_META) - ) - ), + uiFactory.createTypeReferenceFromString(StateManagementTypes.MUTABLE_STATE_META), arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, false ); } setterIfEqualsNewValue(originalName: string, newName: string): arkts.IfStatement { - const backingValue = this.genThisBacking(newName); + const backingValue = generateThisBacking(newName); const setNewValue = arkts.factory.createExpressionStatement( arkts.factory.createAssignmentExpression( backingValue, arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, - arkts.factory.createIdentifier('newValue') + arkts.factory.createIdentifier(ObservedNames.NEW_VALUE) ) ); @@ -238,7 +216,7 @@ export class ObservedTrackTranslator { false, false ), - arkts.factory.createIdentifier('fireChange'), + arkts.factory.createIdentifier(ObservedNames.FIRE_CHANGE), arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, false, false @@ -249,23 +227,15 @@ export class ObservedTrackTranslator { ); const subscribingWatches = arkts.factory.createExpressionStatement( - arkts.factory.createCallExpression( - arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier('executeOnSubscribingWatches'), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ), - undefined, - [arkts.factory.createStringLiteral(originalName)] - ) + arkts.factory.createCallExpression(generateThisBacking(ObservedNames.EXECUATE_WATCHES), undefined, [ + arkts.factory.createStringLiteral(originalName), + ]) ); return arkts.factory.createIfStatement( arkts.factory.createBinaryExpression( backingValue, - arkts.factory.createIdentifier('newValue'), + arkts.factory.createIdentifier(ObservedNames.NEW_VALUE), arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL ), arkts.factory.createBlock([setNewValue, fireChange, subscribingWatches]) diff --git a/arkui-plugins/ui-plugins/property-translators/observedV2Trace.ts b/arkui-plugins/ui-plugins/property-translators/observedV2Trace.ts new file mode 100644 index 0000000000000000000000000000000000000000..e55ea797f0a65cf3b83c7480887ff748805364a1 --- /dev/null +++ b/arkui-plugins/ui-plugins/property-translators/observedV2Trace.ts @@ -0,0 +1,274 @@ +/* + * 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 arkts from '@koalaui/libarkts'; +import { annotation, backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, ObservedNames, StateManagementTypes } from '../../common/predefines'; +import { ObservedPropertyTranslator } from "./base"; +import { + collectStateManagementTypeImport, + generateThisBacking, + hasDecorator, + hasDecoratorName, + removeDecorator, + removeImplementProperty, +} from './utils'; +import { ClassScopeInfo } from '../struct-translators/utils'; +import { factory } from './factory'; +import { factory as uiFactory } from '../ui-factory'; +import { ImportCollector } from '../../common/import-collector'; + +export class ObservedV2TraceTranslator extends ObservedPropertyTranslator { + private hasImplement: boolean; + private isTraced: boolean; + private isStatic: boolean; + + constructor(property: arkts.ClassProperty, classScopeInfo: ClassScopeInfo) { + super(property, classScopeInfo); + this.hasImplement = expectName(this.property.key).startsWith(ObservedNames.PROPERTY_PREFIX); + this.isTraced = hasDecorator(this.property, DecoratorNames.TRACE); + this.isStatic = arkts.hasModifierFlag(this.property, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC); + } + + translateMember(): arkts.AstNode[] { + if (!this.isTraced) { + return [this.property]; + } + const originalName: string = this.hasImplement + ? removeImplementProperty(expectName(this.property.key)) + : expectName(this.property.key); + const newName: string = backingField(originalName); + const field = this.createField(originalName, newName); + this.transformGetterSetter(originalName, newName); + return [...field]; + } + + createField(originalName: string, newName: string): arkts.ClassProperty[] { + const backingField = arkts.factory.createClassProperty( + arkts.factory.createIdentifier(newName), + this.property.value, + this.property.typeAnnotation, + this.isStatic + ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC + : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + if (!this.property.value) { + backingField.modifiers |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL; + } + const annotations: arkts.AnnotationUsage[] = [...this.property.annotations]; + if ( + !hasDecoratorName(this.property, DecoratorNames.JSONSTRINGIFYIGNORE) && + !hasDecoratorName(this.property, DecoratorNames.JSONRENAME) + ) { + annotations.push( + annotation(DecoratorNames.JSONRENAME).addProperty( + arkts.factory.createClassProperty( + arkts.factory.createIdentifier(ObservedNames.NEW_NAME), + arkts.factory.createStringLiteral(originalName), + undefined, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE, + false + ) + ) + ); + } + backingField.setAnnotations(annotations); + removeDecorator(backingField, DecoratorNames.TRACE); + const metaField = this.metaField(originalName); + metaField.setAnnotations([annotation(DecoratorNames.JSONSTRINGIFYIGNORE)]); + return [backingField, metaField]; + } + + createGetter(originalName: string, newName: string, methodModifier: arkts.Es2pandaModifierFlags): arkts.MethodDefinition { + const classIdent: arkts.Identifier = arkts.factory.createIdentifier(this.classScopeInfo.className); + ImportCollector.getInstance().collectImport(StateManagementTypes.UI_UTILS); + const observedMember: arkts.Expression = this.isStatic + ? uiFactory.generateMemberExpression(classIdent.clone(), newName) + : generateThisBacking(newName); + const returnMember: arkts.ReturnStatement = arkts.factory.createReturnStatement( + arkts.factory.createCallExpression( + uiFactory.generateMemberExpression( + arkts.factory.createIdentifier(StateManagementTypes.UI_UTILS), + StateManagementTypes.MAKE_OBSERVED + ), + undefined, + [ + this.property.value + ? observedMember + : arkts.factory.createTSAsExpression( + observedMember, + this.property.typeAnnotation?.clone(), + false + ), + ] + ) + ); + const body = arkts.factory.createBlock([this.createAddRef(originalName, classIdent), returnMember]); + return uiFactory.createMethodDefinition({ + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, + key: arkts.factory.createIdentifier(originalName), + function: { + body: body, + returnTypeAnnotation: this.property.typeAnnotation, + modifiers: methodModifier, + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_GETTER, + }, + modifiers: methodModifier, + }); + } + + createSetter( + originalName: string, + newName: string, + methodModifier: arkts.Es2pandaModifierFlags + ): arkts.MethodDefinition { + const ifEqualsNewValue: arkts.IfStatement = this.setterIfEqualsNewValue(originalName, newName); + const body = arkts.factory.createBlock([ifEqualsNewValue]); + const param = arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier(ObservedNames.NEW_VALUE, this.property.typeAnnotation), + undefined + ); + + return uiFactory.createMethodDefinition({ + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, + key: arkts.factory.createIdentifier(originalName), + function: { + body: body, + params: [param], + modifiers: methodModifier, + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER, + }, + modifiers: methodModifier, + }); + } + + transformGetterSetter(originalName: string, newName: string): void { + const methodModifier = this.isStatic + ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC + : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC; + const newGetter = this.createGetter(originalName, newName, methodModifier); + const newSetter = this.createSetter(originalName, newName, methodModifier); + if (this.hasImplement) { + { + const idx: number = this.classScopeInfo.getters.findIndex( + (getter) => getter.name.name === originalName + ); + const originGetter: arkts.MethodDefinition = this.classScopeInfo.getters[idx]; + const originSetter: arkts.MethodDefinition = originGetter.overloads[0]; + const updateGetter: arkts.MethodDefinition = arkts.factory.updateMethodDefinition( + originGetter, + originGetter.kind, + newGetter.name, + newGetter.scriptFunction.addFlag(arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD), + originGetter.modifiers, + false + ); + arkts.factory.updateMethodDefinition( + originSetter, + originSetter.kind, + newSetter.name, + newSetter.scriptFunction + .addFlag(arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_OVERLOAD) + .addFlag(arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD), + originSetter.modifiers, + false + ); + this.classScopeInfo.getters[idx] = updateGetter; + } + } else { + this.classScopeInfo.getters.push(...[newGetter, newSetter]); + } + } + + metaField(originalName: string): arkts.ClassProperty { + collectStateManagementTypeImport(StateManagementTypes.MUTABLE_STATE_META); + return arkts.factory.createClassProperty( + arkts.factory.createIdentifier(`${StateManagementTypes.META}_${originalName}`), + factory.generateStateMgmtFactoryCall(StateManagementTypes.MAKE_MUTABLESTATE_META, undefined, [], false), + uiFactory.createTypeReferenceFromString(StateManagementTypes.MUTABLE_STATE_META), + this.isStatic + ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC + : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE, + false + ); + } + + createAddRef(originalName: string, classIdent: arkts.Identifier): arkts.ExpressionStatement { + const metaName: string = `${StateManagementTypes.META}_${originalName}`; + const conditionalAddRef = arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression(generateThisBacking(ObservedNames.CONDITIONAL_ADD_REF), undefined, [ + generateThisBacking(metaName), + ]) + ); + const metaAddRef = arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression( + uiFactory.generateMemberExpression( + uiFactory.generateMemberExpression(classIdent.clone(), metaName), + ObservedNames.ADD_REF + ), + undefined, + undefined + ) + ); + return this.isStatic ? metaAddRef : conditionalAddRef; + } + + setterIfEqualsNewValue(originalName: string, newName: string): arkts.IfStatement { + const classIdent: arkts.Identifier = arkts.factory.createIdentifier(this.classScopeInfo.className); + const metaName: string = `${StateManagementTypes.META}_${originalName}`; + const backingValue: arkts.Expression = generateThisBacking(newName); + const metaValue: arkts.Expression = generateThisBacking(metaName); + const staticMetaValue: arkts.Expression = uiFactory.generateMemberExpression(classIdent.clone(), metaName); + const staticBackingValue: arkts.Expression = uiFactory.generateMemberExpression(classIdent.clone(), newName); + const setNewValue = arkts.factory.createExpressionStatement( + arkts.factory.createAssignmentExpression( + this.isStatic ? staticBackingValue : backingValue, + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + arkts.factory.createIdentifier(ObservedNames.NEW_VALUE) + ) + ); + + const fireChange = arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression( + arkts.factory.createMemberExpression( + this.isStatic ? staticMetaValue.clone() : metaValue.clone(), + arkts.factory.createIdentifier(ObservedNames.FIRE_CHANGE), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + undefined, + undefined + ) + ); + + const subscribingWatches = arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression(generateThisBacking(ObservedNames.EXECUATE_WATCHES), undefined, [ + arkts.factory.createStringLiteral(originalName), + ]) + ); + + const consequentArr = this.isStatic ? [setNewValue, fireChange] : [setNewValue, fireChange, subscribingWatches]; + return arkts.factory.createIfStatement( + arkts.factory.createBinaryExpression( + this.isStatic ? staticBackingValue.clone() : backingValue.clone(), + arkts.factory.createIdentifier(ObservedNames.NEW_VALUE), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_STRICT_EQUAL + ), + arkts.factory.createBlock(consequentArr) + ); + } +} diff --git a/arkui-plugins/ui-plugins/property-translators/once.ts b/arkui-plugins/ui-plugins/property-translators/once.ts new file mode 100644 index 0000000000000000000000000000000000000000..13f9868ad4003d266f7dc2f6b5967edc9d3fc69e --- /dev/null +++ b/arkui-plugins/ui-plugins/property-translators/once.ts @@ -0,0 +1,168 @@ +/* + * 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 arkts from '@koalaui/libarkts'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { CustomComponentNames } from '../utils'; +import { + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + hasDecorator, + collectStateManagementTypeImport, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class OnceTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): arkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: arkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + } + + translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { + const field: arkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.ONCE_DECORATED, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + 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 getter: arkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: arkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + returnValue: arkts.Expression + ): arkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + statement: arkts.AstNode + ): arkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } + + generateInitializeStruct(newName: string, originalName: string): arkts.AstNode { + const outInitialize: arkts.Expression = factory.createBlockStatementForOptionalExpression( + arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + originalName + ); + const args: arkts.Expression[] = [ + arkts.factory.create1StringLiteral(originalName), + this.property.value + ? arkts.factory.createBinaryExpression( + outInitialize, + this.property.value, + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ) + : arkts.factory.createTSAsExpression( + factory.createNonNullOrOptionalMemberExpression( + CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + originalName, + false, + true + ), + this.property.typeAnnotation?.clone(), + false + ), + ]; + collectStateManagementTypeImport(StateManagementTypes.ONCE_DECORATED); + const assign: arkts.AssignmentExpression = arkts.factory.createAssignmentExpression( + generateThisBacking(newName), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_PARAM_ONCE, + this.property.typeAnnotation, + args, + true + ) + ); + return arkts.factory.createExpressionStatement(assign); + } +} + +export class OnceInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (arkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (arkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: arkts.AstNode): node is InterfacePropertyTypes { + if (arkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.ONCE)) { + return true; + } else if (arkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.ONCE)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `IParamOnceDecoratedVariable | undefined`. + * + * @param method expecting getter with `@Once` and a setter with `@Once` in the overloads. + */ + private updateStateMethodInInterface(method: arkts.MethodDefinition): arkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.ONCE); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `IParamOnceDecoratedVariable | undefined`. + * + * @param property expecting property with `@Once`. + */ + private updateStatePropertyInInterface(property: arkts.ClassProperty): arkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.ONCE); + } +} diff --git a/arkui-plugins/ui-plugins/property-translators/param.ts b/arkui-plugins/ui-plugins/property-translators/param.ts new file mode 100644 index 0000000000000000000000000000000000000000..5332703c1158418c91e217759f2c850fba3d4f65 --- /dev/null +++ b/arkui-plugins/ui-plugins/property-translators/param.ts @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as arkts from '@koalaui/libarkts'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { CustomComponentNames } from '../utils'; +import { + createGetter, + generateThisBacking, + generateGetOrSetCall, + hasDecorator, + collectStateManagementTypeImport, + PropertyCache, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class ParamTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): arkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: arkts.AstNode = this.generateInitializeStruct(newName, originalName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + const updateStruct: arkts.AstNode = this.generateUpdateStruct(generateThisBacking(newName), originalName); + PropertyCache.getInstance().collectUpdateStruct(this.structInfo.name, [updateStruct]); + } + + translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { + const field: arkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.PARAM_DECORATED, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + const thisValue: arkts.Expression = generateThisBacking(newName, false, true); + const thisGet: arkts.CallExpression = generateGetOrSetCall(thisValue, GetSetTypes.GET); + const getter: arkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + + return [field, getter]; + } + + translateGetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + returnValue: arkts.Expression + ): arkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + generateInitializeStruct(newName: string, originalName: string): arkts.AstNode { + const outInitialize: arkts.Expression = factory.createBlockStatementForOptionalExpression( + arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_INITIALIZERS_NAME), + originalName + ); + const args: arkts.Expression[] = [ + arkts.factory.create1StringLiteral(originalName), + this.property.value + ? arkts.factory.createBinaryExpression( + outInitialize, + this.property.value, + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NULLISH_COALESCING + ) + : arkts.factory.createTSAsExpression( + factory.createNonNullOrOptionalMemberExpression( + CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + originalName, + false, + true + ), + this.property.typeAnnotation?.clone(), + false + ), + ]; + collectStateManagementTypeImport(StateManagementTypes.PARAM_DECORATED); + const assign: arkts.AssignmentExpression = arkts.factory.createAssignmentExpression( + generateThisBacking(newName), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_PARAM, + this.property.typeAnnotation, + args, + true + ) + ); + return arkts.factory.createExpressionStatement(assign); + } + + generateUpdateStruct(mutableThis: arkts.Expression, originalName: string): arkts.AstNode { + const member: arkts.MemberExpression = arkts.factory.createMemberExpression( + arkts.factory.createTSNonNullExpression(mutableThis), + arkts.factory.createIdentifier(StateManagementTypes.UPDATE), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ); + return factory.createIfInUpdateStruct(originalName, member, [ + arkts.factory.createTSAsExpression( + factory.createNonNullOrOptionalMemberExpression( + CustomComponentNames.COMPONENT_INITIALIZERS_NAME, + originalName, + false, + true + ), + this.property.typeAnnotation ? this.property.typeAnnotation.clone() : undefined, + false + ), + ]); + } +} + +export class ParamInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (arkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (arkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: arkts.AstNode): node is InterfacePropertyTypes { + if (arkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.PARAM)) { + return true; + } else if (arkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.PARAM)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `IParamDecoratedVariable | undefined`. + * + * @param method expecting getter with `@Param` and a setter with `@Param` in the overloads. + */ + private updateStateMethodInInterface(method: arkts.MethodDefinition): arkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.PARAM); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `IParamDecoratedVariable | undefined`. + * + * @param property expecting property with `@Param`. + */ + private updateStatePropertyInInterface(property: arkts.ClassProperty): arkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.PARAM); + } +} diff --git a/arkui-plugins/ui-plugins/property-translators/provider.ts b/arkui-plugins/ui-plugins/property-translators/provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..474b78b160db9ef1669edde91885b44ad43e9712 --- /dev/null +++ b/arkui-plugins/ui-plugins/property-translators/provider.ts @@ -0,0 +1,150 @@ +/* + * 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 arkts from '@koalaui/libarkts'; + +import { backingField, expectName } from '../../common/arkts-utils'; +import { DecoratorNames, GetSetTypes, StateManagementTypes } from '../../common/predefines'; +import { + createGetter, + createSetter2, + generateThisBacking, + generateGetOrSetCall, + hasDecorator, + PropertyCache, + getValueInAnnotation, +} from './utils'; +import { InterfacePropertyTranslator, InterfacePropertyTypes, PropertyTranslator } from './base'; +import { GetterSetter, InitializerConstructor } from './types'; +import { factory } from './factory'; + +export class ProviderTranslator extends PropertyTranslator implements InitializerConstructor, GetterSetter { + translateMember(): arkts.AstNode[] { + const originalName: string = expectName(this.property.key); + const newName: string = backingField(originalName); + this.cacheTranslatedInitializer(newName, originalName); + return this.translateWithoutInitializer(newName, originalName); + } + + cacheTranslatedInitializer(newName: string, originalName: string): void { + const initializeStruct: arkts.AstNode = this.generateInitializeStruct(originalName, newName); + PropertyCache.getInstance().collectInitializeStruct(this.structInfo.name, [initializeStruct]); + } + + translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[] { + const field: arkts.ClassProperty = factory.createOptionalClassProperty( + newName, + this.property, + StateManagementTypes.PROVIDER_DECORATED, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE + ); + 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 getter: arkts.MethodDefinition = this.translateGetter( + originalName, + this.property.typeAnnotation, + thisGet + ); + const setter: arkts.MethodDefinition = this.translateSetter( + originalName, + this.property.typeAnnotation, + thisSet + ); + + return [field, getter, setter]; + } + + translateGetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + returnValue: arkts.Expression + ): arkts.MethodDefinition { + return createGetter(originalName, typeAnnotation, returnValue); + } + + translateSetter( + originalName: string, + typeAnnotation: arkts.TypeNode | undefined, + statement: arkts.AstNode + ): arkts.MethodDefinition { + return createSetter2(originalName, typeAnnotation, statement); + } + + generateInitializeStruct(originalName: string, newName: string): arkts.AstNode { + const args: arkts.Expression[] = [ + arkts.factory.create1StringLiteral(originalName), + arkts.factory.create1StringLiteral( + getValueInAnnotation(this.property, DecoratorNames.PROVIDER) ?? originalName + ), + this.property.value! + ]; + const assign: arkts.AssignmentExpression = arkts.factory.createAssignmentExpression( + generateThisBacking(newName), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION, + factory.generateStateMgmtFactoryCall( + StateManagementTypes.MAKE_PROVIDER, + this.property.typeAnnotation, + args, + true + ) + ); + return arkts.factory.createExpressionStatement(assign); + } +} + +export class ProviderInterfaceTranslator extends InterfacePropertyTranslator { + translateProperty(): T { + if (arkts.isMethodDefinition(this.property)) { + this.modified = true; + return this.updateStateMethodInInterface(this.property) as T; + } else if (arkts.isClassProperty(this.property)) { + this.modified = true; + return this.updateStatePropertyInInterface(this.property) as T; + } + return this.property; + } + + static canBeTranslated(node: arkts.AstNode): node is InterfacePropertyTypes { + if (arkts.isMethodDefinition(node) && hasDecorator(node, DecoratorNames.PROVIDER)) { + return true; + } else if (arkts.isClassProperty(node) && hasDecorator(node, DecoratorNames.PROVIDER)) { + return true; + } + return false; + } + + /** + * Wrap getter's return type and setter's param type (expecting an union type with `T` and `undefined`) + * to `IProviderDecoratedVariable | undefined`. + * + * @param method expecting getter with `@Provider` and a setter with `@Provider` in the overloads. + */ + private updateStateMethodInInterface(method: arkts.MethodDefinition): arkts.MethodDefinition { + return factory.wrapStateManagementTypeToMethodInInterface(method, DecoratorNames.PROVIDER); + } + + /** + * Wrap to the type of the property (expecting an union type with `T` and `undefined`) + * to `IProviderDecoratedVariable | undefined`. + * + * @param property expecting property with `@Provider`. + */ + private updateStatePropertyInInterface(property: arkts.ClassProperty): arkts.ClassProperty { + return factory.wrapStateManagementTypeToPropertyInInterface(property, DecoratorNames.PROVIDER); + } +} diff --git a/arkui-plugins/ui-plugins/property-translators/types.ts b/arkui-plugins/ui-plugins/property-translators/types.ts index 24e9e3bf628b4cf0b57ac9301a5397fa1a8516b0..0b903d0e3586521e9f2796c7439633a2b6231949 100644 --- a/arkui-plugins/ui-plugins/property-translators/types.ts +++ b/arkui-plugins/ui-plugins/property-translators/types.ts @@ -31,10 +31,4 @@ export interface GetterSetter { export interface InitializerConstructor { cacheTranslatedInitializer(newName: string, originalName: string): void; translateWithoutInitializer(newName: string, originalName: string): arkts.AstNode[]; -} - -export type ClassScopeInfo = { - isObserved: boolean; - classHasTrack: boolean; - getters: arkts.MethodDefinition[] -}; +} \ No newline at end of file diff --git a/arkui-plugins/ui-plugins/property-translators/utils.ts b/arkui-plugins/ui-plugins/property-translators/utils.ts index e0a5f75db5287a2763569902394f7a017fe581c5..0c2ae71c6702ee083b39206eb9a4583652315dfa 100644 --- a/arkui-plugins/ui-plugins/property-translators/utils.ts +++ b/arkui-plugins/ui-plugins/property-translators/utils.ts @@ -22,6 +22,7 @@ import { DECORATOR_TYPE_MAP, StateManagementTypes, GetSetTypes, + ObservedNames, } from '../../common/predefines'; import { addMemoAnnotation, @@ -34,6 +35,11 @@ export interface DecoratorInfo { name: DecoratorNames; } +export interface OptionalMemberInfo { + isCall?: boolean; + isNumeric?: boolean; +} + export function isDecoratorIntrinsicAnnotation( anno: arkts.AnnotationUsage, decoratorName: DecoratorIntrinsicNames @@ -99,7 +105,9 @@ export function needDefiniteOrOptionalModifier(st: arkts.ClassProperty): boolean hasDecoratorName(st, DecoratorNames.CONSUME) || hasDecoratorName(st, DecoratorNames.OBJECT_LINK) || (hasDecoratorName(st, DecoratorNames.PROP) && !st.value) || - (hasDecoratorName(st, DecoratorNames.PROP_REF) && !st.value) + (hasDecoratorName(st, DecoratorNames.PROP_REF) && !st.value) || + (hasDecoratorName(st, DecoratorNames.PARAM) && !st.value) || + (hasDecoratorName(st, DecoratorNames.EVENT) && !st.value) ); } @@ -374,11 +382,17 @@ export function generateToRecord(newName: string, originalName: string): arkts.P ); } +export function removeImplementProperty(originalName: string): string { + const prefix = ObservedNames.PROPERTY_PREFIX; + return originalName.substring(prefix.length); +} + // CACHE export interface PropertyCachedBody { initializeBody?: arkts.AstNode[]; updateBody?: arkts.AstNode[]; toRecordBody?: arkts.Property[]; + constructorBody?: arkts.AstNode[]; } export class PropertyCache { @@ -412,6 +426,10 @@ export class PropertyCache { return this._cache.get(name)?.toRecordBody ?? []; } + getConstructorBody(name: string): arkts.AstNode[] { + return this._cache.get(name)?.constructorBody ?? []; + } + collectInitializeStruct(name: string, initializeStruct: arkts.AstNode[]): void { const initializeBody = this._cache.get(name)?.initializeBody ?? []; const newInitializeBody = [...initializeBody, ...initializeStruct]; @@ -429,5 +447,10 @@ export class PropertyCache { const newToRecordBody = [...toRecordBody, ...toRecord]; this._cache.set(name, { ...this._cache.get(name), toRecordBody: newToRecordBody }); } -} + collectContructor(name: string, insertedConstructorBody: arkts.AstNode[]): void { + const constructorBody = this._cache.get(name)?.constructorBody ?? []; + const newConstructorBody = [...constructorBody, ...insertedConstructorBody]; + this._cache.set(name, { ...this._cache.get(name), constructorBody: newConstructorBody }); + } +} diff --git a/arkui-plugins/ui-plugins/struct-translators/factory.ts b/arkui-plugins/ui-plugins/struct-translators/factory.ts index 1d7c8fc83fad6bb36f8f536db83589b86eb0d082..247be18c75a08a72c9208920f654e8032913567c 100644 --- a/arkui-plugins/ui-plugins/struct-translators/factory.ts +++ b/arkui-plugins/ui-plugins/struct-translators/factory.ts @@ -31,9 +31,9 @@ import { factory as PropertyFactory } from '../property-translators/factory'; import { factory as BuilderLambdaFactory } from '../builder-lambda-translators/factory'; import { backingField, collect, filterDefined } from '../../common/arkts-utils'; import { - classifyObservedTrack, - classifyProperty, + classifyInObservedClass, classifyPropertyInInterface, + classifyStructMembers, ClassScopeInfo, InterfacePropertyTranslator, PropertyTranslator, @@ -56,6 +56,8 @@ import { findBuilderIndexInControllerOptions, getControllerName, DialogControllerInfo, + ObservedAnnoInfo, + getNoTransformationMembersInClass, } from './utils'; import { collectStateManagementTypeImport, @@ -75,10 +77,11 @@ import { RESOURCE_TYPE, ARKUI_BUILDER_SOURCE_NAME, } from '../../common/predefines'; -import { ObservedTrackTranslator } from '../property-translators/observedTrack'; +import { ObservedTranslator } from '../property-translators/index'; import { addMemoAnnotation } from '../../collectors/memo-collectors/utils'; import { generateArkUICompatible, isArkUICompatible } from '../interop/interop'; import { GenSymGenerator } from '../../common/gensym-generator'; +import { MethodTranslator } from 'ui-plugins/property-translators/base'; export class factory { /** @@ -444,7 +447,7 @@ export class factory { * transform property members in custom-component class. */ static tranformPropertyMembers( - propertyTranslators: PropertyTranslator[], + propertyTranslators: (PropertyTranslator | MethodTranslator)[], optionsTypeName: string, scope: CustomComponentScopeInfo ): arkts.AstNode[] { @@ -497,8 +500,8 @@ export class factory { throw new Error('Non Empty className expected for Component'); } - const propertyTranslators: PropertyTranslator[] = filterDefined( - definition.body.map((it) => classifyProperty(it, scope)) + const propertyTranslators: (PropertyTranslator | MethodTranslator)[] = filterDefined( + definition.body.map((it) => classifyStructMembers(it, scope)) ); const translatedMembers: arkts.AstNode[] = this.tranformPropertyMembers( propertyTranslators, @@ -516,7 +519,11 @@ export class factory { } } const updateMembers: arkts.AstNode[] = definition.body - .filter((member) => !arkts.isClassProperty(member)) + .filter( + (member) => + !arkts.isClassProperty(member) && + !(arkts.isMethodDefinition(member) && hasDecorator(member, DecoratorNames.COMPUTED)) + ) .map((member: arkts.AstNode) => factory.transformNonPropertyMembersInClass(member, scope.isDecl)); const updateClassDef: arkts.ClassDefinition = this.updateCustomComponentClass(definition, [ @@ -835,14 +842,23 @@ export class factory { return arkts.factory.updateClassDeclaration(node, newClassDef); } + /** + * transform class definition with , `@ObservedV2` and `@Trace`, `@Observed` or `@Trace`. + */ static updateObservedTrackClassDef(node: arkts.ClassDefinition): arkts.ClassDefinition { const isObserved: boolean = hasDecorator(node, DecoratorNames.OBSERVED); + const isObservedV2: boolean = hasDecorator(node, DecoratorNames.OBSERVED_V2); const classHasTrack: boolean = node.body.some( (member) => arkts.isClassProperty(member) && hasDecorator(member, DecoratorNames.TRACK) ); - if (!isObserved && !classHasTrack) { + const classHasTrace: boolean = node.body.some( + (member) => arkts.isClassProperty(member) && hasDecorator(member, DecoratorNames.TRACE) + ); + const className: string | undefined = node.ident?.name; + if (!className || !(isObserved || classHasTrack || isObservedV2)) { return node; } + const ObservedAnno: ObservedAnnoInfo = { isObserved, classHasTrack, isObservedV2, classHasTrace, className }; const updateClassDef: arkts.ClassDefinition = arkts.factory.updateClassDefinition( node, node.ident, @@ -851,23 +867,15 @@ export class factory { [ ...node.implements, arkts.TSClassImplements.createTSClassImplements( - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart( - arkts.factory.createIdentifier(StateManagementTypes.OBSERVED_OBJECT) - ) - ) + UIFactory.createTypeReferenceFromString(StateManagementTypes.OBSERVED_OBJECT) ), arkts.TSClassImplements.createTSClassImplements( - arkts.factory.createTypeReference( - arkts.factory.createTypeReferencePart( - arkts.factory.createIdentifier(StateManagementTypes.SUBSCRIBED_WATCHES) - ) - ) + UIFactory.createTypeReferenceFromString(StateManagementTypes.SUBSCRIBED_WATCHES) ), ], undefined, node.super, - factory.observedTrackPropertyMembers(classHasTrack, node, isObserved), + factory.observedTrackPropertyMembers(node, ObservedAnno), node.modifiers, arkts.classDefinitionFlags(node) ); @@ -876,38 +884,70 @@ export class factory { } static observedTrackPropertyMembers( - classHasTrack: boolean, definition: arkts.ClassDefinition, - isObserved: boolean + ObservedAnno: ObservedAnnoInfo ): arkts.AstNode[] { const watchMembers: arkts.AstNode[] = PropertyFactory.createWatchMembers(); - const v1RenderIdMembers: arkts.AstNode[] = PropertyFactory.createV1RenderIdMembers(); - const conditionalAddRef: arkts.MethodDefinition = PropertyFactory.conditionalAddRef(); + const v1RenderIdMembers: arkts.AstNode[] = PropertyFactory.createV1RenderIdMembers(ObservedAnno.isObservedV2); + const conditionalAddRef: arkts.MethodDefinition = PropertyFactory.conditionalAddRef(ObservedAnno.isObservedV2); const getters: arkts.MethodDefinition[] = getGettersFromClassDecl(definition); const classScopeInfo: ClassScopeInfo = { - isObserved: isObserved, - classHasTrack: classHasTrack, + isObserved: ObservedAnno.isObserved, + classHasTrack: ObservedAnno.classHasTrack, + isObservedV2: ObservedAnno.isObservedV2, + classHasTrace: ObservedAnno.classHasTrace, + className: ObservedAnno.className, getters: getters, }; - const propertyTranslators: ObservedTrackTranslator[] = filterDefined( - definition.body.map((it) => classifyObservedTrack(it, classScopeInfo)) + const translators: (ObservedTranslator | MethodTranslator)[] = filterDefined( + definition.body.map((it) => classifyInObservedClass(it, classScopeInfo)) ); - const propertyMembers = propertyTranslators.map((translator) => translator.translateMember()); - const nonClassPropertyOrGetter: arkts.AstNode[] = definition.body.filter( - (member) => - !arkts.isClassProperty(member) && - !( - arkts.isMethodDefinition(member) && - arkts.hasModifierFlag(member, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_GETTER) - ) - ); - return [ + const metaProperty: arkts.ClassProperty[] = ObservedAnno.classHasTrack + ? [] + : [PropertyFactory.createMetaInObservedClass()]; + const propertyMembers = translators.map((translator) => translator.translateMember()); + const restMembers: arkts.AstNode[] = getNoTransformationMembersInClass(definition, ObservedAnno); + const returnNodes = [ ...[...watchMembers, ...v1RenderIdMembers, conditionalAddRef], - ...(classHasTrack ? [] : [PropertyFactory.createMetaInObservedClass()]), + ...(ObservedAnno.isObserved ? metaProperty : []), ...collect(...propertyMembers), - ...nonClassPropertyOrGetter, + ...restMembers, ...classScopeInfo.getters, ]; + return ObservedAnno.isObservedV2 + ? returnNodes.concat(this.transformObservedV2Constuctor(definition, classScopeInfo.className)) + : returnNodes; + } + + static transformObservedV2Constuctor(definition: arkts.ClassDefinition, className: string): arkts.MethodDefinition { + const addConstructorNodes: arkts.AstNode[] = PropertyCache.getInstance().getConstructorBody(className); + let originConstructorMethod: arkts.MethodDefinition | undefined = definition.body.find( + (it) => + arkts.isMethodDefinition(it) && + isKnownMethodDefinition(it, CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI) + ) as arkts.MethodDefinition | undefined; + if (!originConstructorMethod) { + return UIFactory.createMethodDefinition({ + key: arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI), + function: { + key: arkts.factory.createIdentifier(CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI), + body: arkts.factory.createBlock(addConstructorNodes), + flags: arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_CONSTRUCTOR, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_CONSTRUCTOR, + }, + kind: arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_CONSTRUCTOR, + modifiers: arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_CONSTRUCTOR, + }); + } + + const originBody = originConstructorMethod.scriptFunction.body as arkts.BlockStatement | undefined; + return UIFactory.updateMethodDefinition(originConstructorMethod, { + function: { + body: originBody + ? arkts.factory.updateBlock(originBody, [...originBody.statements, ...addConstructorNodes]) + : arkts.factory.createBlock(addConstructorNodes), + }, + }); } /* diff --git a/arkui-plugins/ui-plugins/struct-translators/utils.ts b/arkui-plugins/ui-plugins/struct-translators/utils.ts index fb952880f566f6829ecb3fc49d8f56872e93f128..f133af551112f6131213d061d6b8e20e382766ea 100644 --- a/arkui-plugins/ui-plugins/struct-translators/utils.ts +++ b/arkui-plugins/ui-plugins/struct-translators/utils.ts @@ -16,7 +16,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as arkts from '@koalaui/libarkts'; -import { CustomComponentInfo, CustomDialogNames } from '../utils'; +import { CustomComponentInfo, CustomComponentNames, CustomDialogNames, isKnownMethodDefinition } from '../utils'; import { matchPrefix } from '../../common/arkts-utils'; import { ARKUI_IMPORT_PREFIX_NAMES, @@ -27,10 +27,12 @@ import { RESOURCE_TYPE, InnerComponentNames, ARKUI_FOREACH_SOURCE_NAME, + DecoratorNames, } from '../../common/predefines'; import { DeclarationCollector } from '../../common/declaration-collector'; import { ProjectConfig } from '../../common/plugin-context'; import { LogCollector } from '../../common/log-collector'; +import { hasDecorator } from '../../ui-plugins/property-translators/utils'; export type ScopeInfoCollection = { customComponents: CustomComponentScopeInfo[]; @@ -69,6 +71,18 @@ export interface DialogControllerInfo { parent?: arkts.AstNode; } +export interface ObservedAnnoInfo { + className: string; + isObserved: boolean; + isObservedV2: boolean; + classHasTrace: boolean; + classHasTrack: boolean; +} + +export type ClassScopeInfo = ObservedAnnoInfo & { + getters: arkts.MethodDefinition[]; +}; + export function getResourceParams(id: number, type: number, params: arkts.Expression[]): ResourceParameter { return { id, type, params }; } @@ -598,3 +612,23 @@ export function getControllerName(node: arkts.ETSNewClassInstanceExpression): Di return { controllerName, isClassProperty, parent }; } + +export function getNoTransformationMembersInClass( + definition: arkts.ClassDefinition, + ObservedAnno: ObservedAnnoInfo +): arkts.AstNode[] { + return definition.body.filter( + (member) => + !arkts.isClassProperty(member) && + !( + arkts.isMethodDefinition(member) && + arkts.hasModifierFlag(member, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_GETTER) + ) && + !( + ObservedAnno.isObservedV2 && + arkts.isMethodDefinition(member) && + (hasDecorator(member, DecoratorNames.COMPUTED) || + isKnownMethodDefinition(member, CustomComponentNames.COMPONENT_CONSTRUCTOR_ORI)) + ) + ); +} diff --git a/arkui-plugins/ui-plugins/ui-factory.ts b/arkui-plugins/ui-plugins/ui-factory.ts index 9c204231a786f1ce3e43566236bd9708801668bf..c8c7cfc6f1689a4adbd58177a3305f8f8ba06907 100644 --- a/arkui-plugins/ui-plugins/ui-factory.ts +++ b/arkui-plugins/ui-plugins/ui-factory.ts @@ -441,4 +441,33 @@ export class factory { newNode.isFromExternal ); } + + /** + * Generate member expression, e.g. `.`. + * + * @param method method definition node + */ + static generateMemberExpression(object: arkts.AstNode, property: string, optional = false): arkts.MemberExpression { + return arkts.factory.createMemberExpression( + object, + arkts.factory.createIdentifier(property), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + optional + ); + } + + /** + * create `: = ` as parameter + */ + static createParameterDeclaration( + keyName: string, + typeName: string, + initializers?: arkts.AstNode + ): arkts.ETSParameterExpression { + return arkts.factory.createParameterDeclaration( + arkts.factory.createIdentifier(keyName, this.createTypeReferenceFromString(typeName)), + initializers + ); + } } diff --git a/arkui-plugins/ui-plugins/utils.ts b/arkui-plugins/ui-plugins/utils.ts index 5e541ac7fce9671293451058c575fca22971677f..5bcfa691802aa287fc5fb41092f0ed976b0323d3 100644 --- a/arkui-plugins/ui-plugins/utils.ts +++ b/arkui-plugins/ui-plugins/utils.ts @@ -18,9 +18,11 @@ import { matchPrefix } from '../common/arkts-utils'; import { ARKUI_IMPORT_PREFIX_NAMES, CUSTOM_DIALOG_CONTROLLER_SOURCE_NAME, + DecoratorNames, StructDecoratorNames, } from '../common/predefines'; import { DeclarationCollector } from '../common/declaration-collector'; +import { hasDecorator } from './property-translators/utils'; export enum CustomComponentNames { COMPONENT_BUILD_ORI = 'build', @@ -127,7 +129,8 @@ export function getGettersFromClassDecl(definition: arkts.ClassDefinition): arkt return definition.body.filter( (member) => arkts.isMethodDefinition(member) && - arkts.hasModifierFlag(member, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_GETTER) + arkts.hasModifierFlag(member, arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_GETTER) && + !hasDecorator(member, DecoratorNames.COMPUTED) ) as arkts.MethodDefinition[]; } @@ -176,6 +179,11 @@ export type ComponentType = { hasCustomLayout: boolean; }; +export type ClassInfo = { + className: string; + isFromStruct: boolean; +}; + export function isCustomComponentAnnotation( anno: arkts.AnnotationUsage, decoratorName: StructDecoratorNames, @@ -286,10 +294,11 @@ export function isCustomComponentClass(node: arkts.ClassDeclaration, scopeInfo: export function isCustomComponentInterface(node: arkts.TSInterfaceDeclaration): boolean { const checkPrefix = !!node.id?.name.startsWith(CustomComponentNames.COMPONENT_INTERFACE_PREFIX); - const checkComponent = node.annotations.some((anno) => - isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT) || - isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT_V2) || - isCustomComponentAnnotation(anno, StructDecoratorNames.CUSTOMDIALOG) + const checkComponent = node.annotations.some( + (anno) => + isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT) || + isCustomComponentAnnotation(anno, StructDecoratorNames.COMPONENT_V2) || + isCustomComponentAnnotation(anno, StructDecoratorNames.CUSTOMDIALOG) ); return checkPrefix && checkComponent; } @@ -347,7 +356,7 @@ export function getComponentExtendsName(annotations: CustomComponentAnontations, if (!!annotations.customdialog) { componentType.hasCustomDialog ||= true; return CustomComponentNames.BASE_CUSTOM_DIALOG_NAME; - } + } if (!!annotations.componentV2) { componentType.hasComponentV2 ||= true; return CustomComponentNames.COMPONENT_V2_CLASS_NAME; @@ -355,3 +364,11 @@ export function getComponentExtendsName(annotations: CustomComponentAnontations, componentType.hasComponent ||= true; return CustomComponentNames.COMPONENT_CLASS_NAME; } + +export function computedField(name: string): string { + return `__computed_${name}`; +} + +export function monitorField(name: string): string { + return `__monitor_${name}`; +} \ No newline at end of file