From 7ccb6969277e1335ec7208908db799f18f84beca Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Mon, 4 Aug 2025 21:02:45 +0700 Subject: [PATCH 1/9] WIP --- .../arkui/src/ArkStateProperties.ets | 2 +- ets-tests/ets/environment-tests/Pages.ets | 4 + .../pages/param/ParamBasicTest.ets | 70 ++++ .../suites/ParamDecorator.ets | 42 +++ .../ui-plugins/src/component-transformer.ts | 13 +- .../ui-plugins/src/property-transformers.ts | 315 ++++++++++++------ ui2abc/ui-plugins/src/utils.ts | 3 +- 7 files changed, 348 insertions(+), 101 deletions(-) create mode 100644 ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets create mode 100644 ets-tests/ets/environment-tests/suites/ParamDecorator.ets diff --git a/arkoala-arkts/arkui/src/ArkStateProperties.ets b/arkoala-arkts/arkui/src/ArkStateProperties.ets index ddd76c6af..cf4a221da 100644 --- a/arkoala-arkts/arkui/src/ArkStateProperties.ets +++ b/arkoala-arkts/arkui/src/ArkStateProperties.ets @@ -120,7 +120,7 @@ export class LinkDecoratorProperty implements SubscribedAbstractProperty< } linkTo(maybeProperty: SubscribedAbstractProperty | Value | undefined): void { - if (!maybeProperty) throw new Error(`${this.name} must be linked with another property`) + if (maybeProperty == undefined) throw new Error(`${this.name} must be linked with another property`) const property = maybeProperty! // Improve: this is to workaround Any considered non-nulish if (!(property instanceof SubscribedAbstractProperty)) throw new Error('Property must be passed, got') if (this.property) throw new Error(`${this.name} is already linked with some property`) diff --git a/ets-tests/ets/environment-tests/Pages.ets b/ets-tests/ets/environment-tests/Pages.ets index 1d57cc945..ecab442f6 100644 --- a/ets-tests/ets/environment-tests/Pages.ets +++ b/ets-tests/ets/environment-tests/Pages.ets @@ -32,6 +32,7 @@ import { suiteRenderServiceNode } from './suites/RenderServiceNodeTest'; import { suiteTraceDecorator } from './suites/TraceDecoratorTests'; import { suiteComponentV2Decorator } from './suites/ComponentV2DecoratorTests'; import { suiteLocalDecorator } from './suites/LocalDecoratorTests'; +import { suiteParamDecorator } from './suites/ParamDecorator'; import { InitializationFromParent } from "./pages/states/InitializationFromParent" import { StateIncrement } from "./pages/states/StateIncrement" import { StateDecrement } from "./pages/states/StateDecrement" @@ -127,6 +128,7 @@ import { ComponentV2Page2 } from "./pages/componentv2/ComponentV2Page2" import { LocalObjectTest } from "./pages/local/LocalObjectTest" import { ProviderConsumerBaseTest } from "./pages/provider/ProviderConsumerBaseTest" import { EnvironmentTest } from "./pages/storage/EnvironmentTest" +import { ParamBasicTest } from "./pages/param/ParamBasicTest" initModule("./pages/lifecycle/PageCallbacks") initModule("./pages/lifecycle/PageSwitch") @@ -235,6 +237,7 @@ function pageByName(name: string): void { case "LocalObjectTest": LocalObjectTest(); break case "ProviderConsumerBaseTest": ProviderConsumerBaseTest(); break case "EnvironmentTest": EnvironmentTest(); break + case "ParamBasicTest": ParamBasicTest(); break case "": break default: throw new Error(`No test case ${name} provided!`) } @@ -261,5 +264,6 @@ export function Suites(control: TestController) { suiteScreenshotService(control) suiteComponentV2Decorator(control) suiteLocalDecorator(control) + suiteParamDecorator(control) }) } \ No newline at end of file diff --git a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets new file mode 100644 index 000000000..9df48775d --- /dev/null +++ b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets @@ -0,0 +1,70 @@ +/* + * 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 { TestComponent, uiLog } from '@ohos.arkui' + +@Entry +@ComponentV2 +struct ParamBasicTest { + @Local count: number = 0 + @Local message: string = "Hello" + @Local flag: boolean = true + + build() { + TestComponent({}) { + uiLog(`Local ${this.count}`) + uiLog(`Local ${this.message}`) + uiLog(`Local ${this.flag}`) + } + + TestComponent({ id: 10 }) + .onChange(() => { + // Changes to the data source will be synchronized to the child component. + this.count++ + this.message += " World" + this.flag = !this.flag + }) + + Child({ + count: $count, + message: $message, + flag: $flag, + changeCount: (x: number) => { + this.count = x + } + }) + } +} + +@ComponentV2 +struct Child { + @Require @Param count: number + @Require @Param message: string + @Require @Param flag: boolean + @Require @Event changeCount: (x: number) => void + + build() { + TestComponent({}) { + uiLog(`Param ${this.count}`) + uiLog(`Param ${this.message}`) + uiLog(`Param ${this.flag}`) + } + + TestComponent({ id: 11 }) + .onChange(() => { + this.changeCount(100) + }) + } +} diff --git a/ets-tests/ets/environment-tests/suites/ParamDecorator.ets b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets new file mode 100644 index 000000000..1dca309e7 --- /dev/null +++ b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { testPageOnLoad, testPageOnChange } from "../../utils" +import { TestController } from "../../TestHarness" +import { assert, suite, test } from "@koalaui/harness" + +export function suiteParamDecorator(control: TestController) { + suite("ParamDecoratorTest", () => { + test("@Param decorator basic test", () => { + testPageOnChange(control, "ParamBasicTest", [10, 11], [ + "Local 0", + "Local Hello", + "Local true", + "Param 0", + "Param Hello", + "Param true", + + "Local 1", + "Local Hello World", + "Local false", + "Param 1", + "Param Hello World", + "Param false", + "Local 100", + "Param 100" + ]) + }) + }) +} diff --git a/ui2abc/ui-plugins/src/component-transformer.ts b/ui2abc/ui-plugins/src/component-transformer.ts index 02fa48219..25d3a417b 100644 --- a/ui2abc/ui-plugins/src/component-transformer.ts +++ b/ui2abc/ui-plugins/src/component-transformer.ts @@ -49,7 +49,10 @@ import { fieldOf, LocalPropertyTransformer, ProviderTransformer, - ConsumerTransformer + ConsumerTransformer, + ParamPropertyTransformer, + EventPropertyTransformer, + RequirePropertyTransformer } from "./property-transformers"; import { annotation, isAnnotation, classMethods } from "./common/arkts-utils"; import { DecoratorNames, DecoratorParameters, hasBuilderDecorator, hasEntryAnnotation } from "./utils"; @@ -570,7 +573,10 @@ export class ComponentTransformer extends arkts.AbstractVisitor { new ConsumeTransformer(), new ConsumerTransformer(), new BuilderParamTransformer(), - new LocalPropertyTransformer() + new LocalPropertyTransformer(), + new ParamPropertyTransformer(), + new EventPropertyTransformer(), + new RequirePropertyTransformer() ] verifyStructProperty(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty) { @@ -581,7 +587,7 @@ export class ComponentTransformer extends arkts.AbstractVisitor { return !ComponentV2Config.allowedDecorators.some(it => isDecoratorAnnotation(anno, it)) }) if (unsuitable) { - throw Error(`@${getAnnotationName(unsuitable)} decorator cannot be used with @${DecoratorNames.COMPONENT_V2}`) + throw new Error(`@${getAnnotationName(unsuitable)} decorator cannot be used with @${DecoratorNames.COMPONENT_V2}`) } } } @@ -830,6 +836,7 @@ class ComponentV2Config { DecoratorNames.EVENT, DecoratorNames.PROVIDER, DecoratorNames.CONSUMER, + DecoratorNames.REQUIRE, ] static readonly freezableDecoratorPropName = "freezeWhenInactive" diff --git a/ui2abc/ui-plugins/src/property-transformers.ts b/ui2abc/ui-plugins/src/property-transformers.ts index fc8813bae..7c7c90a42 100644 --- a/ui2abc/ui-plugins/src/property-transformers.ts +++ b/ui2abc/ui-plugins/src/property-transformers.ts @@ -16,8 +16,21 @@ import * as arkts from "@koalaui/libarkts" import { Es2pandaTokenType } from "@koalaui/libarkts" -import { DecoratorNames, DecoratorParameters, findDecorator, hasDecorator } from "./utils" -import { CustomComponentNames, getDecoratorPackage, getRuntimePackage, Importer, ImportingTransformer, InternalAnnotations } from "./utils" +import { + createThrowError, + CustomComponentNames, + DecoratorNames, + DecoratorParameters, + findDecorator, + getAnnotationName, + getDecoratorPackage, + getRuntimePackage, + hasDecorator, + Importer, + ImportingTransformer, + InternalAnnotations, + isDecoratorAnnotation +} from "./utils" import { annotation, classMethods, isAnnotation } from "./common/arkts-utils" export interface PropertyTransformer extends ImportingTransformer { @@ -71,6 +84,7 @@ function createWrappedProperty( clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, wrapperTypeName: string, + isPropertyReadOnly: boolean = false, ctorParams: arkts.Expression[] = [] ): arkts.ClassElement[] { const wrapperTypeForValue = createWrapperType(wrapperTypeName, property.typeAnnotation!) @@ -118,71 +132,72 @@ function createWrappedProperty( arkts.factory.createIdentifier(name), [] ) - - const setterStatements: arkts.Statement[] = [ - arkts.factory.createExpressionStatement( - arkts.factory.createCallExpression( - arkts.factory.createMemberExpression( + const getterOverloads: arkts.MethodDefinition[] = [] + if (!isPropertyReadOnly) { + const setterStatements: arkts.Statement[] = [ + arkts.factory.createExpressionStatement( + arkts.factory.createCallExpression( arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier(backingFieldNameOf(property)), + arkts.factory.createMemberExpression( + arkts.factory.createThisExpression(), + arkts.factory.createIdentifier(backingFieldNameOf(property)), + arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + false, + false + ), + arkts.factory.createIdentifier("set"), arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, false, false ), - arkts.factory.createIdentifier("set"), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, + [ + arkts.factory.createIdentifier("value"), + ], + undefined, false, - false - ), - [ - arkts.factory.createIdentifier("value"), - ], - undefined, - false, - false, - undefined, - ) - ) - ] - const watchMethods = property.annotations.filter(isWatchDecorator).map(getWatchParameter) - for (let i = 0; i < watchMethods.length; i++) { - setterStatements.push(createWatchCall(clazz, watchMethods[i], name)) - } - - const setterFunction = arkts.factory.createScriptFunction( - arkts.factory.createBlockStatement(setterStatements), - undefined, - [ - arkts.factory.createETSParameterExpression( - arkts.factory.createIdentifier("value", property.typeAnnotation?.clone()), - false, - undefined, + false, + undefined, + ) ) - ], - undefined, - true, - arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD | arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER | arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_OVERLOAD, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - arkts.factory.createIdentifier(name), - [] - ) + ] + const watchMethods = property.annotations.filter(isWatchDecorator).map(getWatchParameter) + for (let i = 0; i < watchMethods.length; i++) { + setterStatements.push(createWatchCall(clazz, watchMethods[i], name)) + } - let setter = arkts.factory.createMethodDefinition( - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, - arkts.factory.createIdentifier(name), - arkts.factory.createFunctionExpression(arkts.factory.createIdentifier(name), setterFunction), - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - false - ) + const setterFunction = arkts.factory.createScriptFunction( + arkts.factory.createBlockStatement(setterStatements), + undefined, + [ + arkts.factory.createETSParameterExpression( + arkts.factory.createIdentifier("value", property.typeAnnotation?.clone()), + false, + undefined, + ) + ], + undefined, + true, + arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD | arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER | arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_OVERLOAD, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + arkts.factory.createIdentifier(name), + [] + ) + getterOverloads.push(arkts.factory.createMethodDefinition( + arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, + arkts.factory.createIdentifier(name), + arkts.factory.createFunctionExpression(arkts.factory.createIdentifier(name), setterFunction), + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false + )) + } let getter = arkts.factory.createMethodDefinition( arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, arkts.factory.createIdentifier(name), arkts.factory.createFunctionExpression(arkts.factory.createIdentifier(name), getterFunction), arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, false, - [setter] + getterOverloads ) return [backingField, getter] @@ -192,7 +207,8 @@ function addTrackablePropertyTo( result: arkts.ClassElement[], clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, - propertyTypeName: string + propertyTypeName: string, + isPropertyReadOnly: boolean ) { const valueType = property.typeAnnotation if (!valueType) throw new Error(`@${propertyTypeName}: type is not specified for ${property.id?.name}`) @@ -241,39 +257,43 @@ function addTrackablePropertyTo( arkts.factory.createIdentifier(propertyName), [] ) - const setterFunction = arkts.factory.createScriptFunction( - arkts.factory.createBlockStatement([ - thisPropertyMethodCall(property, "set", [arkts.factory.createIdentifier("value")]) - ]), - undefined, - [ - arkts.factory.createETSParameterExpression( - arkts.factory.createIdentifier("value", valueType.clone()), - false, - undefined, + const getterOverloads: arkts.MethodDefinition[] = [] + if (!isPropertyReadOnly) { + const setterFunction = arkts.factory.createScriptFunction( + arkts.factory.createBlockStatement([ + thisPropertyMethodCall(property, "set", [arkts.factory.createIdentifier("value")]) + ]), + undefined, + [ + arkts.factory.createETSParameterExpression( + arkts.factory.createIdentifier("value", valueType.clone()), + false, + undefined, + ) + ], + undefined, + true, + arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD | arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER | arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_OVERLOAD, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + arkts.factory.createIdentifier(propertyName), + [] + ) + getterOverloads.push(arkts.factory.createMethodDefinition( + arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, + arkts.factory.createIdentifier(propertyName), + arkts.factory.createFunctionExpression(arkts.factory.createIdentifier(propertyName), setterFunction), + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, + false ) - ], - undefined, - true, - arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_METHOD | arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_SETTER | arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_OVERLOAD, - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - arkts.factory.createIdentifier(propertyName), - [] - ) - const setter = arkts.factory.createMethodDefinition( - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET, - arkts.factory.createIdentifier(propertyName), - arkts.factory.createFunctionExpression(arkts.factory.createIdentifier(propertyName), setterFunction), - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, - false - ) + ) + } const getter = arkts.factory.createMethodDefinition( arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET, arkts.factory.createIdentifier(propertyName), arkts.factory.createFunctionExpression(arkts.factory.createIdentifier(propertyName), getterFunction), arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PUBLIC, false, - [setter] + getterOverloads ) result.push(backingField) result.push(getter) @@ -341,6 +361,7 @@ export abstract class PropertyTransformerBase implements PropertyTransformer { constructor(public decoratorName: DecoratorNames, public className: string) { } check(property: arkts.ClassProperty): boolean { + checkRequireDecoratorUsage(property) return hasDecorator(property, this.decoratorName) } collectImports(importer: Importer): void { @@ -349,7 +370,7 @@ export abstract class PropertyTransformerBase implements PropertyTransformer { applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { } applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void { - addTrackablePropertyTo(result, clazz, property, this.className) + addTrackablePropertyTo(result, clazz, property, this.className, false) } abstract applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void applyDisposeStruct(property: arkts.ClassProperty, result: arkts.Statement[]): void { @@ -369,6 +390,7 @@ export class StateTransformer extends PropertyTransformerBase { result.push(createOptionalClassProperty(property.id!.name, property)) } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", [initializerOf(property), property.value!])) } applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { @@ -378,6 +400,7 @@ export class StateTransformer extends PropertyTransformerBase { export class PlainPropertyTransformer implements PropertyTransformer { check(property: arkts.ClassProperty): boolean { + checkRequireDecoratorUsage(property) return property.annotations.length == 0 } collectImports(imports: Importer): void { @@ -390,6 +413,7 @@ export class PlainPropertyTransformer implements PropertyTransformer { result.push(...createWrappedProperty(clazz, property, "PlainStructProperty")) } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", [initializerOf(property)])) } applyDisposeStruct(property: arkts.ClassProperty, result: arkts.Statement[]): void { @@ -401,32 +425,38 @@ export class PlainPropertyTransformer implements PropertyTransformer { } } -export class LinkTransformer extends PropertyTransformerBase { - constructor() { - super(DecoratorNames.LINK, "LinkDecoratorProperty") +class LinkablePropertyTransformer extends PropertyTransformerBase { + constructor(decoratorName: DecoratorNames) { + super(decoratorName, "LinkDecoratorProperty") } applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { - result.push(arkts.factory.createClassProperty( - arkts.factory.createIdentifier(property.id?.name!), - undefined, - createWrapperType("SubscribedAbstractProperty", property.typeAnnotation!, true), - arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL, - false + result.push(arkts.factory.createClassProperty( + arkts.factory.createIdentifier(property.id?.name!), + undefined, + createWrapperType("SubscribedAbstractProperty", property.typeAnnotation!, true), + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL, + false )) - //result.push(createOptionalClassProperty(property.id!.name, property)) } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "linkTo", [initializerOf(property)])) } - applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { - addPropertyRecordTo(result, property) - } collectImports(imports: Importer): void { imports.add("SubscribedAbstractProperty", getDecoratorPackage()) imports.add("LinkDecoratorProperty", getDecoratorPackage()) } } +export class LinkTransformer extends LinkablePropertyTransformer { + constructor() { + super(DecoratorNames.LINK) + } + applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { + addPropertyRecordTo(result, property) + } +} + function withStorageKey(expressions: arkts.Expression[], property: arkts.ClassProperty, decorator: DecoratorNames): arkts.Expression[] { const props = property.annotations.find(usage => isAnnotation(usage, decorator))!.properties if (props.length > 1) throw new Error(`@${decorator}: only one parameter is expected`) @@ -444,6 +474,7 @@ export class StorageLinkTransformer extends PropertyTransformerBase { super(DecoratorNames.STORAGE_LINK, "StorageLinkDecoratorProperty") } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", withStorageKey([property.value!], property, this.decoratorName))) } } @@ -467,6 +498,7 @@ export class PropTransformer extends PropertyTransformerBase { result.push(createOptionalClassProperty(property.id!.name, property)) } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", [initializerOf(property), property.value ?? arkts.factory.createUndefinedLiteral()])) } applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { @@ -572,6 +604,7 @@ export class ProvideTransformer extends PropertyTransformerBase { result.push(createOptionalClassProperty(property.id!.name, property)) } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", [initializerOf(property), property.value!])) } applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { @@ -600,7 +633,7 @@ export class ProviderTransformer extends PropertyTransformerBase { applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { if (property.value == undefined) { - throw new Error(`@Provider decorator requires an initial value for '${property.id?.name}'`) + throwPropertyRequiresInit(this.decoratorName, property) } result.push( thisPropertyMethodCall(property, "init", [arkts.factory.createUndefinedLiteral(), @@ -633,18 +666,23 @@ export class ConsumerTransformer extends PropertyTransformerBase { } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { if (property.value == undefined) { - throw new Error(`@Consumer decorator requires an initial value for '${property.id?.name}'`) + throwPropertyRequiresInit(this.decoratorName, property) } result.push( thisPropertyMethodCall(property, "init", - withStorageKey([arkts.factory.createTSAsExpression(property.value, property.typeAnnotation, false)], property, this.decoratorName)) + withStorageKey([ + arkts.isArrowFunctionExpression(property.value) + ? arkts.factory.createTSAsExpression(property.value, property.typeAnnotation, false) + : property.value! + ], property, this.decoratorName)) ) } } export class BuilderParamTransformer implements PropertyTransformer { check(property: arkts.ClassProperty): boolean { + checkRequireDecoratorUsage(property) return hasDecorator(property, DecoratorNames.BUILDER_PARAM) } collectImports(result: Importer): void { @@ -666,6 +704,7 @@ export class BuilderParamTransformer implements PropertyTransformer { result.push(backing) } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + checkRequiredProperty(property, result) result.push(applyInitStatement(property)) } applyDisposeStruct(property: arkts.ClassProperty, result: arkts.Statement[]): void { @@ -691,6 +730,54 @@ export class LocalPropertyTransformer extends PropertyTransformerBase { } } +export class ParamPropertyTransformer extends LinkablePropertyTransformer { + constructor() { + super(DecoratorNames.PARAM) + } + applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void { + addTrackablePropertyTo(result, clazz, property, this.className, true) + } + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]) { + checkRequiredProperty(property, result) + // Skip initialization if property is marked as @Require + if (property.value == undefined && !hasDecorator(property, DecoratorNames.REQUIRE)) { + throwPropertyRequiresInit(this.decoratorName, property) + } + super.applyInitializeStruct(localStorage, property, result); + } +} + +export class EventPropertyTransformer extends PlainPropertyTransformer { + check(property: arkts.ClassProperty): boolean { + checkRequireDecoratorUsage(property) + return hasDecorator(property, DecoratorNames.EVENT) + } + applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void { + result.push(...createWrappedProperty(clazz, property, "PlainStructProperty", true)) + } + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + checkRequiredProperty(property, result) + if (property.value == undefined && !hasDecorator(property, DecoratorNames.REQUIRE)) { + throwPropertyRequiresInit(DecoratorNames.EVENT, property) + } + super.applyInitializeStruct(localStorage, property, result) + } +} + +// @Require does not transform the property +export class RequirePropertyTransformer implements PropertyTransformer { + check(property: arkts.ClassProperty): boolean { + return property.annotations.length == 1 && hasDecorator(property, DecoratorNames.REQUIRE) + } + collectImports(imports: Importer): void {} + applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void {} + applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void {} + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void {} + applyDisposeStruct(property: arkts.ClassProperty, result: arkts.Statement[]): void {} + applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void {} + applyBuild(property: arkts.ClassProperty, result: arkts.Statement[]): void {} +} + function applyInitStatement(property: arkts.ClassProperty): arkts.Statement { const name = property.id?.name! const initDeclaration = arkts.factory.createVariableDeclaration( @@ -729,3 +816,39 @@ export function isOptionBackedByProperty(property: arkts.ClassProperty): boolean export function isOptionBackedByPropertyName(decorator: string): boolean { return decorator == DecoratorNames.LINK } + +function throwPropertyRequiresInit(decorator: DecoratorNames, property: arkts.ClassProperty): string { + throw new Error(`@${decorator} decorator requires an initial value for the class property '${property.id!.name}'`) +} + +// If class property is marked as @Require, then init value must exist +function checkRequiredProperty(property: arkts.ClassProperty, result: arkts.Statement[]): void { + // Needs to be reworked for compile-time checking + if (hasDecorator(property, DecoratorNames.REQUIRE)) { + result.push( + arkts.factory.createIfStatement( + arkts.factory.createBinaryExpression( + initializerOf(property), + arkts.factory.createUndefinedLiteral(), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_EQUAL), + createThrowError(`The '${property.id?.name!}' property requires initialization in parent component`), + ) + ) + } +} + +function checkRequireDecoratorUsage(property: arkts.ClassProperty) { + if (hasDecorator(property, DecoratorNames.REQUIRE)) { + const unsuitable = property + .annotations + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.BUILDER_PARAM)) + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.EVENT)) + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.PARAM)) + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.PROP)) + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.REQUIRE)) + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.STATE)) + if (unsuitable.length) { + throw new Error(`@${getAnnotationName(unsuitable[0])} cannot be used with @Require decorator`) + } + } +} \ No newline at end of file diff --git a/ui2abc/ui-plugins/src/utils.ts b/ui2abc/ui-plugins/src/utils.ts index a5b34ae00..7d9d425a3 100644 --- a/ui2abc/ui-plugins/src/utils.ts +++ b/ui2abc/ui-plugins/src/utils.ts @@ -304,7 +304,8 @@ export enum DecoratorNames { LOCAL = "Local", ONCE = "Once", EVENT = "Event", - PARAM = "Param" + PARAM = "Param", + REQUIRE = "Require" } const DECORATOR_NAMES_SET = new Set(Object.values(DecoratorNames)); -- Gitee From 7106bf90b4f4d59a3412d073ee881b5819c92ea3 Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Mon, 11 Aug 2025 13:26:02 +0700 Subject: [PATCH 2/9] Added @Once decorator support --- .../arkui/sdk/ArkStateProperties.ets | 11 ++++ .../arkui/src/ArkStateProperties.ets | 18 ++++++ .../pages/param/ParamBasicTest.ets | 9 ++- .../suites/ParamDecorator.ets | 6 +- .../ui-plugins/src/property-transformers.ts | 58 +++++++++++++++++-- 5 files changed, 95 insertions(+), 7 deletions(-) diff --git a/arkoala-arkts/arkui/sdk/ArkStateProperties.ets b/arkoala-arkts/arkui/sdk/ArkStateProperties.ets index f28e2c0f1..f1069cdd5 100644 --- a/arkoala-arkts/arkui/sdk/ArkStateProperties.ets +++ b/arkoala-arkts/arkui/sdk/ArkStateProperties.ets @@ -162,3 +162,14 @@ export declare class LocalStoragePropDecoratorProperty implements Subscri unsubscribe(listener: () => void): void aboutToBeDeleted(): void } + +export declare class ParamDecoratorProperty implements SubscribedAbstractProperty { + constructor(name: string, listener?: () => void) + init(value: SubscribedAbstractProperty | Value | undefined, isOnce: boolean): void + info(): string + get(): Value + set(value: Value): void + subscribe(listener: () => void): void + unsubscribe(listener: () => void): void + aboutToBeDeleted(): void +} \ No newline at end of file diff --git a/arkoala-arkts/arkui/src/ArkStateProperties.ets b/arkoala-arkts/arkui/src/ArkStateProperties.ets index cf4a221da..f3773baa5 100644 --- a/arkoala-arkts/arkui/src/ArkStateProperties.ets +++ b/arkoala-arkts/arkui/src/ArkStateProperties.ets @@ -318,3 +318,21 @@ export class LocalStoragePropDecoratorProperty extends LinkDecoratorPrope this.linkTo(storage.setAndProp(storageKey ?? this.info(), value)) } } + +export class ParamDecoratorProperty extends LinkDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + init(value: SubscribedAbstractProperty | Value | undefined, isOnce: boolean): void { + if (value instanceof SubscribedAbstractProperty) { + super.linkTo(isOnce ? this.createState((value as SubscribedAbstractProperty).get()) : value) + } else { + super.linkTo(this.createState(value)) + } + } + private createState(initial: Value | undefined): StateDecoratorProperty { + const state = new StateDecoratorProperty(this.info()) + state.init(undefined, initial) + return state + } +} diff --git a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets index 9df48775d..96f566700 100644 --- a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets +++ b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets @@ -43,7 +43,8 @@ struct ParamBasicTest { flag: $flag, changeCount: (x: number) => { this.count = x - } + }, + onceParamField: $message }) } } @@ -54,17 +55,23 @@ struct Child { @Require @Param message: string @Require @Param flag: boolean @Require @Event changeCount: (x: number) => void + @Require @Once @Param onceParamField: string build() { TestComponent({}) { uiLog(`Param ${this.count}`) uiLog(`Param ${this.message}`) uiLog(`Param ${this.flag}`) + uiLog(`Param(@Once) ${this.onceParamField}`) } TestComponent({ id: 11 }) .onChange(() => { this.changeCount(100) }) + TestComponent({ id: 12 }) + .onChange(() => { + this.onceParamField += " World" + }) } } diff --git a/ets-tests/ets/environment-tests/suites/ParamDecorator.ets b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets index 1dca309e7..b6977320a 100644 --- a/ets-tests/ets/environment-tests/suites/ParamDecorator.ets +++ b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets @@ -20,13 +20,14 @@ import { assert, suite, test } from "@koalaui/harness" export function suiteParamDecorator(control: TestController) { suite("ParamDecoratorTest", () => { test("@Param decorator basic test", () => { - testPageOnChange(control, "ParamBasicTest", [10, 11], [ + testPageOnChange(control, "ParamBasicTest", [10, 11, 12], [ "Local 0", "Local Hello", "Local true", "Param 0", "Param Hello", "Param true", + "Param(@Once) Hello", "Local 1", "Local Hello World", @@ -35,7 +36,8 @@ export function suiteParamDecorator(control: TestController) { "Param Hello World", "Param false", "Local 100", - "Param 100" + "Param 100", + "Param(@Once) Hello World" ]) }) }) diff --git a/ui2abc/ui-plugins/src/property-transformers.ts b/ui2abc/ui-plugins/src/property-transformers.ts index 7c7c90a42..b8d4d2c93 100644 --- a/ui2abc/ui-plugins/src/property-transformers.ts +++ b/ui2abc/ui-plugins/src/property-transformers.ts @@ -362,6 +362,7 @@ export abstract class PropertyTransformerBase implements PropertyTransformer { } check(property: arkts.ClassProperty): boolean { checkRequireDecoratorUsage(property) + checkOnceDecoratorUsage(property) return hasDecorator(property, this.decoratorName) } collectImports(importer: Importer): void { @@ -730,12 +731,22 @@ export class LocalPropertyTransformer extends PropertyTransformerBase { } } -export class ParamPropertyTransformer extends LinkablePropertyTransformer { +export class ParamPropertyTransformer extends PropertyTransformerBase { constructor() { - super(DecoratorNames.PARAM) + super(DecoratorNames.PARAM, "ParamDecoratorProperty"); } applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void { - addTrackablePropertyTo(result, clazz, property, this.className, true) + // @Once decorator allows to change @Param + addTrackablePropertyTo(result, clazz, property, this.className, !hasDecorator(property, DecoratorNames.ONCE)) + } + applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { + result.push(arkts.factory.createClassProperty( + arkts.factory.createIdentifier(property.id?.name!), + undefined, + createWrapperType("SubscribedAbstractProperty", property.typeAnnotation!, true), + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL, + false + )) } applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]) { checkRequiredProperty(property, result) @@ -743,7 +754,29 @@ export class ParamPropertyTransformer extends LinkablePropertyTransformer { if (property.value == undefined && !hasDecorator(property, DecoratorNames.REQUIRE)) { throwPropertyRequiresInit(this.decoratorName, property) } - super.applyInitializeStruct(localStorage, property, result); + + result.push( + arkts.factory.createIfStatement( + arkts.factory.createBinaryExpression( + initializerOf(property), + arkts.factory.createUndefinedLiteral(), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_NOT_EQUAL), + thisPropertyMethodCall(property, + "init", + [ + initializerOf(property), + arkts.factory.createBooleanLiteral(hasDecorator(property, DecoratorNames.ONCE)) + ] + ), + thisPropertyMethodCall(property, + "init", + [ + property.value ? property.value : arkts.factory.createUndefinedLiteral(), + arkts.factory.createBooleanLiteral(hasDecorator(property, DecoratorNames.ONCE)) + ] + ) + ) + ) } } @@ -847,8 +880,25 @@ function checkRequireDecoratorUsage(property: arkts.ClassProperty) { .filter(it => !isDecoratorAnnotation(it, DecoratorNames.PROP)) .filter(it => !isDecoratorAnnotation(it, DecoratorNames.REQUIRE)) .filter(it => !isDecoratorAnnotation(it, DecoratorNames.STATE)) + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.ONCE)) if (unsuitable.length) { throw new Error(`@${getAnnotationName(unsuitable[0])} cannot be used with @Require decorator`) } } +} + +function checkOnceDecoratorUsage(property: arkts.ClassProperty) { + if (hasDecorator(property, DecoratorNames.ONCE)) { + if (!property.annotations.some(it => isDecoratorAnnotation(it, DecoratorNames.PARAM))) { + throw new Error(`@Once must be used with @Param decorator`) + } + const unsuitable = property + .annotations + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.ONCE)) + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.PARAM)) + .filter(it => !isDecoratorAnnotation(it, DecoratorNames.REQUIRE)) + if (unsuitable.length) { + throw new Error(`@${getAnnotationName(unsuitable[0])} cannot be used with @Once decorator`) + } + } } \ No newline at end of file -- Gitee From 606175d93cb224883767efbaaf44e794a9732880 Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Mon, 11 Aug 2025 14:51:11 +0700 Subject: [PATCH 3/9] More tests --- .../ets/environment-tests/pages/param/ParamBasicTest.ets | 8 +++++++- ets-tests/ets/environment-tests/suites/ParamDecorator.ets | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets index 96f566700..391f56b12 100644 --- a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets +++ b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets @@ -44,7 +44,8 @@ struct ParamBasicTest { changeCount: (x: number) => { this.count = x }, - onceParamField: $message + onceParamField: $message, + valueAsExternal: 20 }) } } @@ -56,6 +57,8 @@ struct Child { @Require @Param flag: boolean @Require @Event changeCount: (x: number) => void @Require @Once @Param onceParamField: string + @Param valueAsConst: number = 10 + @Once @Param valueAsExternal: number = 10 build() { TestComponent({}) { @@ -63,6 +66,8 @@ struct Child { uiLog(`Param ${this.message}`) uiLog(`Param ${this.flag}`) uiLog(`Param(@Once) ${this.onceParamField}`) + uiLog(`Param(valueAsConst) ${this.valueAsConst}`) + uiLog(`Param(valueAsExternal) ${this.valueAsExternal}`) } TestComponent({ id: 11 }) @@ -72,6 +77,7 @@ struct Child { TestComponent({ id: 12 }) .onChange(() => { this.onceParamField += " World" + this.valueAsExternal += 10 }) } } diff --git a/ets-tests/ets/environment-tests/suites/ParamDecorator.ets b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets index b6977320a..29f24e5e7 100644 --- a/ets-tests/ets/environment-tests/suites/ParamDecorator.ets +++ b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets @@ -28,6 +28,8 @@ export function suiteParamDecorator(control: TestController) { "Param Hello", "Param true", "Param(@Once) Hello", + "Param(valueAsConst) 10", + "Param(valueAsExternal) 20", "Local 1", "Local Hello World", @@ -37,7 +39,8 @@ export function suiteParamDecorator(control: TestController) { "Param false", "Local 100", "Param 100", - "Param(@Once) Hello World" + "Param(@Once) Hello World", + "Param(valueAsExternal) 30" ]) }) }) -- Gitee From f8f706a18aec1ff4525b8888934ff54bf7af78fa Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Fri, 8 Aug 2025 18:53:27 +0700 Subject: [PATCH 4/9] WIP --- incremental/compat/src/arkts/observable.ts | 68 +++++++++++++++---- incremental/compat/src/index.ts | 1 + incremental/compat/src/ohos/index.ts | 1 + .../compat/src/typescript/observable.ts | 19 ++++++ incremental/runtime/src/index.ts | 2 +- ui2abc/ui-plugins/src/class-transformer.ts | 66 +++++++++++------- 6 files changed, 118 insertions(+), 39 deletions(-) diff --git a/incremental/compat/src/arkts/observable.ts b/incremental/compat/src/arkts/observable.ts index 979520896..956860768 100644 --- a/incremental/compat/src/arkts/observable.ts +++ b/incremental/compat/src/arkts/observable.ts @@ -230,20 +230,20 @@ export function observableProxy(value: Value, parent?: ObservableHandler, // Improve: Fatal error on using proxy with generic types // see: panda issue #26492 - const valueType = Type.of(value) - if (valueType instanceof ClassType && !(value instanceof BaseEnum)) { - const isObservable = isObservedV1Class(value as Object) - if (!hasTrackableProperties(value as Object) && !isObservable) { - return value as Value - } - if (valueType.hasEmptyConstructor()) { - const result = proxy.Proxy.create(value as Object, new CustomProxyHandler(isObservable)) as Value - ObservableHandler.installOn(result as Object, new ObservableHandler(parent)) - return result - } else { - throw new Error(`Class '${valueType.getName()}' must contain a default constructor`) - } - } + // const valueType = Type.of(value) + // if (valueType instanceof ClassType && !(value instanceof BaseEnum)) { + // const isObservable = isObservedV1Class(value as Object) + // if (!hasTrackableProperties(value as Object) && !isObservable) { + // return value as Value + // } + // if (valueType.hasEmptyConstructor()) { + // const result = proxy.Proxy.create(value as Object, new CustomProxyHandler(isObservable)) as Value + // ObservableHandler.installOn(result as Object, new ObservableHandler(parent)) + // return result + // } else { + // throw new Error(`Class '${valueType.getName()}' must contain a default constructor`) + // } + // } return value as Value } @@ -1252,4 +1252,42 @@ export class ClassMetadata implements TrackableProperties { } return undefined } -} \ No newline at end of file +} + +export class PropertyAccessDelegate { + readonly name: string + readonly parent: Object + private value: T + + constructor(name: string, parent: Object, value: T) { + this.name = name + this.parent = parent + this.value = observableProxy(value) + ObservableHandler.installOn(parent, new ObservableHandler()) + } + get(): T { + const targetHandler = ObservableHandler.find(this.parent) + + if (targetHandler) { + const valueHandler = ObservableHandler.find(this.value as Object) + if (valueHandler && !targetHandler.hasChild(valueHandler)) { + valueHandler.addParent(targetHandler) + } + } + targetHandler?.onAccess(this.name) + console.log(`PropertyAccessDelegate.get targetHandler=${targetHandler}, fieldName=${this.name}, value=${this.value}`) + + return this.value + } + set(value: T) { + const observable = ObservableHandler.find(this.parent) + + console.log(`PropertyAccessDelegate.set targetHandler=${observable}, fieldName=${this.name}, new_value=${this.value}`) + if (observable) { + observable.onModify(this.name) + observable.removeChild(this.value) + value = observableProxy(value, observable, ObservableHandler.contains(observable)) + } + this.value = value + } +} diff --git a/incremental/compat/src/index.ts b/incremental/compat/src/index.ts index d068f170a..2f9fb1b57 100644 --- a/incremental/compat/src/index.ts +++ b/incremental/compat/src/index.ts @@ -33,6 +33,7 @@ export { trackableProperties, ClassMetadata, observableProxy, + PropertyAccessDelegate, observableProxyArray, propDeepCopy, lcClassName, diff --git a/incremental/compat/src/ohos/index.ts b/incremental/compat/src/ohos/index.ts index 892265e66..450242656 100644 --- a/incremental/compat/src/ohos/index.ts +++ b/incremental/compat/src/ohos/index.ts @@ -27,6 +27,7 @@ export { Observable, ObservableHandler, observableProxy, + PropertyAccessDelegate, ObservableClass, TrackableProperties, trackableProperties, diff --git a/incremental/compat/src/typescript/observable.ts b/incremental/compat/src/typescript/observable.ts index 31d9f74cd..75cadb31f 100644 --- a/incremental/compat/src/typescript/observable.ts +++ b/incremental/compat/src/typescript/observable.ts @@ -625,4 +625,23 @@ export class ClassMetadata implements TrackableProperties { } return undefined } +} + +export class PropertyAccessDelegate { + readonly fieldName: string + readonly target: Object + value: T | undefined + + constructor(fieldName: string, target: Object, value?: T) { + this.fieldName = fieldName + this.target = target + this.value = value + } + get(): T { + return this.value! + } + set(value: T): T { + this.value = value + return this.value! + } } \ No newline at end of file diff --git a/incremental/runtime/src/index.ts b/incremental/runtime/src/index.ts index 581d61d97..5298bba34 100644 --- a/incremental/runtime/src/index.ts +++ b/incremental/runtime/src/index.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -export { observableProxy, ObservableClass, ClassMetadata, TrackableProperties, trackableProperties } from "@koalaui/compat" +export { observableProxy, PropertyAccessDelegate, ObservableClass, ClassMetadata, TrackableProperties, trackableProperties } from "@koalaui/compat" export { AnimatedState, diff --git a/ui2abc/ui-plugins/src/class-transformer.ts b/ui2abc/ui-plugins/src/class-transformer.ts index 0b0de415f..0d5fcaee7 100644 --- a/ui2abc/ui-plugins/src/class-transformer.ts +++ b/ui2abc/ui-plugins/src/class-transformer.ts @@ -27,7 +27,7 @@ import { isDecoratorAnnotation, mangle } from "./utils" -import { backingFieldNameOf, fieldOf } from "./property-transformers"; +import { backingFieldNameOf, createWrapperType, fieldOf } from "./property-transformers"; type ObservedDecoratorType = DecoratorNames.OBSERVED | DecoratorNames.OBSERVED_V2 @@ -71,6 +71,7 @@ export class ClassTransformer extends arkts.AbstractVisitor { if (properties.length > 0) { if (classContext.isClassObservable()) { this.importer.add("observableProxy", getRuntimePackage()) + this.importer.add("PropertyAccessDelegate", getRuntimePackage()) this.addObservableClassImplements(classImplements) } classDef = arkts.factory.updateClassDefinition( @@ -187,6 +188,8 @@ export class ClassTransformer extends arkts.AbstractVisitor { if (isStaticProperty(property)) { modifiers |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC this.importer.add("globalMutableState", getRuntimePackage()) + } else { + modifiers |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_READONLY } const backing = arkts.factory.createClassProperty( arkts.factory.createIdentifier(backingFieldNameOf(property)), @@ -267,14 +270,8 @@ export class ClassTransformer extends arkts.AbstractVisitor { if (property && classContext.needRewriteProperty(property)) { return arkts.factory.createExpressionStatement( arkts.factory.createAssignmentExpression( - arkts.factory.createMemberExpression( - arkts.factory.createThisExpression(), - arkts.factory.createIdentifier(backingFieldNameOf(property)), - arkts.Es2pandaMemberExpressionKind.MEMBER_EXPRESSION_KIND_PROPERTY_ACCESS, - false, - false - ), - observePropIfNeeded(propertyName, expr.right, classContext), + createPropertyAccess(property, classContext), + createPropertyDelegateInstance(property, expr.right), expr.operatorType ) ) @@ -332,29 +329,28 @@ function extractClassContext(clazz: arkts.ClassDeclaration, } function createBackingPropertyRValue(property: arkts.ClassProperty, value: arkts.Expression | undefined, classContext: ClassContext): arkts.Expression | undefined { - const propValue = observePropIfNeeded(property.id?.name!, value, classContext) if (isStaticProperty(property) && classContext.tracedPropertyNames.includes(property.id?.name!)) { return arkts.factory.createCallExpression( arkts.factory.createIdentifier("globalMutableState"), - [propValue!], + [observePropIfNeeded(property.id?.name!, value, classContext)!], arkts.factory.createTSTypeParameterInstantiation([property.typeAnnotation!]), false, false, undefined ) } - return propValue + return value ? createPropertyDelegateInstance(property, value) : undefined } function createBackingPropertyType(property: arkts.ClassProperty, classContext: ClassContext) { return isStaticProperty(property) && classContext.tracedPropertyNames.includes(property.id?.name!) ? undefined - : property.typeAnnotation + : createPropertyDelegateType(property) } -function createPropertyAccess(className: string, property: arkts.ClassProperty, classContext: ClassContext) { +function createPropertyAccess(property: arkts.ClassProperty, classContext: ClassContext) { const receiver = isStaticProperty(property) - ? createETSTypeReference(className) + ? createETSTypeReference((property.parent as arkts.ClassDefinition).ident!.name) : arkts.factory.createThisExpression() const expr = fieldOf(receiver, backingFieldNameOf(property)) if (isStaticProperty(property) && classContext.tracedPropertyNames.includes(property.id?.name!)) { @@ -373,7 +369,17 @@ function createGetterSetter(property: arkts.ClassProperty, const name = property.id!.name const getterStatements: arkts.Statement[] = [] recreateDisposedState(property, classContext, className, getterStatements) - getterStatements.push(arkts.factory.createReturnStatement(createPropertyAccess(className, property, classContext))) + + getterStatements.push( + arkts.factory.createReturnStatement( + isStaticProperty(property) + ? createPropertyAccess(property, classContext) + : arkts.factory.createCallExpression(fieldOf(createPropertyAccess(property, classContext), "get"), + [], + undefined, false, false + ) + ) + ) const getterFunction = arkts.factory.createScriptFunction( arkts.factory.createBlockStatement(getterStatements), undefined, @@ -389,13 +395,14 @@ function createGetterSetter(property: arkts.ClassProperty, const setterFunction = arkts.factory.createScriptFunction( arkts.factory.createBlockStatement([ arkts.factory.createExpressionStatement( - arkts.factory.createAssignmentExpression( - createPropertyAccess(className, property, classContext), - observePropIfNeeded(property.id?.name!, - arkts.factory.createIdentifier("value", property.typeAnnotation?.clone()), - classContext), - arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION - ) + isStaticProperty(property) + ? createPropertyAccess(property, classContext) + : arkts.factory.createCallExpression( + fieldOf(createPropertyAccess(property, classContext), "set"), + [ + arkts.factory.createIdentifier("value", property.typeAnnotation?.clone()) + ], undefined, false, false + ), ) ]), undefined, @@ -608,4 +615,17 @@ class PropertyDecorator { this.name = name this.args = args } +} + +function createPropertyDelegateType(property: arkts.ClassProperty) { + return createWrapperType("PropertyAccessDelegate", property.typeAnnotation!) +} + +function createPropertyDelegateInstance(property: arkts.ClassProperty, value: arkts.Expression | undefined) { + return arkts.factory.createETSNewClassInstanceExpression( + createPropertyDelegateType(property), + [arkts.factory.createStringLiteral(property.id!.name!), + arkts.factory.createThisExpression(), + value ?? arkts.factory.createUndefinedLiteral()] + ) } \ No newline at end of file -- Gitee From 9091fc2d532fda6ed4c4b88173f49c4670e69f03 Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Tue, 12 Aug 2025 22:23:35 +0700 Subject: [PATCH 5/9] Fixed tests --- .../arkui/src/ArkStateProperties.ets | 2 +- .../pages/observedv2/StaticTraceDecorator.ets | 10 +- .../environment-tests/suites/ObservedTest.ets | 2 +- .../suites/TraceDecoratorTests.ets | 10 +- incremental/compat/src/arkts/observable.ts | 127 ++++-------------- incremental/compat/src/index.ts | 3 +- incremental/compat/src/ohos/index.ts | 3 +- .../compat/src/typescript/observable.ts | 36 ++--- incremental/runtime/src/index.ts | 2 +- ui2abc/ui-plugins/src/class-transformer.ts | 64 +++++---- 10 files changed, 100 insertions(+), 159 deletions(-) diff --git a/arkoala-arkts/arkui/src/ArkStateProperties.ets b/arkoala-arkts/arkui/src/ArkStateProperties.ets index f3773baa5..eb2e1c437 100644 --- a/arkoala-arkts/arkui/src/ArkStateProperties.ets +++ b/arkoala-arkts/arkui/src/ArkStateProperties.ets @@ -60,7 +60,7 @@ class StatableHolder { private isStatable(value: Value | undefined): boolean { if (value instanceof ObservableClass) { - return value.getClassMetadata()?.isObservedV2(value) ?? false + return value.getClassMetadata()?.hasTrackableProperties() ?? false } return false } diff --git a/ets-tests/ets/environment-tests/pages/observedv2/StaticTraceDecorator.ets b/ets-tests/ets/environment-tests/pages/observedv2/StaticTraceDecorator.ets index f54e9b21e..3d229af45 100644 --- a/ets-tests/ets/environment-tests/pages/observedv2/StaticTraceDecorator.ets +++ b/ets-tests/ets/environment-tests/pages/observedv2/StaticTraceDecorator.ets @@ -37,22 +37,22 @@ class StaticClassRegular { @Component struct StaticTraceDecorator { build() { - TestComponent({}) { - StaticClassObservedV2.tracedCountDeferred = 1 - } TestComponent({ id: 100 }).onChange(() => { + StaticClassObservedV2.tracedCountDeferred = 1 + }) + TestComponent({ id: 101 }).onChange(() => { StaticClassObservedV2.tracedCount!++ }) TestComponent({}) { uiLog(`StaticClassObservedV2.tracedCount: ${StaticClassObservedV2.tracedCount}`) } - TestComponent({ id: 101 }).onChange(() => { + TestComponent({ id: 102 }).onChange(() => { StaticClassObservedV2.count!++ }) TestComponent({}) { uiLog(`StaticClassObservedV2.count: ${StaticClassObservedV2.count}`) } - TestComponent({ id: 102 }).onChange(() => { + TestComponent({ id: 103 }).onChange(() => { StaticClassObservedV2.tracedCountDeferred!++ }) TestComponent({}) { diff --git a/ets-tests/ets/environment-tests/suites/ObservedTest.ets b/ets-tests/ets/environment-tests/suites/ObservedTest.ets index df4284c9b..944b17358 100644 --- a/ets-tests/ets/environment-tests/suites/ObservedTest.ets +++ b/ets-tests/ets/environment-tests/suites/ObservedTest.ets @@ -190,7 +190,7 @@ export function suiteObservedTest(control: TestController) { ] ) }) - test.expectFailure('Proxy is broken in panda', '@ObservedObject', () => { + test('@ObservedObject', () => { testPageOnChange(control, 'ObservedObject', [100, 101, 102, 103, 102, 104], [ diff --git a/ets-tests/ets/environment-tests/suites/TraceDecoratorTests.ets b/ets-tests/ets/environment-tests/suites/TraceDecoratorTests.ets index 204c5435a..bd1d0f2cb 100644 --- a/ets-tests/ets/environment-tests/suites/TraceDecoratorTests.ets +++ b/ets-tests/ets/environment-tests/suites/TraceDecoratorTests.ets @@ -19,7 +19,7 @@ import { suite, test } from '@koalaui/harness' export function suiteTraceDecorator(control: TestController) { suite("@Trace decorator tests", () => { - test.expectFailure('Proxy is broken in panda', "Trace decorator with object", () => { + test("Trace decorator with object", () => { testPageOnChange(control, "TraceDecorator", [100, 101, 102, 103], ["person.age: 8", "person.id: 0", @@ -34,7 +34,7 @@ export function suiteTraceDecorator(control: TestController) { suite("@Trace decorator with static properties tests", () => { test("Trace decorator with static properties", () => { - testPageOnChange(control, "StaticTraceDecorator", [100, 101, 102], + testPageOnChange(control, "StaticTraceDecorator", [100, 101, 102, 103], ["StaticClassObservedV2.tracedCount: 1", "StaticClassObservedV2.count: 1", "StaticClassObservedV2.tracedCountDeferred: undefined", @@ -47,13 +47,13 @@ export function suiteTraceDecorator(control: TestController) { suite("@Trace decorator with static properties tests(reload page)", () => { test("Trace decorator with static properties", () => { - testPageOnChange(control, "StaticTraceDecorator", [100, 101, 102], + testPageOnChange(control, "StaticTraceDecorator", [101, 102, 103], ["StaticClassObservedV2.tracedCount: 2", "StaticClassObservedV2.count: 2", - "StaticClassObservedV2.tracedCountDeferred: 1", + "StaticClassObservedV2.tracedCountDeferred: 2", "StaticClassObservedV2.tracedCount: 3", - "StaticClassObservedV2.tracedCountDeferred: 2"]) + "StaticClassObservedV2.tracedCountDeferred: 3"]) }) }) } \ No newline at end of file diff --git a/incremental/compat/src/arkts/observable.ts b/incremental/compat/src/arkts/observable.ts index 956860768..25674eb94 100644 --- a/incremental/compat/src/arkts/observable.ts +++ b/incremental/compat/src/arkts/observable.ts @@ -13,19 +13,8 @@ * limitations under the License. */ -export function getObservableTarget(proxy0: Object): Object { - try { - // do not use proxy for own observables - if (proxy0 instanceof ObservableArray - || proxy0 instanceof ObservableDate - || proxy0 instanceof ObservableMap - || proxy0 instanceof ObservableSet ) { - return proxy0 - } - return (proxy.Proxy.tryGetTarget(proxy0) as Object|undefined|null) ?? proxy0 - } catch (error) { - return proxy0 - } +export function getObservableTarget(value: Object): Object { + return value } /** @@ -151,10 +140,6 @@ export class ObservableHandler implements Observable { this.parents.add(parent) } - hasChild(child: ObservableHandler): boolean { - return this.children.has(child) - } - removeParent(parent: ObservableHandler) { const count = parent.children.get(this) ?? 0 if (count > 1) { @@ -171,6 +156,10 @@ export class ObservableHandler implements Observable { if (child) child.removeParent(this) } + hasChild(child: ObservableHandler): boolean { + return this.children.has(child) + } + private collect(all: boolean, guards: Set) { if (guards.has(this)) return guards // already collected guards.add(this) // handler is already guarded @@ -227,57 +216,10 @@ export function observableProxy(value: Value, parent?: ObservableHandler, return ObservableDate(value, parent, observed) as Value } - // Improve: Fatal error on using proxy with generic types - // see: panda issue #26492 - - // const valueType = Type.of(value) - // if (valueType instanceof ClassType && !(value instanceof BaseEnum)) { - // const isObservable = isObservedV1Class(value as Object) - // if (!hasTrackableProperties(value as Object) && !isObservable) { - // return value as Value - // } - // if (valueType.hasEmptyConstructor()) { - // const result = proxy.Proxy.create(value as Object, new CustomProxyHandler(isObservable)) as Value - // ObservableHandler.installOn(result as Object, new ObservableHandler(parent)) - // return result - // } else { - // throw new Error(`Class '${valueType.getName()}' must contain a default constructor`) - // } - // } - - return value as Value -} - -class CustomProxyHandler extends proxy.DefaultProxyHandler { - private readonly isObservable: boolean - - constructor(isObservable: boolean) { - super(); - this.isObservable = isObservable - } - - override get(target: T, name: string): Any { - const value = super.get(target, name) - const targetHandler = ObservableHandler.find(target) - if (targetHandler && this.isObservable) { - const valueHandler = ObservableHandler.find(value as Object) - if (valueHandler && !targetHandler.hasChild(valueHandler)) { - valueHandler.addParent(targetHandler) - } - } - targetHandler?.onAccess(name) - return value - } - - override set(target: T, name: string, value: Any): boolean { - const observable = ObservableHandler.find(target) - if (observable) { - observable.onModify(name) - observable.removeChild(super.get(target, name)) - value = observableProxy(value, observable, ObservableHandler.contains(observable)) - } - return super.set(target, name, value) + if (Type.of(value) instanceof ClassType) { + ObservableHandler.installOn(value as Object, new ObservableHandler()) } + return value as Value } function proxyChildrenOnly(array: T[], parent: ObservableHandler, observed?: boolean) { @@ -1254,40 +1196,23 @@ export class ClassMetadata implements TrackableProperties { } } -export class PropertyAccessDelegate { - readonly name: string - readonly parent: Object - private value: T - - constructor(name: string, parent: Object, value: T) { - this.name = name - this.parent = parent - this.value = observableProxy(value) - ObservableHandler.installOn(parent, new ObservableHandler()) - } - get(): T { - const targetHandler = ObservableHandler.find(this.parent) - - if (targetHandler) { - const valueHandler = ObservableHandler.find(this.value as Object) - if (valueHandler && !targetHandler.hasChild(valueHandler)) { - valueHandler.addParent(targetHandler) - } - } - targetHandler?.onAccess(this.name) - console.log(`PropertyAccessDelegate.get targetHandler=${targetHandler}, fieldName=${this.name}, value=${this.value}`) - - return this.value +export function observableGetProperty(value: Value, propertyName: string, parent: Object): Value { + const parentHandler = parent ? ObservableHandler.find(parent) : undefined + const valueHandler = ObservableHandler.find(value as Object) + if (valueHandler && parentHandler && !parentHandler.hasChild(valueHandler!)) { + valueHandler.addParent(parentHandler) } - set(value: T) { - const observable = ObservableHandler.find(this.parent) + parentHandler?.onAccess(propertyName) + return value +} - console.log(`PropertyAccessDelegate.set targetHandler=${observable}, fieldName=${this.name}, new_value=${this.value}`) - if (observable) { - observable.onModify(this.name) - observable.removeChild(this.value) - value = observableProxy(value, observable, ObservableHandler.contains(observable)) - } - this.value = value +export function observableSetProperty(value: Value, newValue: Value, propertyName: string, parent: Object): Value { + const parentHandler = parent ? ObservableHandler.find(parent) : undefined + if (parentHandler) { + parentHandler.onModify(propertyName) + const valueHandler = ObservableHandler.find(value as Object) + valueHandler?.removeChild(value) + return observableProxy(newValue, parentHandler) } -} + return newValue +} \ No newline at end of file diff --git a/incremental/compat/src/index.ts b/incremental/compat/src/index.ts index 2f9fb1b57..2d842999f 100644 --- a/incremental/compat/src/index.ts +++ b/incremental/compat/src/index.ts @@ -33,7 +33,8 @@ export { trackableProperties, ClassMetadata, observableProxy, - PropertyAccessDelegate, + observableGetProperty, + observableSetProperty, observableProxyArray, propDeepCopy, lcClassName, diff --git a/incremental/compat/src/ohos/index.ts b/incremental/compat/src/ohos/index.ts index 450242656..36f386098 100644 --- a/incremental/compat/src/ohos/index.ts +++ b/incremental/compat/src/ohos/index.ts @@ -27,7 +27,8 @@ export { Observable, ObservableHandler, observableProxy, - PropertyAccessDelegate, + observableGetProperty, + observableSetProperty, ObservableClass, TrackableProperties, trackableProperties, diff --git a/incremental/compat/src/typescript/observable.ts b/incremental/compat/src/typescript/observable.ts index 75cadb31f..0e4b02e2b 100644 --- a/incremental/compat/src/typescript/observable.ts +++ b/incremental/compat/src/typescript/observable.ts @@ -159,6 +159,10 @@ export class ObservableHandler implements Observable { if (child) child.removeParent(this) } + hasChild(child: ObservableHandler): boolean { + return this.children.has(child) + } + private collect(all: boolean, guards = new Set()) { if (guards.has(this)) return guards // already collected guards.add(this) // handler is already guarded @@ -627,21 +631,23 @@ export class ClassMetadata implements TrackableProperties { } } -export class PropertyAccessDelegate { - readonly fieldName: string - readonly target: Object - value: T | undefined - - constructor(fieldName: string, target: Object, value?: T) { - this.fieldName = fieldName - this.target = target - this.value = value +export function observableGetProperty(value: Value, propertyName: string, parent: Object | undefined): Value { + const parentHandler = parent ? ObservableHandler.find(parent) : undefined + const valueHandler = ObservableHandler.find(value as Object) + if (valueHandler && parentHandler && !parentHandler.hasChild(valueHandler!)) { + valueHandler.addParent(parentHandler) } - get(): T { - return this.value! - } - set(value: T): T { - this.value = value - return this.value! + parentHandler?.onAccess(propertyName) + return value +} + +export function observableSetProperty(value: Value, newValue: Value, propertyName: string, parent: Object | undefined): Value { + const parentHandler = parent ? ObservableHandler.find(parent) : undefined + if (parentHandler) { + parentHandler.onModify(propertyName) + const valueHandler = ObservableHandler.find(value as Object) + valueHandler?.removeChild(value) + return observableProxy(newValue, parentHandler) } + return newValue } \ No newline at end of file diff --git a/incremental/runtime/src/index.ts b/incremental/runtime/src/index.ts index 5298bba34..45c303c61 100644 --- a/incremental/runtime/src/index.ts +++ b/incremental/runtime/src/index.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -export { observableProxy, PropertyAccessDelegate, ObservableClass, ClassMetadata, TrackableProperties, trackableProperties } from "@koalaui/compat" +export { observableProxy, observableGetProperty, observableSetProperty, ObservableClass, ClassMetadata, TrackableProperties, trackableProperties } from "@koalaui/compat" export { AnimatedState, diff --git a/ui2abc/ui-plugins/src/class-transformer.ts b/ui2abc/ui-plugins/src/class-transformer.ts index 0d5fcaee7..b72d94fa0 100644 --- a/ui2abc/ui-plugins/src/class-transformer.ts +++ b/ui2abc/ui-plugins/src/class-transformer.ts @@ -71,7 +71,8 @@ export class ClassTransformer extends arkts.AbstractVisitor { if (properties.length > 0) { if (classContext.isClassObservable()) { this.importer.add("observableProxy", getRuntimePackage()) - this.importer.add("PropertyAccessDelegate", getRuntimePackage()) + this.importer.add("observableGetProperty", getRuntimePackage()) + this.importer.add("observableSetProperty", getRuntimePackage()) this.addObservableClassImplements(classImplements) } classDef = arkts.factory.updateClassDefinition( @@ -188,8 +189,6 @@ export class ClassTransformer extends arkts.AbstractVisitor { if (isStaticProperty(property)) { modifiers |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_STATIC this.importer.add("globalMutableState", getRuntimePackage()) - } else { - modifiers |= arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_READONLY } const backing = arkts.factory.createClassProperty( arkts.factory.createIdentifier(backingFieldNameOf(property)), @@ -270,8 +269,8 @@ export class ClassTransformer extends arkts.AbstractVisitor { if (property && classContext.needRewriteProperty(property)) { return arkts.factory.createExpressionStatement( arkts.factory.createAssignmentExpression( - createPropertyAccess(property, classContext), - createPropertyDelegateInstance(property, expr.right), + fieldOf(arkts.factory.createThisExpression(), backingFieldNameOf(property)), + observePropIfNeeded(propertyName, expr.right, classContext), expr.operatorType ) ) @@ -329,23 +328,24 @@ function extractClassContext(clazz: arkts.ClassDeclaration, } function createBackingPropertyRValue(property: arkts.ClassProperty, value: arkts.Expression | undefined, classContext: ClassContext): arkts.Expression | undefined { + const propValue = observePropIfNeeded(property.id?.name!, value, classContext) if (isStaticProperty(property) && classContext.tracedPropertyNames.includes(property.id?.name!)) { return arkts.factory.createCallExpression( arkts.factory.createIdentifier("globalMutableState"), - [observePropIfNeeded(property.id?.name!, value, classContext)!], + [propValue!], arkts.factory.createTSTypeParameterInstantiation([property.typeAnnotation!]), false, false, undefined ) } - return value ? createPropertyDelegateInstance(property, value) : undefined + return propValue } function createBackingPropertyType(property: arkts.ClassProperty, classContext: ClassContext) { return isStaticProperty(property) && classContext.tracedPropertyNames.includes(property.id?.name!) ? undefined - : createPropertyDelegateType(property) + : property.typeAnnotation } function createPropertyAccess(property: arkts.ClassProperty, classContext: ClassContext) { @@ -374,10 +374,7 @@ function createGetterSetter(property: arkts.ClassProperty, arkts.factory.createReturnStatement( isStaticProperty(property) ? createPropertyAccess(property, classContext) - : arkts.factory.createCallExpression(fieldOf(createPropertyAccess(property, classContext), "get"), - [], - undefined, false, false - ) + : createGetProperty(property, name, classContext) ) ) const getterFunction = arkts.factory.createScriptFunction( @@ -395,14 +392,13 @@ function createGetterSetter(property: arkts.ClassProperty, const setterFunction = arkts.factory.createScriptFunction( arkts.factory.createBlockStatement([ arkts.factory.createExpressionStatement( - isStaticProperty(property) - ? createPropertyAccess(property, classContext) - : arkts.factory.createCallExpression( - fieldOf(createPropertyAccess(property, classContext), "set"), - [ - arkts.factory.createIdentifier("value", property.typeAnnotation?.clone()) - ], undefined, false, false - ), + arkts.factory.createAssignmentExpression( + createPropertyAccess(property, classContext), + isStaticProperty(property) + ? arkts.factory.createIdentifier("value", property.typeAnnotation?.clone()) + : createSetProperty(property, name, classContext), + arkts.Es2pandaTokenType.TOKEN_TYPE_PUNCTUATOR_SUBSTITUTION + ), ) ]), undefined, @@ -617,15 +613,27 @@ class PropertyDecorator { } } -function createPropertyDelegateType(property: arkts.ClassProperty) { - return createWrapperType("PropertyAccessDelegate", property.typeAnnotation!) +function createGetProperty(property: arkts.ClassProperty, name: string, classContext: ClassContext): arkts.Expression { + return arkts.factory.createCallExpression(arkts.factory.createIdentifier("observableGetProperty"), + [ + createPropertyAccess(property, classContext), + arkts.factory.createStringLiteral(name), + arkts.factory.createThisExpression(), + + ], + arkts.factory.createTSTypeParameterInstantiation([property.typeAnnotation!]), false, false + ) } -function createPropertyDelegateInstance(property: arkts.ClassProperty, value: arkts.Expression | undefined) { - return arkts.factory.createETSNewClassInstanceExpression( - createPropertyDelegateType(property), - [arkts.factory.createStringLiteral(property.id!.name!), - arkts.factory.createThisExpression(), - value ?? arkts.factory.createUndefinedLiteral()] +function createSetProperty(property: arkts.ClassProperty, name: string, classContext: ClassContext): arkts.Expression { + return arkts.factory.createCallExpression(arkts.factory.createIdentifier("observableSetProperty"), + [ + createPropertyAccess(property, classContext), + arkts.factory.createIdentifier("value", property.typeAnnotation?.clone()), + arkts.factory.createStringLiteral(name), + isStaticProperty(property) ? arkts.factory.createUndefinedLiteral() : arkts.factory.createThisExpression(), + + ], + arkts.factory.createTSTypeParameterInstantiation([property.typeAnnotation!.clone()]), false, false ) } \ No newline at end of file -- Gitee From 0446eb2565e92e99a71bc42d1ca2c843f34ff29a Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Wed, 13 Aug 2025 10:27:30 +0700 Subject: [PATCH 6/9] Added check for observable classes --- incremental/compat/src/arkts/observable.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/incremental/compat/src/arkts/observable.ts b/incremental/compat/src/arkts/observable.ts index 25674eb94..0f97a2a98 100644 --- a/incremental/compat/src/arkts/observable.ts +++ b/incremental/compat/src/arkts/observable.ts @@ -216,7 +216,11 @@ export function observableProxy(value: Value, parent?: ObservableHandler, return ObservableDate(value, parent, observed) as Value } - if (Type.of(value) instanceof ClassType) { + const meta = getClassMetadata(value) + // Create ObservableHandle if the next conditions are true: + // hasTrackableProperties - if class or its descendants contain properties with @Trace or @Track decorators + // isObservedV1 - if class contains @Observed decorator + if (meta?.hasTrackableProperties() || meta?.isObservedV1(value as Object)) { ObservableHandler.installOn(value as Object, new ObservableHandler()) } return value as Value @@ -1197,9 +1201,11 @@ export class ClassMetadata implements TrackableProperties { } export function observableGetProperty(value: Value, propertyName: string, parent: Object): Value { + // ObservableHandler is not null if it is a handler for the class. const parentHandler = parent ? ObservableHandler.find(parent) : undefined const valueHandler = ObservableHandler.find(value as Object) if (valueHandler && parentHandler && !parentHandler.hasChild(valueHandler!)) { + // If the class contains a property of class type, then explicitly call addParent valueHandler.addParent(parentHandler) } parentHandler?.onAccess(propertyName) -- Gitee From b4848d83eb51baef9ece68410c3ae4d3c794b260 Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Wed, 13 Aug 2025 12:28:10 +0700 Subject: [PATCH 7/9] Removed Proxy from StateMgmtTool, InterfaceProxyHandler --- .../tools/arkts/observeInterfaceProxy.ts | 41 ++++++++++--------- .../tools/arkts/stateMgmtTool.ts | 14 ++++--- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/arkoala-arkts/arkui/src/stateManagement/tools/arkts/observeInterfaceProxy.ts b/arkoala-arkts/arkui/src/stateManagement/tools/arkts/observeInterfaceProxy.ts index 371cad7f2..7df552ff7 100644 --- a/arkoala-arkts/arkui/src/stateManagement/tools/arkts/observeInterfaceProxy.ts +++ b/arkoala-arkts/arkui/src/stateManagement/tools/arkts/observeInterfaceProxy.ts @@ -22,8 +22,9 @@ import { OBSERVE } from '../../decorator'; import { NullableObject } from '../../base/types'; import { UIUtils } from '../../utils'; +// proxy.Proxy class will be removed from panda stdlib export class InterfaceProxyHandler - extends proxy.DefaultProxyHandler + // extends proxy.DefaultProxyHandler implements IObservedObject, ISubscribedWatches { private readonly __meta: IMutableStateMeta = STATE_MGMT_FACTORY.makeMutableStateMeta(); @@ -47,23 +48,23 @@ export class InterfaceProxyHandler public shouldAddRef(): boolean { return OBSERVE.shouldAddRef(this.____V1RenderId); } - override get(target: T, name: string): Any { - const value = super.get(target, name) - if (typeof value !== 'function' && this.shouldAddRef()) { - this.__meta.addRef(); - } - return UIUtils.makeObserved(value); - } - override set(target: T, name: string, newValue: Any): boolean { - if (super.get(target, name) !== newValue) { - const result = super.set(target, name, newValue) - this.__meta.fireChange(); - this.executeOnSubscribingWatches(name); - return result; - } - return true; - } - override invoke(target: T, method: Method, args: FixedArray): Any { - return method.invoke(target, args); - } + // override get(target: T, name: string): Any { + // const value = super.get(target, name) + // if (typeof value !== 'function' && this.shouldAddRef()) { + // this.__meta.addRef(); + // } + // return UIUtils.makeObserved(value); + // } + // override set(target: T, name: string, newValue: Any): boolean { + // if (super.get(target, name) !== newValue) { + // const result = super.set(target, name, newValue) + // this.__meta.fireChange(); + // this.executeOnSubscribingWatches(name); + // return result; + // } + // return true; + // } + // override invoke(target: T, method: Method, args: FixedArray): Any { + // return method.invoke(target, args); + // } } diff --git a/arkoala-arkts/arkui/src/stateManagement/tools/arkts/stateMgmtTool.ts b/arkoala-arkts/arkui/src/stateManagement/tools/arkts/stateMgmtTool.ts index 806143581..b18fe4b6f 100644 --- a/arkoala-arkts/arkui/src/stateManagement/tools/arkts/stateMgmtTool.ts +++ b/arkoala-arkts/arkui/src/stateManagement/tools/arkts/stateMgmtTool.ts @@ -30,6 +30,8 @@ import { ISubscribedWatches } from '../../decorator'; import { DecoratedV1VariableBase } from '../../decoratorImpl/decoratorBase'; import { StateManager, GlobalStateManager } from '@koalaui/runtime'; import { StateMgmtConsole } from '../stateMgmtDFX'; + +// proxy.Proxy class will be removed from panda stdlib export class StateMgmtTool { static isIObservedObject(value: NullableObject): boolean { return value instanceof IObservedObject; @@ -71,13 +73,15 @@ export class StateMgmtTool { return value instanceof InterfaceProxyHandler; } static tryGetHandler(value: Object): NullableObject { - const objType = Type.of(value); - return objType instanceof ClassType && (objType as ClassType).getName().endsWith('@Proxy') - ? (proxy.Proxy.tryGetHandler(value) as NullableObject) // a very slow call so need to judge proxy first - : undefined; + return undefined; + // const objType = Type.of(value); + // return objType instanceof ClassType && (objType as ClassType).getName().endsWith('@Proxy') + // ? (proxy.Proxy.tryGetHandler(value) as NullableObject) // a very slow call so need to judge proxy first + // : undefined; } static createProxy(value: T): T { - return proxy.Proxy.create(value, new InterfaceProxyHandler()) as T; + return value; + // return proxy.Proxy.create(value, new InterfaceProxyHandler()) as T; } static isObjectLiteral(value: T): boolean { return Reflect.isLiteralInitializedInterface(value); -- Gitee From a0de0a353bd7acc1f60a5b67f2e5567c166afaf1 Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Wed, 13 Aug 2025 14:00:51 +0700 Subject: [PATCH 8/9] Fixed compilation errors --- incremental/compat/src/typescript/observable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/incremental/compat/src/typescript/observable.ts b/incremental/compat/src/typescript/observable.ts index 0e4b02e2b..71ed6d83d 100644 --- a/incremental/compat/src/typescript/observable.ts +++ b/incremental/compat/src/typescript/observable.ts @@ -637,14 +637,14 @@ export function observableGetProperty(value: Value, propertyName: string, if (valueHandler && parentHandler && !parentHandler.hasChild(valueHandler!)) { valueHandler.addParent(parentHandler) } - parentHandler?.onAccess(propertyName) + parentHandler?.onAccess() return value } export function observableSetProperty(value: Value, newValue: Value, propertyName: string, parent: Object | undefined): Value { const parentHandler = parent ? ObservableHandler.find(parent) : undefined if (parentHandler) { - parentHandler.onModify(propertyName) + parentHandler.onModify() const valueHandler = ObservableHandler.find(value as Object) valueHandler?.removeChild(value) return observableProxy(newValue, parentHandler) -- Gitee From f3a176e21a81794747dddd4b6b2eaa2f0edfb25c Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Fri, 15 Aug 2025 17:33:56 +0700 Subject: [PATCH 9/9] Added @Monitor decorator concept for @Local and @Provider --- .../arkui/sdk/ArkStateProperties.ets | 5 +- .../arkui/src/ArkStateProperties.ets | 6 +- ets-tests/ets/environment-tests/Pages.ets | 4 ++ .../pages/monitor/MonitorDecorator.ets | 56 +++++++++++++++++ .../suites/MonitorDecoratorTests.ets | 33 ++++++++++ incremental/runtime/src/index.ts | 6 ++ .../runtime/src/states/GlobalStateManager.ts | 7 ++- incremental/runtime/src/states/Monitor.ts | 62 ++++++++++++++++++ incremental/runtime/src/states/State.ts | 56 +++++++++++++---- ui2abc/ui-plugins/src/class-transformer.ts | 2 +- .../ui-plugins/src/component-transformer.ts | 63 ++++++++++++++++++- .../ui-plugins/src/property-transformers.ts | 49 ++++++++------- ui2abc/ui-plugins/src/utils.ts | 1 + 13 files changed, 304 insertions(+), 46 deletions(-) create mode 100644 ets-tests/ets/environment-tests/pages/monitor/MonitorDecorator.ets create mode 100644 ets-tests/ets/environment-tests/suites/MonitorDecoratorTests.ets create mode 100644 incremental/runtime/src/states/Monitor.ts diff --git a/arkoala-arkts/arkui/sdk/ArkStateProperties.ets b/arkoala-arkts/arkui/sdk/ArkStateProperties.ets index f1069cdd5..d5e68f391 100644 --- a/arkoala-arkts/arkui/sdk/ArkStateProperties.ets +++ b/arkoala-arkts/arkui/sdk/ArkStateProperties.ets @@ -15,6 +15,7 @@ import { SubscribedAbstractProperty } from "./ArkState" import { LocalStorage } from "./Storage" +import { MonitorCallback } from "@koalaui/runtime" export declare class PlainStructProperty implements SubscribedAbstractProperty { constructor(name: string, value?: Value) @@ -51,7 +52,7 @@ export declare class LinkDecoratorProperty implements SubscribedAbstractP export declare class StateDecoratorProperty implements SubscribedAbstractProperty { constructor(name: string, listener?: () => void) - init(value?: Value, initial?: Value): void + init(value?: Value, initial?: Value, monitor?: MonitorCallback): void info(): string get(): Value set(value: Value): void @@ -86,7 +87,7 @@ export declare class ObjectLinkDecoratorProperty implements SubscribedAbs export declare class ProvideDecoratorProperty implements SubscribedAbstractProperty { constructor(name: string, listener?: () => void) - init(value?: Value, initial?: Value): void + init(value?: Value, initial?: Value, monitor?: MonitorCallback): void provide(provideKey?: string): void checkOverrides(provideKey?: string): void info(): string diff --git a/arkoala-arkts/arkui/src/ArkStateProperties.ets b/arkoala-arkts/arkui/src/ArkStateProperties.ets index eb2e1c437..d76a5b836 100644 --- a/arkoala-arkts/arkui/src/ArkStateProperties.ets +++ b/arkoala-arkts/arkui/src/ArkStateProperties.ets @@ -14,7 +14,7 @@ */ import { int32, observableProxy, propDeepCopy } from "@koalaui/common" -import { mutableState, scheduleCallback, MutableState, GlobalStateManager, globalMutableState, ObservableClass, ClassMetadata } from "@koalaui/runtime" +import { mutableState, scheduleCallback, MutableState, GlobalStateManager, globalMutableState, ObservableClass, ClassMetadata, MonitorCallback } from "@koalaui/runtime" import { SubscribedAbstractProperty } from "./ArkState" import { AppStorage, LocalStorage } from "./Storage" @@ -162,8 +162,8 @@ export class StateDecoratorProperty implements SubscribedAbstractProperty this.name = name if (listener) this.subscribe(listener) } - init(value?: Value, initial?: Value): void { - this.state = mutableState(observableProxy(value ?? (initial as Value))) + init(value?: Value, initial?: Value, monitor?: MonitorCallback): void { + this.state = mutableState(observableProxy(value ?? (initial as Value)), undefined, undefined, this.info(), monitor) } info(): string { return this.name diff --git a/ets-tests/ets/environment-tests/Pages.ets b/ets-tests/ets/environment-tests/Pages.ets index ecab442f6..1e420c901 100644 --- a/ets-tests/ets/environment-tests/Pages.ets +++ b/ets-tests/ets/environment-tests/Pages.ets @@ -33,6 +33,7 @@ import { suiteTraceDecorator } from './suites/TraceDecoratorTests'; import { suiteComponentV2Decorator } from './suites/ComponentV2DecoratorTests'; import { suiteLocalDecorator } from './suites/LocalDecoratorTests'; import { suiteParamDecorator } from './suites/ParamDecorator'; +import { suiteMonitorDecorator } from './suites/MonitorDecoratorTests'; import { InitializationFromParent } from "./pages/states/InitializationFromParent" import { StateIncrement } from "./pages/states/StateIncrement" import { StateDecrement } from "./pages/states/StateDecrement" @@ -129,6 +130,7 @@ import { LocalObjectTest } from "./pages/local/LocalObjectTest" import { ProviderConsumerBaseTest } from "./pages/provider/ProviderConsumerBaseTest" import { EnvironmentTest } from "./pages/storage/EnvironmentTest" import { ParamBasicTest } from "./pages/param/ParamBasicTest" +import { MonitorDecorator } from "./pages/monitor/MonitorDecorator" initModule("./pages/lifecycle/PageCallbacks") initModule("./pages/lifecycle/PageSwitch") @@ -238,6 +240,7 @@ function pageByName(name: string): void { case "ProviderConsumerBaseTest": ProviderConsumerBaseTest(); break case "EnvironmentTest": EnvironmentTest(); break case "ParamBasicTest": ParamBasicTest(); break + case "MonitorDecorator": MonitorDecorator(); break case "": break default: throw new Error(`No test case ${name} provided!`) } @@ -265,5 +268,6 @@ export function Suites(control: TestController) { suiteComponentV2Decorator(control) suiteLocalDecorator(control) suiteParamDecorator(control) + suiteMonitorDecorator(control) }) } \ No newline at end of file diff --git a/ets-tests/ets/environment-tests/pages/monitor/MonitorDecorator.ets b/ets-tests/ets/environment-tests/pages/monitor/MonitorDecorator.ets new file mode 100644 index 000000000..907fd8aca --- /dev/null +++ b/ets-tests/ets/environment-tests/pages/monitor/MonitorDecorator.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 { TestComponent, uiLog } from '@ohos.arkui' + +@Entry +@ComponentV2 +struct MonitorDecorator { + @Local message: string = "Hello World"; + @Local name: string = "Tom"; + @Local age: number = 24; + updateText: string = "" + @Local + updateTextState: string = "" + @Monitor(["message", "name", "age"]) + onStrChange(monitor: IMonitor) { + console.log(`monitor.dirty=${monitor.dirty}`) + this.updateText = "" + monitor.dirty.forEach((path: string) => { + this.updateText += `${path} changed from '${monitor.value(path)?.before}' to '${monitor.value(path)?.now}'\n` + }) + } + build() { + TestComponent({ }) { + uiLog(`${this.updateTextState}`) + } + TestComponent({ id: 100 }).onChange(() => { + this.updateTextState = this.updateText.trim() + }) + TestComponent({ id: 101 }).onChange(() => { + this.age += 2; + this.message = "Hi" + }) + TestComponent({ id: 102 }).onChange(() => { + this.age += 2; + this.age += 2; + this.age += 2; + }) + TestComponent({ id: 103 }).onChange(() => { + this.message = "Bye" + this.name = "Bob" + }) + } +} diff --git a/ets-tests/ets/environment-tests/suites/MonitorDecoratorTests.ets b/ets-tests/ets/environment-tests/suites/MonitorDecoratorTests.ets new file mode 100644 index 000000000..e8b6dcd91 --- /dev/null +++ b/ets-tests/ets/environment-tests/suites/MonitorDecoratorTests.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 { testPageOnChange } from "../../utils" +import { TestController } from "../../TestHarness" +import { suite, test } from '@koalaui/harness' + +export function suiteMonitorDecorator(control: TestController) { + suite("@Monitor decorator tests", () => { + test("Basic tests", () => { + testPageOnChange(control, "MonitorDecorator", [101, 100, 102, 100, 103, 100], + [ + "message changed from 'Hello World' to 'Hi'", + "age changed from '24' to '26'", + "age changed from '26' to '32'", + "message changed from 'Hi' to 'Bye'", + "name changed from 'Tom' to 'Bob'", + ]) + }) + }) +} \ No newline at end of file diff --git a/incremental/runtime/src/index.ts b/incremental/runtime/src/index.ts index 45c303c61..e68a7f9be 100644 --- a/incremental/runtime/src/index.ts +++ b/incremental/runtime/src/index.ts @@ -158,6 +158,12 @@ export { ValueTracker, } from "./states/State" +export { + IMonitor, + IMonitorValue, + MonitorCallback +} from "./states/Monitor" + export { __context, __id, diff --git a/incremental/runtime/src/states/GlobalStateManager.ts b/incremental/runtime/src/states/GlobalStateManager.ts index e8e9ef134..042f403f4 100644 --- a/incremental/runtime/src/states/GlobalStateManager.ts +++ b/incremental/runtime/src/states/GlobalStateManager.ts @@ -14,6 +14,7 @@ */ import { ArrayState, Equivalent, MutableState, StateManager, StateManagerLocal, ValueTracker, createStateManager } from "./State" +import { MonitorCallback } from "./Monitor"; /** * This class provides an access to the global state manager of the application. @@ -107,10 +108,12 @@ export function scheduleCallback(callback?: () => void, manager: StateManager = * @param value - initial value to initialize the created state * @param equivalent - optional value comparator for a state * @param tracker - optional tracker of values assigned to a state + * @param name - name of the created state + * @param monitorCallback - callback is marked as @Monitor decorator * @returns new mutable state trackable by memo-functions */ -export function mutableState(value: T, equivalent?: Equivalent, tracker?: ValueTracker): MutableState { - return GlobalStateManager.instance.mutableState(value, undefined, equivalent, tracker) +export function mutableState(value: T, equivalent?: Equivalent, tracker?: ValueTracker, name?: string, monitorCallback?: MonitorCallback): MutableState { + return GlobalStateManager.instance.mutableState(value, undefined, equivalent, tracker, name, monitorCallback) } /** diff --git a/incremental/runtime/src/states/Monitor.ts b/incremental/runtime/src/states/Monitor.ts new file mode 100644 index 000000000..a821764ff --- /dev/null +++ b/incremental/runtime/src/states/Monitor.ts @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022-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. + */ + +export interface IMonitor { + readonly dirty: Array + value(path?: string): IMonitorValue +} + +export interface IMonitorValue { + before: T + now: T + path: string +} + +export type MonitorCallback = (monitor: IMonitor) => void + +export class ValueChangeMonitor implements IMonitor { + private values = new Map>() + + addValueChange(path: string, before: Value, now: Value) { + this.values.set(path, new ValueChange(path, before as Object, now as Object)) + } + + get dirty(): Array { + return Array.from(this.values.keys()) + } + + value(path?: string): IMonitorValue { + if (path != undefined) { + if (!this.values.has(path)) { + throw new Error(`Unknown path: '${path}'`) + } + } else { + path = this.dirty[0] + } + return this.values.get(path)! as IMonitorValue + } +} + +class ValueChange implements IMonitorValue { + path: string + before: T + now: T + + constructor(path: string, before: T, now: T) { + this.path = path + this.before = before + this.now = now + } +} \ No newline at end of file diff --git a/incremental/runtime/src/states/State.ts b/incremental/runtime/src/states/State.ts index 93b460a87..8e1983440 100644 --- a/incremental/runtime/src/states/State.ts +++ b/incremental/runtime/src/states/State.ts @@ -34,7 +34,8 @@ import { markableQueue } from "../common/MarkableQueue" import { RuntimeProfiler } from "../common/RuntimeProfiler" import { IncrementalNode } from "../tree/IncrementalNode" import { ReadonlyTreeNode } from "../tree/ReadonlyTreeNode" -import { TrackableProperties } from "@koalaui/compat"; +import { TrackableProperties } from "@koalaui/compat" +import { ValueChangeMonitor, MonitorCallback } from "./Monitor" export const CONTEXT_ROOT_SCOPE = "ohos.koala.context.root.scope" export const CONTEXT_ROOT_NODE = "ohos.koala.context.root.node" @@ -184,7 +185,7 @@ export interface StateContext { attach(id: KoalaCallsiteKey, create: () => Node, update: () => void, cleanup?: () => void): void compute(id: KoalaCallsiteKey, compute: () => Value, cleanup?: (value: Value | undefined) => void, once?: boolean): Value computableState(compute: (context: StateContext) => Value, cleanup?: (context: StateContext, value: Value | undefined) => void): ComputableState - mutableState(initial: Value, global?: boolean, equivalent?: Equivalent, tracker?: ValueTracker): MutableState + mutableState(initial: Value, global?: boolean, equivalent?: Equivalent, tracker?: ValueTracker, name?: string, monitorCallback?: MonitorCallback): MutableState arrayState(initial?: ReadonlyArray, global?: boolean, equivalent?: Equivalent): ArrayState namedState(name: string, create: () => Value, global?: boolean, equivalent?: Equivalent, tracker?: ValueTracker): MutableState stateBy(name: string, global?: boolean): MutableState | undefined @@ -273,7 +274,7 @@ interface ManagedState extends Disposable, StateEx { */ readonly global: boolean readonly modified: boolean - updateStateSnapshot(changes?: Changes): void + updateStateSnapshot(changes?: Changes, valueChangeCollector?: ValueChangeCollector): void } interface ManagedScope extends Disposable, StateEx, Dependency, ReadonlyTreeNode { @@ -318,6 +319,7 @@ export /* Improve:HQ private as public*/ class StateImpl implements Obser private tracker: ValueTracker | undefined = undefined private name: string | undefined = undefined private trackedScopes = new TrackedScopes() + private readonly monitorCallback: MonitorCallback | undefined /** * @param manager - current state manager to register with @@ -326,7 +328,7 @@ export /* Improve:HQ private as public*/ class StateImpl implements Obser * @param name - name defined for named states only * @see StateManagerImpl.namedState */ - constructor(manager: StateManagerImpl, initial: Value, global: boolean, equivalent?: Equivalent, tracker?: ValueTracker, name?: string) { + constructor(manager: StateManagerImpl, initial: Value, global: boolean, equivalent?: Equivalent, tracker?: ValueTracker, name?: string, monitorCallback?: MonitorCallback) { if (tracker) initial = tracker.onCreate(initial) this.myGlobal = global this.equivalent = equivalent @@ -336,6 +338,7 @@ export /* Improve:HQ private as public*/ class StateImpl implements Obser this.dependencies = new StateToScopes() this.snapshot = initial this.trackedScopes.setTrackedProperties(trackableProperties(initial)) + this.monitorCallback = monitorCallback ObservableHandler.attach(initial, this) manager.addCreatedState(this) } @@ -407,7 +410,7 @@ export /* Improve:HQ private as public*/ class StateImpl implements Obser return change ? change.value : this.snapshot } - updateStateSnapshot(changes?: Changes): void { + updateStateSnapshot(changes?: Changes, valueChangeCollector?: ValueChangeCollector): void { let modifiedTrackedScopes: ReadonlySet | undefined = undefined if (this.myUpdated) { this.myModified = false @@ -415,7 +418,7 @@ export /* Improve:HQ private as public*/ class StateImpl implements Obser else { modifiedTrackedScopes = this.trackedScopes.getModifiedDependencies() this.trackedScopes.clear() - this.applyStateSnapshot(this.current(changes)) + this.applyStateSnapshot(this.current(changes), valueChangeCollector) this.myUpdated = true } const dependencies = this.dependencies @@ -430,7 +433,7 @@ export /* Improve:HQ private as public*/ class StateImpl implements Obser } } - protected applyStateSnapshot(newValue: Value) { + protected applyStateSnapshot(newValue: Value, valueChangeCollector?: ValueChangeCollector) { const oldValue = this.snapshot const isModified = ObservableHandler.dropModified(oldValue) if (!refEqual(oldValue, newValue)) { @@ -438,6 +441,9 @@ export /* Improve:HQ private as public*/ class StateImpl implements Obser ObservableHandler.attach(newValue, this) this.snapshot = newValue this.myModified = isModified || (this.equivalent?.(oldValue, newValue) != true) + if (this.monitorCallback && this.name && this.myModified) { + valueChangeCollector?.addValueChange(this.monitorCallback!, this.name!, oldValue, newValue) + } } else { this.myModified = isModified } @@ -484,7 +490,7 @@ class ArrayStateImpl extends StateImpl> implements ArrayState< }) } - protected override applyStateSnapshot(newValue: Array) { + protected override applyStateSnapshot(newValue: Array, valueChangeCollector?: ValueChangeCollector) { const modified = isModified>(this.snapshot, newValue, this.equivalent) if (modified) this.snapshot = newValue this.myModified = modified @@ -648,6 +654,7 @@ export /* Improve:HQ private as public*/ class StateManagerImpl implements State private threadCheckerCallback?: () => boolean private childManager: Array = new Array() private parentManager: StateManagerImpl | undefined = undefined + private valueChangeCollector = new StateValueChangeCollector() get currentScopeId(): KoalaCallsiteKey | undefined { return this.current?.id @@ -702,10 +709,11 @@ export /* Improve:HQ private as public*/ class StateManagerImpl implements State while (true) { const result = it.next() if (result.done) break - result.value?.updateStateSnapshot(changes) + result.value?.updateStateSnapshot(changes, this.valueChangeCollector) if (result.value?.modified == true) modified++ } } + this.valueChangeCollector.notifyAndClear() changes?.clear() RuntimeProfiler.instance?.updateSnapshot(modified, created) // recompute dirty scopes only @@ -772,8 +780,8 @@ export /* Improve:HQ private as public*/ class StateManagerImpl implements State return false } - mutableState(initial: Value, global?: boolean, equivalent?: Equivalent, tracker?: ValueTracker): MutableState { - return new StateImpl(this, initial, this.isGlobal(global), equivalent, tracker) + mutableState(initial: Value, global?: boolean, equivalent?: Equivalent, tracker?: ValueTracker, name?: string, monitorCallback?: MonitorCallback): MutableState { + return new StateImpl(this, initial, this.isGlobal(global), equivalent, tracker, name, monitorCallback) } arrayState(initial?: ReadonlyArray, global?: boolean, equivalent?: Equivalent): ArrayState { @@ -1502,4 +1510,28 @@ class TrackedScopes { it.isModified = false }) } -} \ No newline at end of file +} + +interface ValueChangeCollector { + addValueChange(callback: MonitorCallback, name: string, before: Value, now: Value): void +} + +class StateValueChangeCollector implements ValueChangeCollector { + private readonly monitors = new Map() + addValueChange(callback: MonitorCallback, name: string, before: Value, now: Value) { + let changeMonitor = this.monitors.get(callback) + if (changeMonitor == undefined) { + changeMonitor = new ValueChangeMonitor() + this.monitors.set(callback, changeMonitor) + } + changeMonitor.addValueChange(name, before, now) + } + notifyAndClear() { + for (const monitor of this.monitors.entries()) { + const callback = monitor[0] + const valueChangeMonitor = monitor[1] + callback(valueChangeMonitor) + } + this.monitors.clear() + } +} diff --git a/ui2abc/ui-plugins/src/class-transformer.ts b/ui2abc/ui-plugins/src/class-transformer.ts index b72d94fa0..932b90358 100644 --- a/ui2abc/ui-plugins/src/class-transformer.ts +++ b/ui2abc/ui-plugins/src/class-transformer.ts @@ -27,7 +27,7 @@ import { isDecoratorAnnotation, mangle } from "./utils" -import { backingFieldNameOf, createWrapperType, fieldOf } from "./property-transformers"; +import { backingFieldNameOf, fieldOf } from "./property-transformers"; type ObservedDecoratorType = DecoratorNames.OBSERVED | DecoratorNames.OBSERVED_V2 diff --git a/ui2abc/ui-plugins/src/component-transformer.ts b/ui2abc/ui-plugins/src/component-transformer.ts index 25d3a417b..cf1288466 100644 --- a/ui2abc/ui-plugins/src/component-transformer.ts +++ b/ui2abc/ui-plugins/src/component-transformer.ts @@ -30,7 +30,9 @@ import { findDecorator, createThrowError, getAnnotationName, - getRouterPackage + getRouterPackage, + getRuntimePackage, + createETSTypeReference } from "./utils"; import { BuilderParamTransformer, @@ -194,6 +196,8 @@ export class ComponentTransformer extends arkts.AbstractVisitor { result.push(this.rewriteBuild(clazz, node)) } else if (hasBuilderDecorator(node)) { result.push(this.rewriteBuilder(method)) + } else if (hasDecorator(method, DecoratorNames.MONITOR)) { + rewriteMonitorMethod(method, this.imports, result) } else { result.push(method) } @@ -236,7 +240,12 @@ export class ComponentTransformer extends arkts.AbstractVisitor { ) forEachProperty(clazz, property => { - this.getPropertyTransformer(property).applyInitializeStruct(this.pageLocalStorage, property, statements) + this.getPropertyTransformer(property).applyInitializeStruct( + this.pageLocalStorage, + property, + findPropertyMonitorCallback(clazz, property), + statements + ) }) if (statements.length > 1) { classBody.push(createVoidMethod(CustomComponentNames.COMPONENT_INITIALIZE_STRUCT, [ @@ -840,4 +849,54 @@ class ComponentV2Config { ] static readonly freezableDecoratorPropName = "freezeWhenInactive" +} + +export function rewriteMonitorMethod(method: arkts.MethodDefinition, imports: Importer, result: arkts.ClassElement[]) { + imports.add("IMonitor", getRuntimePackage()) + imports.add("IMonitorValue", getRuntimePackage()) + const newFunction = arkts.factory.createScriptFunction( + method.function!.body, + method.function!.typeParams, + method.function!.params, + method.function!.returnTypeAnnotation, + method.function!.hasReceiver, + method.function!.flags, + method.function!.modifierFlags, + method.function!.id, + [] + ) + const requiredMonitorType = arkts.factory.createETSFunctionType(undefined, + [ + arkts.factory.createETSParameterExpression( + arkts.factory.createIdentifier("monitor", createETSTypeReference("IMonitor")), false) + ], + arkts.factory.createETSPrimitiveType(arkts.Es2pandaPrimitiveType.PRIMITIVE_TYPE_VOID), + false, + arkts.Es2pandaScriptFunctionFlags.SCRIPT_FUNCTION_FLAGS_ARROW + ) + result.push( + arkts.factory.createClassProperty(method.key, + arkts.factory.createArrowFunctionExpression(newFunction), + requiredMonitorType, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_PRIVATE | arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_READONLY, + false + ) + ) +} + +function findPropertyMonitorCallback(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty): arkts.Expression | undefined { + const monitor = clazz.definition.body.find(it => arkts.isMethodDefinition(it) + && getDecoratorProperties(findDecorator(it.function!.annotations, DecoratorNames.MONITOR)) + .some(it => { + const expr = it[1] + if (arkts.isArrayExpression(expr)) { + return expr.elements.some(it => arkts.isStringLiteral(it) && it.str == property.id?.name) + } + return false + }) + ) + if (arkts.isMethodDefinition(monitor)) { + return fieldOf(arkts.factory.createThisExpression(), monitor.function?.id?.name!) + } + return undefined } \ No newline at end of file diff --git a/ui2abc/ui-plugins/src/property-transformers.ts b/ui2abc/ui-plugins/src/property-transformers.ts index b8d4d2c93..57e7ab8a5 100644 --- a/ui2abc/ui-plugins/src/property-transformers.ts +++ b/ui2abc/ui-plugins/src/property-transformers.ts @@ -38,7 +38,7 @@ export interface PropertyTransformer extends ImportingTransformer { applyBuild(property: arkts.ClassProperty, result: arkts.Statement[]): void applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void applyOptions(property: arkts.ClassProperty, result: arkts.Statement[]): void - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void applyDisposeStruct(property: arkts.ClassProperty, result: arkts.Statement[]): void applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void } @@ -373,7 +373,7 @@ export abstract class PropertyTransformerBase implements PropertyTransformer { applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void { addTrackablePropertyTo(result, clazz, property, this.className, false) } - abstract applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void + abstract applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void applyDisposeStruct(property: arkts.ClassProperty, result: arkts.Statement[]): void { result.push(thisPropertyMethodCall(property, "aboutToBeDeleted")) } @@ -390,7 +390,7 @@ export class StateTransformer extends PropertyTransformerBase { applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { result.push(createOptionalClassProperty(property.id!.name, property)) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", [initializerOf(property), property.value!])) } @@ -413,7 +413,7 @@ export class PlainPropertyTransformer implements PropertyTransformer { applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void { result.push(...createWrappedProperty(clazz, property, "PlainStructProperty")) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", [initializerOf(property)])) } @@ -439,7 +439,7 @@ class LinkablePropertyTransformer extends PropertyTransformerBase { false )) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "linkTo", [initializerOf(property)])) } @@ -474,7 +474,7 @@ export class StorageLinkTransformer extends PropertyTransformerBase { constructor() { super(DecoratorNames.STORAGE_LINK, "StorageLinkDecoratorProperty") } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", withStorageKey([property.value!], property, this.decoratorName))) } @@ -485,7 +485,7 @@ export class LocalStorageLinkTransformer extends PropertyTransformerBase { constructor() { super(DecoratorNames.LOCAL_STORAGE_LINK, "LocalStorageLinkDecoratorProperty") } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { if (!localStorage) throw new Error("@LocalStorageLink decorator requires specified local storage") result.push(thisPropertyMethodCall(property, "init", withStorageKey([property.value!, localStorage], property, this.decoratorName))) } @@ -498,7 +498,7 @@ export class PropTransformer extends PropertyTransformerBase { applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { result.push(createOptionalClassProperty(property.id!.name, property)) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "init", [initializerOf(property), property.value ?? arkts.factory.createUndefinedLiteral()])) } @@ -514,7 +514,7 @@ export class StoragePropTransformer extends PropertyTransformerBase { constructor() { super(DecoratorNames.STORAGE_PROP, "StoragePropDecoratorProperty") } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { result.push(thisPropertyMethodCall(property, "init", withStorageKey([property.value!], property, this.decoratorName))) } } @@ -523,7 +523,7 @@ export class LocalStoragePropTransformer extends PropertyTransformerBase { constructor() { super(DecoratorNames.LOCAL_STORAGE_PROP, "LocalStoragePropDecoratorProperty") } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { if (!localStorage) throw new Error("@LocalStorageProp decorator requires specified local storage") result.push(thisPropertyMethodCall(property, "init", withStorageKey([property.value!, localStorage], property, this.decoratorName))) } @@ -536,7 +536,7 @@ export class ObjectLinkTransformer extends PropertyTransformerBase { applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { result.push(createOptionalClassProperty(property.id!.name, property)) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { result.push(thisPropertyMethodCall(property, "init", [initializerOf(property), property.value ?? arkts.factory.createUndefinedLiteral()])) } applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { @@ -604,9 +604,9 @@ export class ProvideTransformer extends PropertyTransformerBase { applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { result.push(createOptionalClassProperty(property.id!.name, property)) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { checkRequiredProperty(property, result) - result.push(thisPropertyMethodCall(property, "init", [initializerOf(property), property.value!])) + result.push(thisPropertyMethodCall(property, "init", [initializerOf(property), property.value!, arkts.factory.createUndefinedLiteral()])) } applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { addPropertyRecordTo(result, property) @@ -632,13 +632,14 @@ export class ProviderTransformer extends PropertyTransformerBase { super(DecoratorNames.PROVIDER, "ProvideDecoratorProperty") } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { if (property.value == undefined) { throwPropertyRequiresInit(this.decoratorName, property) } result.push( thisPropertyMethodCall(property, "init", [arkts.factory.createUndefinedLiteral(), - arkts.factory.createTSAsExpression(property.value, property.typeAnnotation, false) + arkts.factory.createTSAsExpression(property.value, property.typeAnnotation, false), + monitorCallback ?? arkts.factory.createUndefinedLiteral() ]) ) } @@ -655,7 +656,7 @@ export class ConsumeTransformer extends PropertyTransformerBase { constructor() { super(DecoratorNames.CONSUME, "ConsumeDecoratorProperty") } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { if (property.value) throw new Error("@Consume decorator does not expect property initializer") result.push(thisPropertyMethodCall(property, "init", withStorageKey([], property, this.decoratorName))) } @@ -665,7 +666,7 @@ export class ConsumerTransformer extends PropertyTransformerBase { constructor() { super(DecoratorNames.CONSUMER, "ConsumerDecoratorProperty") } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { if (property.value == undefined) { throwPropertyRequiresInit(this.decoratorName, property) } @@ -704,7 +705,7 @@ export class BuilderParamTransformer implements PropertyTransformer { backing.setAnnotations([annotation(InternalAnnotations.MEMO)]) result.push(backing) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { checkRequiredProperty(property, result) result.push(applyInitStatement(property)) } @@ -723,8 +724,8 @@ export class LocalPropertyTransformer extends PropertyTransformerBase { constructor() { super(DecoratorNames.LOCAL, "StateDecoratorProperty") } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { - result.push(thisPropertyMethodCall(property, "init", [arkts.factory.createUndefinedLiteral(), property.value!])) + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { + result.push(thisPropertyMethodCall(property, "init", [arkts.factory.createUndefinedLiteral(), property.value!, monitorCallback ?? arkts.factory.createUndefinedLiteral()])) } applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { addPropertyRecordTo(result, property) @@ -748,7 +749,7 @@ export class ParamPropertyTransformer extends PropertyTransformerBase { false )) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]) { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]) { checkRequiredProperty(property, result) // Skip initialization if property is marked as @Require if (property.value == undefined && !hasDecorator(property, DecoratorNames.REQUIRE)) { @@ -788,12 +789,12 @@ export class EventPropertyTransformer extends PlainPropertyTransformer { applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void { result.push(...createWrappedProperty(clazz, property, "PlainStructProperty", true)) } - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void { + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void { checkRequiredProperty(property, result) if (property.value == undefined && !hasDecorator(property, DecoratorNames.REQUIRE)) { throwPropertyRequiresInit(DecoratorNames.EVENT, property) } - super.applyInitializeStruct(localStorage, property, result) + super.applyInitializeStruct(localStorage, property, undefined, result) } } @@ -805,7 +806,7 @@ export class RequirePropertyTransformer implements PropertyTransformer { collectImports(imports: Importer): void {} applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void {} applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void {} - applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, result: arkts.Statement[]): void {} + applyInitializeStruct(localStorage: arkts.Expression | undefined, property: arkts.ClassProperty, monitorCallback: arkts.Expression | undefined, result: arkts.Statement[]): void {} applyDisposeStruct(property: arkts.ClassProperty, result: arkts.Statement[]): void {} applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void {} applyBuild(property: arkts.ClassProperty, result: arkts.Statement[]): void {} diff --git a/ui2abc/ui-plugins/src/utils.ts b/ui2abc/ui-plugins/src/utils.ts index 7d9d425a3..72dfab99e 100644 --- a/ui2abc/ui-plugins/src/utils.ts +++ b/ui2abc/ui-plugins/src/utils.ts @@ -292,6 +292,7 @@ export enum DecoratorNames { OBSERVED = "Observed", OBSERVED_V2 = "ObservedV2", WATCH = "Watch", + MONITOR = "Monitor", BUILDER_PARAM = "BuilderParam", BUILDER = "Builder", CUSTOM_DIALOG = "CustomDialog", -- Gitee