From 3f3dd77361ae9db8bf69e058643b9fd40e3c07bd Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Mon, 4 Aug 2025 21:02:45 +0700 Subject: [PATCH 1/4] WIP --- ets-tests/ets/environment-tests/Pages.ets | 40 ++--- .../pages/param/ParamBasicTest.ets | 60 ++++++++ .../suites/ParamDecorator.ets | 30 ++++ .../ui-plugins/src/component-transformer.ts | 9 +- .../ui-plugins/src/property-transformers.ts | 142 ++++++++++++------ ui2abc/ui-plugins/src/utils.ts | 3 +- 6 files changed, 217 insertions(+), 67 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/ets-tests/ets/environment-tests/Pages.ets b/ets-tests/ets/environment-tests/Pages.ets index 1d57cc945d..152ee1a81f 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!`) } @@ -243,23 +246,24 @@ function pageByName(name: string): void { export function Suites(control: TestController) { registerPageByNameFunction(pageByName) suite("ETS_TESTS", () => { - suiteComponentLifeCycle(control) - suiteStateManagement(control) - suiteLinkDecorator(control) - suitePropDecorator(control) - suiteProvideConsumeDecorators(control) - suiteAppLocalStorage(control) - suiteBuilderDecorator(control) - suiteBuilderParamDecorator(control) - suiteWatchDecorator(control) - suiteObservedTest(control) - suiteAttributeModifier(control) - suiteDrawModifier(control) - suiteTrackDecorator(control) - suiteRenderServiceNode(control) - suiteTraceDecorator(control) - suiteScreenshotService(control) - suiteComponentV2Decorator(control) - suiteLocalDecorator(control) +// suiteComponentLifeCycle(control) +// suiteStateManagement(control) +// suiteLinkDecorator(control) +// suitePropDecorator(control) +// suiteProvideConsumeDecorators(control) +// suiteAppLocalStorage(control) +// suiteBuilderDecorator(control) +// suiteBuilderParamDecorator(control) +// suiteWatchDecorator(control) +// suiteObservedTest(control) +// suiteAttributeModifier(control) +// suiteDrawModifier(control) +// suiteTrackDecorator(control) +// suiteRenderServiceNode(control) +// suiteTraceDecorator(control) +// 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 0000000000..cdf11e0e17 --- /dev/null +++ b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets @@ -0,0 +1,60 @@ +/* + * 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 = false + + 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: this.count, + message: this.message, + flag: this.flag + }) + } +} + +@ComponentV2 +struct Child { + @Require @Param count: number + @Require @Param message: string + @Require @Param flag: boolean + + build() { + TestComponent({}) { + uiLog(`Param ${this.count}`) + uiLog(`Param ${this.message}`) + uiLog(`Param ${this.flag}`) + } + } +} 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 0000000000..e56f369f00 --- /dev/null +++ b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets @@ -0,0 +1,30 @@ +/* + * 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], [ + "Local 0", + "Local Hello", + "Local false" + ]) + }) + }) +} diff --git a/ui2abc/ui-plugins/src/component-transformer.ts b/ui2abc/ui-plugins/src/component-transformer.ts index 02fa482199..7618de5155 100644 --- a/ui2abc/ui-plugins/src/component-transformer.ts +++ b/ui2abc/ui-plugins/src/component-transformer.ts @@ -49,7 +49,8 @@ import { fieldOf, LocalPropertyTransformer, ProviderTransformer, - ConsumerTransformer + ConsumerTransformer, + ParamPropertyTransformer } from "./property-transformers"; import { annotation, isAnnotation, classMethods } from "./common/arkts-utils"; import { DecoratorNames, DecoratorParameters, hasBuilderDecorator, hasEntryAnnotation } from "./utils"; @@ -570,7 +571,8 @@ export class ComponentTransformer extends arkts.AbstractVisitor { new ConsumeTransformer(), new ConsumerTransformer(), new BuilderParamTransformer(), - new LocalPropertyTransformer() + new LocalPropertyTransformer(), + new ParamPropertyTransformer() ] verifyStructProperty(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty) { @@ -581,7 +583,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 +832,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 fc8813baea..d5a6dd5b3d 100644 --- a/ui2abc/ui-plugins/src/property-transformers.ts +++ b/ui2abc/ui-plugins/src/property-transformers.ts @@ -16,8 +16,19 @@ 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, + getDecoratorPackage, + getRuntimePackage, + hasDecorator, + Importer, + ImportingTransformer, + InternalAnnotations +} from "./utils" import { annotation, classMethods, isAnnotation } from "./common/arkts-utils" export interface PropertyTransformer extends ImportingTransformer { @@ -192,7 +203,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 +253,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) @@ -349,7 +365,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 { @@ -401,32 +417,49 @@ 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), + hasDecorator(property, DecoratorNames.REQUIRE) ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE : 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 { + // 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`), + ) + ) + } 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`) @@ -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,7 +666,7 @@ 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, @@ -691,6 +724,21 @@ 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[]) { + if (property.value == undefined && !hasDecorator(property, DecoratorNames.REQUIRE)) { + throwPropertyRequiresInit(this.decoratorName, property) + } + super.applyInitializeStruct(localStorage, property, result); + } +} + function applyInitStatement(property: arkts.ClassProperty): arkts.Statement { const name = property.id?.name! const initDeclaration = arkts.factory.createVariableDeclaration( @@ -729,3 +777,7 @@ 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}'`) +} diff --git a/ui2abc/ui-plugins/src/utils.ts b/ui2abc/ui-plugins/src/utils.ts index a5b34ae00f..7d9d425a3e 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 939b8a0e94d3cec0754a75830b09632dab743a17 Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Wed, 6 Aug 2025 13:27:19 +0700 Subject: [PATCH 2/4] Fixed tests --- arkoala-arkts/arkui/src/ArkStateProperties.ets | 4 +++- .../pages/param/ParamBasicTest.ets | 16 +++++++++++----- .../environment-tests/suites/ParamDecorator.ets | 14 ++++++++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/arkoala-arkts/arkui/src/ArkStateProperties.ets b/arkoala-arkts/arkui/src/ArkStateProperties.ets index ddd76c6af6..9cb07738da 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`) @@ -133,10 +133,12 @@ export class LinkDecoratorProperty implements SubscribedAbstractProperty< } get(): Value { + console.log(`LinkDecoratorProperty.get`) return this.property!.get() } set(value: Value): void { + console.log(`LinkDecoratorProperty.set`) this.property!.set(value) } diff --git a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets index cdf11e0e17..9ec25a5a10 100644 --- a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets +++ b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets @@ -20,8 +20,8 @@ import { TestComponent, uiLog } from '@ohos.arkui' struct ParamBasicTest { @Local count: number = 0 @Local message: string = "Hello" - @Local flag: boolean = false - + @Local flag: boolean = true + build() { TestComponent({}) { uiLog(`Local ${this.count}`) @@ -36,10 +36,11 @@ struct ParamBasicTest { this.message += " World" this.flag = !this.flag }) + Child({ - count: this.count, - message: this.message, - flag: this.flag + count: $count, + message: $message, + flag: $flag }) } } @@ -56,5 +57,10 @@ struct Child { uiLog(`Param ${this.message}`) uiLog(`Param ${this.flag}`) } + + TestComponent({ id: 11 }) + .onChange(() => { + + }) } } diff --git a/ets-tests/ets/environment-tests/suites/ParamDecorator.ets b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets index e56f369f00..d01171671f 100644 --- a/ets-tests/ets/environment-tests/suites/ParamDecorator.ets +++ b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets @@ -20,10 +20,20 @@ import { assert, suite, test } from "@koalaui/harness" export function suiteParamDecorator(control: TestController) { suite("ParamDecoratorTest", () => { test("@Param decorator basic test", () => { - testPageOnChange(control, "ParamBasicTest", [10], [ + testPageOnChange(control, "ParamBasicTest", [10, 11], [ "Local 0", "Local Hello", - "Local false" + "Local true", + "Param 0", + "Param Hello", + "Param true", + + "Local 1", + "Local Hello World", + "Local false", + "Param 1", + "Param Hello World", + "Param false" ]) }) }) -- Gitee From ae42dcc6311e16a688136a33f5832b28fc8d7814 Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Wed, 6 Aug 2025 17:19:36 +0700 Subject: [PATCH 3/4] Added @Event --- .../arkui/src/ArkStateProperties.ets | 2 - .../pages/param/ParamBasicTest.ets | 8 ++- .../suites/ParamDecorator.ets | 4 +- .../ui-plugins/src/component-transformer.ts | 6 +- .../ui-plugins/src/property-transformers.ts | 62 +++++++++++++++---- 5 files changed, 63 insertions(+), 19 deletions(-) diff --git a/arkoala-arkts/arkui/src/ArkStateProperties.ets b/arkoala-arkts/arkui/src/ArkStateProperties.ets index 9cb07738da..cf4a221daa 100644 --- a/arkoala-arkts/arkui/src/ArkStateProperties.ets +++ b/arkoala-arkts/arkui/src/ArkStateProperties.ets @@ -133,12 +133,10 @@ export class LinkDecoratorProperty implements SubscribedAbstractProperty< } get(): Value { - console.log(`LinkDecoratorProperty.get`) return this.property!.get() } set(value: Value): void { - console.log(`LinkDecoratorProperty.set`) this.property!.set(value) } diff --git a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets index 9ec25a5a10..9df48775db 100644 --- a/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets +++ b/ets-tests/ets/environment-tests/pages/param/ParamBasicTest.ets @@ -40,7 +40,10 @@ struct ParamBasicTest { Child({ count: $count, message: $message, - flag: $flag + flag: $flag, + changeCount: (x: number) => { + this.count = x + } }) } } @@ -50,6 +53,7 @@ struct Child { @Require @Param count: number @Require @Param message: string @Require @Param flag: boolean + @Require @Event changeCount: (x: number) => void build() { TestComponent({}) { @@ -60,7 +64,7 @@ struct Child { 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 index d01171671f..1dca309e71 100644 --- a/ets-tests/ets/environment-tests/suites/ParamDecorator.ets +++ b/ets-tests/ets/environment-tests/suites/ParamDecorator.ets @@ -33,7 +33,9 @@ export function suiteParamDecorator(control: TestController) { "Local false", "Param 1", "Param Hello World", - "Param false" + "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 7618de5155..7a86b5ffb5 100644 --- a/ui2abc/ui-plugins/src/component-transformer.ts +++ b/ui2abc/ui-plugins/src/component-transformer.ts @@ -50,7 +50,8 @@ import { LocalPropertyTransformer, ProviderTransformer, ConsumerTransformer, - ParamPropertyTransformer + ParamPropertyTransformer, + EventPropertyTransformer } from "./property-transformers"; import { annotation, isAnnotation, classMethods } from "./common/arkts-utils"; import { DecoratorNames, DecoratorParameters, hasBuilderDecorator, hasEntryAnnotation } from "./utils"; @@ -572,7 +573,8 @@ export class ComponentTransformer extends arkts.AbstractVisitor { new ConsumerTransformer(), new BuilderParamTransformer(), new LocalPropertyTransformer(), - new ParamPropertyTransformer() + new ParamPropertyTransformer(), + new EventPropertyTransformer() ] verifyStructProperty(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty) { diff --git a/ui2abc/ui-plugins/src/property-transformers.ts b/ui2abc/ui-plugins/src/property-transformers.ts index d5a6dd5b3d..a9aefa847c 100644 --- a/ui2abc/ui-plugins/src/property-transformers.ts +++ b/ui2abc/ui-plugins/src/property-transformers.ts @@ -431,18 +431,7 @@ class LinkablePropertyTransformer extends PropertyTransformerBase { )) } applyInitializeStruct(localStorage: arkts.Expression | undefined, 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`), - ) - ) - } + checkRequiredProperty(property, result) result.push(thisPropertyMethodCall(property, "linkTo", [initializerOf(property)])) } collectImports(imports: Importer): void { @@ -739,6 +728,40 @@ export class ParamPropertyTransformer extends LinkablePropertyTransformer { } } +export class EventPropertyTransformer implements PropertyTransformer { + check(property: arkts.ClassProperty): boolean { + return hasDecorator(property, DecoratorNames.EVENT) + } + collectImports(imports: Importer): void { + imports.add("PlainStructProperty", getDecoratorPackage()) + } + applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { + result.push(arkts.factory.createClassProperty( + arkts.factory.createIdentifier(property.id?.name!), + undefined, + property.typeAnnotation, + hasDecorator(property, DecoratorNames.REQUIRE) ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL, + false + )) + } + 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 { + checkRequiredProperty(property, result) + if (property.value == undefined && !hasDecorator(property, DecoratorNames.REQUIRE)) { + throwPropertyRequiresInit(DecoratorNames.EVENT, property) + } + result.push(thisPropertyMethodCall(property, "init", [initializerOf(property)])) + } + 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( @@ -781,3 +804,18 @@ export function isOptionBackedByPropertyName(decorator: string): boolean { function throwPropertyRequiresInit(decorator: DecoratorNames, property: arkts.ClassProperty): string { throw new Error(`@${decorator} decorator requires an initial value for the class property '${property.id!.name}'`) } + +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`), + ) + ) + } +} \ No newline at end of file -- Gitee From b3a874b737e88be4467134eebeb2080245dfd6a9 Mon Sep 17 00:00:00 2001 From: vadimdolgachev Date: Wed, 6 Aug 2025 18:39:23 +0700 Subject: [PATCH 4/4] WIP --- ets-tests/ets/environment-tests/Pages.ets | 36 ++-- .../ui-plugins/src/component-transformer.ts | 6 +- .../ui-plugins/src/property-transformers.ts | 179 +++++++++++------- 3 files changed, 128 insertions(+), 93 deletions(-) diff --git a/ets-tests/ets/environment-tests/Pages.ets b/ets-tests/ets/environment-tests/Pages.ets index 152ee1a81f..ecab442f63 100644 --- a/ets-tests/ets/environment-tests/Pages.ets +++ b/ets-tests/ets/environment-tests/Pages.ets @@ -246,24 +246,24 @@ function pageByName(name: string): void { export function Suites(control: TestController) { registerPageByNameFunction(pageByName) suite("ETS_TESTS", () => { -// suiteComponentLifeCycle(control) -// suiteStateManagement(control) -// suiteLinkDecorator(control) -// suitePropDecorator(control) -// suiteProvideConsumeDecorators(control) -// suiteAppLocalStorage(control) -// suiteBuilderDecorator(control) -// suiteBuilderParamDecorator(control) -// suiteWatchDecorator(control) -// suiteObservedTest(control) -// suiteAttributeModifier(control) -// suiteDrawModifier(control) -// suiteTrackDecorator(control) -// suiteRenderServiceNode(control) -// suiteTraceDecorator(control) -// suiteScreenshotService(control) -// suiteComponentV2Decorator(control) -// suiteLocalDecorator(control) + suiteComponentLifeCycle(control) + suiteStateManagement(control) + suiteLinkDecorator(control) + suitePropDecorator(control) + suiteProvideConsumeDecorators(control) + suiteAppLocalStorage(control) + suiteBuilderDecorator(control) + suiteBuilderParamDecorator(control) + suiteWatchDecorator(control) + suiteObservedTest(control) + suiteAttributeModifier(control) + suiteDrawModifier(control) + suiteTrackDecorator(control) + suiteRenderServiceNode(control) + suiteTraceDecorator(control) + suiteScreenshotService(control) + suiteComponentV2Decorator(control) + suiteLocalDecorator(control) suiteParamDecorator(control) }) } \ No newline at end of file diff --git a/ui2abc/ui-plugins/src/component-transformer.ts b/ui2abc/ui-plugins/src/component-transformer.ts index 7a86b5ffb5..25d3a417bc 100644 --- a/ui2abc/ui-plugins/src/component-transformer.ts +++ b/ui2abc/ui-plugins/src/component-transformer.ts @@ -51,7 +51,8 @@ import { ProviderTransformer, ConsumerTransformer, ParamPropertyTransformer, - EventPropertyTransformer + EventPropertyTransformer, + RequirePropertyTransformer } from "./property-transformers"; import { annotation, isAnnotation, classMethods } from "./common/arkts-utils"; import { DecoratorNames, DecoratorParameters, hasBuilderDecorator, hasEntryAnnotation } from "./utils"; @@ -574,7 +575,8 @@ export class ComponentTransformer extends arkts.AbstractVisitor { new BuilderParamTransformer(), new LocalPropertyTransformer(), new ParamPropertyTransformer(), - new EventPropertyTransformer() + new EventPropertyTransformer(), + new RequirePropertyTransformer() ] verifyStructProperty(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty) { diff --git a/ui2abc/ui-plugins/src/property-transformers.ts b/ui2abc/ui-plugins/src/property-transformers.ts index a9aefa847c..7c7c90a420 100644 --- a/ui2abc/ui-plugins/src/property-transformers.ts +++ b/ui2abc/ui-plugins/src/property-transformers.ts @@ -22,12 +22,14 @@ import { DecoratorNames, DecoratorParameters, findDecorator, + getAnnotationName, getDecoratorPackage, getRuntimePackage, hasDecorator, Importer, ImportingTransformer, - InternalAnnotations + InternalAnnotations, + isDecoratorAnnotation } from "./utils" import { annotation, classMethods, isAnnotation } from "./common/arkts-utils" @@ -82,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!) @@ -129,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] @@ -357,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 { @@ -385,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 { @@ -394,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 { @@ -406,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 { @@ -426,7 +434,7 @@ class LinkablePropertyTransformer extends PropertyTransformerBase { arkts.factory.createIdentifier(property.id?.name!), undefined, createWrapperType("SubscribedAbstractProperty", property.typeAnnotation!, true), - hasDecorator(property, DecoratorNames.REQUIRE) ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL, + arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL, false )) } @@ -466,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))) } } @@ -489,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 { @@ -594,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 { @@ -660,13 +671,18 @@ export class ConsumerTransformer extends PropertyTransformerBase { 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 { @@ -688,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 { @@ -721,6 +738,8 @@ export class ParamPropertyTransformer extends LinkablePropertyTransformer { 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) } @@ -728,38 +747,35 @@ export class ParamPropertyTransformer extends LinkablePropertyTransformer { } } -export class EventPropertyTransformer implements PropertyTransformer { +export class EventPropertyTransformer extends PlainPropertyTransformer { check(property: arkts.ClassProperty): boolean { + checkRequireDecoratorUsage(property) return hasDecorator(property, DecoratorNames.EVENT) } - collectImports(imports: Importer): void { - imports.add("PlainStructProperty", getDecoratorPackage()) - } - applyOptions(property: arkts.ClassProperty, result: arkts.ClassElement[]): void { - result.push(arkts.factory.createClassProperty( - arkts.factory.createIdentifier(property.id?.name!), - undefined, - property.typeAnnotation, - hasDecorator(property, DecoratorNames.REQUIRE) ? arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_NONE : arkts.Es2pandaModifierFlags.MODIFIER_FLAGS_OPTIONAL, - false - )) - } applyStruct(clazz: arkts.ClassDeclaration, property: arkts.ClassProperty, result: arkts.ClassElement[]): void { - result.push(...createWrappedProperty(clazz, property, "PlainStructProperty")) + 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) } - result.push(thisPropertyMethodCall(property, "init", [initializerOf(property)])) - } - applyDisposeStruct(property: arkts.ClassProperty, result: arkts.Statement[]): void { + super.applyInitializeStruct(localStorage, property, result) } - applyReuseRecord(property: arkts.ClassProperty, result: arkts.Expression[]): void { - } - applyBuild(property: arkts.ClassProperty, result: arkts.Statement[]): void { +} + +// @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 { @@ -805,6 +821,7 @@ function throwPropertyRequiresInit(decorator: DecoratorNames, property: arkts.Cl 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)) { @@ -818,4 +835,20 @@ function checkRequiredProperty(property: arkts.ClassProperty, result: arkts.Stat ) ) } +} + +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 -- Gitee