From 275af4e136696efeb93b4d676c7b0e65abdf8485 Mon Sep 17 00:00:00 2001 From: wangweiyuan Date: Wed, 18 Jun 2025 21:23:52 +0800 Subject: [PATCH] fix ui-syntax-plugins bug 0618 Signed-off-by: wangweiyuan --- .../rules/attribute-no-invoke.ts | 14 +- .../rules/build-root-node.ts | 139 ++++++------ .../rules/builderparam-decorator-check.ts | 12 +- .../rules/component-componentV2-init-check.ts | 23 +- .../rules/componentV2-mix-check.ts | 12 +- .../componentV2-state-usage-validation.ts | 167 +------------- .../rules/computed-decorator-check.ts | 78 +++---- .../consumer-provider-decorator-check.ts | 126 +++-------- .../rules/custom-dialog-missing-controller.ts | 35 ++- .../rules/decorators-in-ui-component-only.ts | 146 ------------ .../ui-syntax-plugins/rules/index.ts | 10 +- .../rules/local-builder-check.ts | 63 ------ .../rules/nested-reuse-component-check.ts | 6 + .../rules/no-child-in-button.ts | 9 + .../rules/no-duplicate-entry.ts | 18 +- .../rules/no-duplicate-id.ts | 2 +- .../rules/no-duplicate-preview.ts | 6 +- .../rules/no-duplicate-state-manager.ts | 6 +- .../observedV2-trace-usage-validation.ts | 38 ++-- .../rules/once-decorator-check.ts | 59 +---- .../rules/reusable-component-in-V2-check.ts | 9 +- .../rules/reuse-attribute-check.ts | 6 +- .../rules/track-decorator-check.ts | 36 +-- ...iable-initialization-via-component-cons.ts | 210 ------------------ ...nitialization-via-component-constructor.ts | 65 +----- .../rules/watch-decorator-function.ts | 26 +-- .../rules/watch-decorator-regular.ts | 6 +- .../rules/wrap-builder-check.ts | 35 +-- .../ui-syntax-plugins/utils/index.ts | 11 + 29 files changed, 328 insertions(+), 1045 deletions(-) delete mode 100644 arkui-plugins/ui-syntax-plugins/rules/decorators-in-ui-component-only.ts delete mode 100644 arkui-plugins/ui-syntax-plugins/rules/local-builder-check.ts delete mode 100644 arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-cons.ts diff --git a/arkui-plugins/ui-syntax-plugins/rules/attribute-no-invoke.ts b/arkui-plugins/ui-syntax-plugins/rules/attribute-no-invoke.ts index 86691ae22..049b1c619 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/attribute-no-invoke.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/attribute-no-invoke.ts @@ -15,7 +15,6 @@ import * as arkts from '@koalaui/libarkts'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; -import { getIdentifierName, BUILD_NAME } from '../utils'; function attributeNoInvoke(node: arkts.AstNode, context: UISyntaxRuleContext): void { const childNode = node.getChildren(); @@ -33,17 +32,6 @@ function attributeNoInvoke(node: arkts.AstNode, context: UISyntaxRuleContext): v } } -function isInBuild(node: arkts.AstNode): boolean { - let structNode = node.parent; - while (!arkts.isMethodDefinition(structNode) || getIdentifierName(structNode.name) !== BUILD_NAME) { - if (!structNode.parent) { - return false; - } - structNode = structNode.parent; - } - return true; -} - function chainJudgment(node: arkts.AstNode): boolean { let childNode = node.getChildren(); while (true) { @@ -71,7 +59,7 @@ const rule: UISyntaxRule = { setup(context) { return { parsed: (node): void => { - if (arkts.isExpressionStatement(node) && isInBuild(node) && chainJudgment(node)) { + if (arkts.isExpressionStatement(node) && !arkts.isIdentifier(node.expression) && chainJudgment(node)) { attributeNoInvoke(node, context); } }, diff --git a/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts b/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts index 48c588453..ce8a01e04 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts @@ -17,84 +17,85 @@ import * as arkts from '@koalaui/libarkts'; import { getIdentifierName, getAnnotationUsage, PresetDecorators, BUILD_NAME } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; +const BUILD_ROOT_NUM: number = 1; const STATEMENT_LENGTH: number = 1; class BuildRootNodeRule extends AbstractUISyntaxRule { - public setup(): Record { - return { - invalidEntryBuildRoot: `In an '@Entry' decorated component, the 'build' function can have only one root node, which must be a container component.`, - invalidBuildRoot: `The 'build' function can have only one root node.`, - }; - } + public setup(): Record { + return { + invalidEntryBuildRoot: `In an '@Entry' decorated component, the 'build' function can have only one root node, which must be a container component.`, + invalidBuildRoot: `The 'build' function can have only one root node.`, + }; + } - public parsed(node: arkts.AstNode): void { - if (!arkts.isStructDeclaration(node)) { - return; - } - const entryDecoratorUsage = getAnnotationUsage(node, PresetDecorators.ENTRY); - node.definition.body.forEach((member) => { - if (!arkts.isMethodDefinition(member) || getIdentifierName(member.name) !== BUILD_NAME) { - return; - } - const blockStatement = member.scriptFunction.body; - if (!blockStatement || !arkts.isBlockStatement(blockStatement)) { - return; - } - const buildNode = member.scriptFunction.id; - if (!buildNode) { - return; - } - const statements = blockStatement.statements; - if (statements.length > STATEMENT_LENGTH) { - // rule1: The 'build' method cannot have more than one root node. - this.report({ - node: buildNode, - message: entryDecoratorUsage ? this.messages.invalidEntryBuildRoot : this.messages.invalidBuildRoot, - }); - } - if (!statements.length || !entryDecoratorUsage) { - return; - } - const expressionStatement = statements[0]; - if (!arkts.isExpressionStatement(expressionStatement)) { - return; - } - const callExpression = expressionStatement.expression; - if (!arkts.isCallExpression(callExpression)) { - return; - } - let componentName: string = this.getComponentName(callExpression); - let isContainer: boolean = this.isContainerComponent(componentName); - // rule2: its 'build' function can have only one root node, which must be a container component. - if (!isContainer) { - this.report({ - node: buildNode, - message: this.messages.invalidEntryBuildRoot, - }); - } - }); + public parsed(node: arkts.AstNode): void { + if (!arkts.isStructDeclaration(node)) { + return; } + const entryDecoratorUsage = getAnnotationUsage(node, PresetDecorators.ENTRY); + node.definition.body.forEach((member) => { + if (!arkts.isMethodDefinition(member) || getIdentifierName(member.name) !== BUILD_NAME) { + return; + } + const blockStatement = member.scriptFunction.body; + if (!blockStatement || !arkts.isBlockStatement(blockStatement)) { + return; + } + const buildNode = member.scriptFunction.id; + if (!buildNode) { + return; + } + const statements = blockStatement.statements; + if (statements.length > STATEMENT_LENGTH) { + // rule1: The 'build' method cannot have more than one root node. + this.report({ + node: buildNode, + message: entryDecoratorUsage ? this.messages.invalidEntryBuildRoot : this.messages.invalidBuildRoot, + }); + } + if (!statements.length || !entryDecoratorUsage) { + return; + } + const expressionStatement = statements[0]; + if (!arkts.isExpressionStatement(expressionStatement)) { + return; + } + const callExpression = expressionStatement.expression; + if (!arkts.isCallExpression(callExpression)) { + return; + } + let componentName: string = this.getComponentName(callExpression); + let isContainer: boolean = this.isContainerComponent(componentName); + // rule2: its 'build' function can have only one root node, which must be a container component. + if (!isContainer) { + this.report({ + node: buildNode, + message: this.messages.invalidEntryBuildRoot, + }); + } + }); + } - private isContainerComponent(componentName: string): boolean { - const loadedContainerComponents = this.context.componentsInfo.containerComponents; - if (!componentName || !loadedContainerComponents) { - return false; - } - return loadedContainerComponents.includes(componentName); + private isContainerComponent(componentName: string): boolean { + const loadedContainerComponents = this.context.componentsInfo.containerComponents; + if (!componentName || !loadedContainerComponents) { + return false; } + return loadedContainerComponents.includes(componentName); + } - private getComponentName(callExpression: arkts.CallExpression): string { - if ( - arkts.isMemberExpression(callExpression.expression) && - arkts.isCallExpression(callExpression.expression.object) && - arkts.isIdentifier(callExpression.expression.object.expression) - ) { - return getIdentifierName(callExpression.expression.object.expression); - } else if (arkts.isIdentifier(callExpression.expression)) { - return getIdentifierName(callExpression.expression); - } - return ''; + private getComponentName(callExpression: arkts.CallExpression): string { + if ( + arkts.isMemberExpression(callExpression.expression) && + arkts.isCallExpression(callExpression.expression.object) && + arkts.isIdentifier(callExpression.expression.object.expression) + ) { + return getIdentifierName(callExpression.expression.object.expression); + } else if (arkts.isIdentifier(callExpression.expression)) { + return getIdentifierName(callExpression.expression); } + return ''; + } } export default BuildRootNodeRule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/builderparam-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/builderparam-decorator-check.ts index 67cfb950c..0a14f3c4c 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/builderparam-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/builderparam-decorator-check.ts @@ -35,12 +35,12 @@ function getStructNameWithMultiplyBuilderParam( if (!arkts.isClassProperty(item) || !item.key) { return; } - const hasBuilderParam = item.annotations.find(annotation => + const builderParam = item.annotations.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.BUILDER_PARAM ); - if (hasBuilderParam) { + if (builderParam) { count++; } }); @@ -51,6 +51,9 @@ function getStructNameWithMultiplyBuilderParam( } function isInBuild(node: arkts.AstNode): boolean { + if (!node.parent) { + return false; + } let structNode = node.parent; arkts.isMethodDefinition(structNode); while (!arkts.isMethodDefinition(structNode) || getIdentifierName(structNode.name) !== BUILD_NAME) { @@ -63,6 +66,9 @@ function isInBuild(node: arkts.AstNode): boolean { } function hasBlockStatement(node: arkts.AstNode): boolean { + if (!node.parent) { + return false; + } let parentNode = node.parent; const siblings = parentNode.getChildren(); if (!Array.isArray(siblings) || siblings.length < 2) { @@ -83,7 +89,7 @@ function checkComponentInitialize( if (!arkts.isIdentifier(node) || !structNameWithMultiplyBuilderParam.includes(getIdentifierName(node))) { return; } - if (!hasBlockStatement(node)) { + if (!hasBlockStatement(node) || !node.parent) { return; } let structName: string = getIdentifierName(node); diff --git a/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-init-check.ts b/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-init-check.ts index df746ab4c..e7d90dc82 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-init-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-init-check.ts @@ -47,20 +47,27 @@ function checkComponentInitLink( if (!arkts.isIdentifier(node) || !componentV1WithLinkList.includes(getIdentifierName(node))) { return; } - let parentNode = node.parent; - while (!arkts.isStructDeclaration(parentNode)) { - if (!parentNode.parent) { + if (!node.parent) { + return; + } + let structNode = node.parent; + while (!arkts.isStructDeclaration(structNode)) { + if (!structNode.parent) { return; } - parentNode = parentNode.parent; + structNode = structNode.parent; } - if (getAnnotationUsage(parentNode, PresetDecorators.COMPONENT_V2) !== undefined) { + if (getAnnotationUsage(structNode, PresetDecorators.COMPONENT_V2) !== undefined) { + if (!node.parent) { + return; + } + const parentNode = node.parent; context.report({ - node: node.parent, + node: parentNode, message: rule.messages.componentInitLinkCheck, - fix: (node) => { + fix: () => { return { - range: [node.startPosition, node.endPosition], + range: [parentNode.startPosition, parentNode.endPosition], code: '', }; } diff --git a/arkui-plugins/ui-syntax-plugins/rules/componentV2-mix-check.ts b/arkui-plugins/ui-syntax-plugins/rules/componentV2-mix-check.ts index b29ca95f2..93f8a023a 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/componentV2-mix-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/componentV2-mix-check.ts @@ -37,15 +37,15 @@ const rule: UISyntaxRule = { return; } // Check if the struct has the '@ComponentV2' annotation - const hasComponentV2 = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - if (!hasComponentV2) { + const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + if (!componentV2Decorator) { return; } // Check for the presence of conflicting decorators: '@Component', '@Reusable', '@CustomDialog' - const hasComponent = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); - const hasReusable = getAnnotationUsage(node, PresetDecorators.REUSABLE_V1); - const hasCustomDialog = getAnnotationUsage(node, PresetDecorators.CUSTOM_DIALOG); - if (hasComponent || hasReusable || hasCustomDialog) { + const componentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); + const reusableDecorator = getAnnotationUsage(node, PresetDecorators.REUSABLE_V1); + const customDialogDecorator = getAnnotationUsage(node, PresetDecorators.CUSTOM_DIALOG); + if (componentDecorator || reusableDecorator || customDialogDecorator) { context.report({ node: structNameNode, message: rule.messages.conflictWithComponentV2, diff --git a/arkui-plugins/ui-syntax-plugins/rules/componentV2-state-usage-validation.ts b/arkui-plugins/ui-syntax-plugins/rules/componentV2-state-usage-validation.ts index 36abf5792..345b9058d 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/componentV2-state-usage-validation.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/componentV2-state-usage-validation.ts @@ -45,46 +45,10 @@ function reportMultipleBuiltInDecoratorsError(context: UISyntaxRuleContext, anno context.report({ node: annotation, message: rule.messages.multipleBuiltInDecorators, - fix: (annotation) => { - const startPosition = annotation.startPosition; - const endPosition = annotation.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, }); } } -function checkDecoratorOnlyInisComponentV2(context: UISyntaxRuleContext, member: arkts.ClassProperty, - node: arkts.StructDeclaration, hasisComponentV2: boolean, hasComponent: boolean): void { - member.annotations?.forEach(annotation => { - if (annotation.expr && arkts.isIdentifier(annotation.expr)) { - const annotationsName = annotation.expr?.name; - if (annotationsName && builtInDecorators.includes(annotationsName) && !hasisComponentV2 && !hasComponent) { - reportDecoratorOnlyInisComponentV2Error(context, annotation, annotationsName, node); - } - } - }); -}; - -function reportDecoratorOnlyInisComponentV2Error(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage, - annotationsName: string, node: arkts.StructDeclaration): void { - context.report({ - node: annotation, - message: rule.messages.decoratorOnlyInisComponentV2, - data: { annotationsName }, - fix: (annotation) => { - const startPosition = node.definition.startPosition; - return { - range: [startPosition, startPosition], - code: `@${PresetDecorators.COMPONENT_V2}\n`, - }; - }, - }); -} - function checkParamRequiresRequire(context: UISyntaxRuleContext, member: arkts.ClassProperty, propertyDecorators: string[]): void { if (propertyDecorators.includes(PresetDecorators.PARAM) && !member.value && @@ -109,7 +73,10 @@ function checkRequireOnlyWithParam(context: UISyntaxRuleContext, member: arkts.C const requireDecorator = member.annotations?.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.REQUIRE ); - if (isComponentV2 && requireDecorator && !propertyDecorators.includes(PresetDecorators.PARAM)) { + if (isComponentV2 && + requireDecorator && + !propertyDecorators.includes(PresetDecorators.PARAM) && + !propertyDecorators.includes(PresetDecorators.BUILDER_PARAM)) { context.report({ node: requireDecorator, message: rule.messages.requireOnlyWithParam, @@ -151,112 +118,6 @@ function reportInvalidDecoratorOnMethod(context: UISyntaxRuleContext, annotation }); } -function checkMustInitialize( - node: arkts.AstNode, - context: UISyntaxRuleContext, - mustInitMap: Map> -): void { - if (!arkts.isIdentifier(node)) { - return; - } - const structName: string = getIdentifierName(node); - if (!mustInitMap.has(structName)) { - return; - } - const parentNode: arkts.AstNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; - } - // Get all the properties of a record via StructName - const mustInitName: Map = mustInitMap.get(structName)!; - parentNode.arguments?.forEach((member) => { - const childkeyNameArray: string[] = getChildKeyNameArray(member); - mustInitName.forEach((value, key) => { - // If an attribute that must be initialized is not initialized, an error is reported - if (!childkeyNameArray.includes(key)) { - context.report({ - node: parentNode, - message: rule.messages.paramNeedInit, - data: { - name: key, - }, - }); - } - }); - }); -} - -function getChildKeyNameArray(member: arkts.AstNode): string[] { - const childkeyNameArray: string[] = []; - member.getChildren().forEach((property) => { - if (!arkts.isProperty(property) || !property.key || !arkts.isIdentifier(property.key)) { - return; - } - const childkeyName = property.key.name; - if (childkeyName !== '') { - childkeyNameArray.push(childkeyName); - } - }); - return childkeyNameArray; -} - -function initMap(node: arkts.AstNode, mustInitMap: Map>, mustInitArray: string[][], -): void { - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; - } - node.getChildren().forEach((member) => { - if (!(arkts.isStructDeclaration(member) || - (arkts.isClassDeclaration(member) && !!member.definition && - arkts.classDefinitionIsFromStructConst(member.definition)) - )) { - return; - } - const structName: string = member.definition!.ident?.name ?? ''; - if (structName === '') { - return; - } - member.definition?.body.forEach((item) => { - checkPropertyByAnnotations(item, structName, mustInitMap, mustInitArray); - }); - }); -} - -function checkPropertyByAnnotations( - item: arkts.AstNode, - structName: string, - mustInitMap: Map>, - mustInitArray: string[][] -): void { - if (!arkts.isClassProperty(item) || !item.key || !arkts.isIdentifier(item.key)) { - return; - } - const propertyName: string = item.key.name; - if (item.annotations.length === 0 || propertyName === '') { - return; - } - const annotationArray: string[] = getClassPropertyAnnotationNames(item); - // If the member variable is decorated, it is added to the corresponding map - mustInitArray.forEach(arr => { - if (arr.every(annotation => annotationArray.includes(annotation))) { - const annotationName: string = arr[0]; - addProperty(mustInitMap, structName, propertyName, annotationName); - } - }); -} - -// Define a function to add property data to the property map -function addProperty(propertyMap: Map>, structName: string, - propertyName: string, annotationName: string): void { - if (!propertyMap.has(structName)) { - propertyMap.set(structName, new Map()); - } - const structProperties = propertyMap.get(structName); - if (structProperties) { - structProperties.set(propertyName, annotationName); - } -} - function validateClassPropertyDecorators(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void { const isComponentV2 = hasisComponentV2(node); const isComponent = hasComponent(node); @@ -268,13 +129,10 @@ function validateClassPropertyDecorators(context: UISyntaxRuleContext, node: ark // Rule 1: Multiple built-in decorators checkMultipleBuiltInDecorators(context, member, propertyDecorators); - // Rule 2: Built-in decorators only allowed in @isComponentV2 - checkDecoratorOnlyInisComponentV2(context, member, node, isComponentV2, isComponent); - - // Rule 3: @Param without default value must be combined with @Require + // Rule 2: @Param without default value must be combined with @Require checkParamRequiresRequire(context, member, propertyDecorators); - // Rule 4: @Require must be used together with @Param + // Rule 3: @Require must be used together with @Param checkRequireOnlyWithParam(context, member, propertyDecorators, isComponentV2); }); } @@ -360,7 +218,7 @@ function checkInitializeRule( context: UISyntaxRuleContext, componentV2PropertyMap: Map>, ): void { - if (!arkts.isIdentifier(node) || !componentV2PropertyMap.has(getIdentifierName(node))) { + if (!arkts.isIdentifier(node) || !componentV2PropertyMap.has(getIdentifierName(node)) || !node.parent) { return; } let structName: string = getIdentifierName(node); @@ -450,27 +308,20 @@ const rule: UISyntaxRule = { name: 'componentV2-state-usage-validation', messages: { multipleBuiltInDecorators: `The member property or method cannot be decorated by multiple built-in decorators.`, - decoratorOnlyInisComponentV2: `The '@{{annotationsName}}' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`, paramRequiresRequire: `When a variable decorated with '@Param' is not assigned a default value, it must also be decorated with '@Require'.`, - requireOnlyWithParam: `In a struct decorated with '@ComponentV2', '@Require' can only be used with @Param.`, + requireOnlyWithParam: `In a struct decorated with '@ComponentV2', '@Require' can only be used with '@Param' or '@BuilderParam'.`, localNeedNoInit: `The '{{decoratorName}}' property '{{key}}' in the custom component '{{componentName}}' cannot be initialized here (forbidden to specify).`, - paramNeedInit: `Property '{{name}}' must be initialized through the component constructor.`, useStateDecoratorsWithProperty: `'@{{annotationName}}' can only decorate member property.`, }, setup(context) { - let mustInitMap: Map> = new Map(); - const mustInitArray: string[][] = [[PresetDecorators.REQUIRE, PresetDecorators.PARAM]]; let componentV2PropertyMap: Map> = new Map(); return { parsed: (node): void => { initComponentV2PropertyMap(node, componentV2PropertyMap); checkInitializeRule(node, context, componentV2PropertyMap); - initMap(node, mustInitMap, mustInitArray); - // Rule 6: Property with require and Param must be initialized through the component constructor - checkMustInitialize(node, context, mustInitMap); if (arkts.isMethodDefinition(node)) { - // Rule 7: Local, Param, Event decorators must be used with Property + // Rule 5: Local, Param, Event decorators must be used with Property checkuseStateDecoratorsWithProperty(context, node); } if (!arkts.isStructDeclaration(node)) { diff --git a/arkui-plugins/ui-syntax-plugins/rules/computed-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/computed-decorator-check.ts index f13ea578a..19215a47a 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/computed-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/computed-decorator-check.ts @@ -23,7 +23,7 @@ function validateStructBody( computedSetters: Map, context: UISyntaxRuleContext ): void { - let hasComputed: arkts.AnnotationUsage | undefined; + let computedDecorator: arkts.AnnotationUsage | undefined; node.definition.body.forEach((member) => { if (arkts.isClassProperty(member)) { validateComputedOnClassProperty(member, context); @@ -31,11 +31,11 @@ function validateStructBody( } if (arkts.isMethodDefinition(member)) { const methodName = getIdentifierName(member.name); - hasComputed = member.scriptFunction.annotations?.find(annotation => + computedDecorator = member.scriptFunction.annotations?.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.COMPUTED ); - validateComputedMethodKind(member, hasComputed, methodName, computedGetters, context); + validateComputedMethodKind(member, computedDecorator, methodName, computedGetters, context); const isSetter = member.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; if (isSetter) { computedSetters.set(methodName, member); @@ -49,17 +49,17 @@ function validateStructBody( } function validateComputedOnClassProperty(member: arkts.ClassProperty, context: UISyntaxRuleContext): void { - const hasComputed = member.annotations?.find(annotation => + const computedDecorator = member.annotations?.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.COMPUTED ); - if (hasComputed) { + if (computedDecorator) { context.report({ - node: hasComputed, + node: computedDecorator, message: rule.messages.onlyOnGetter, - fix: (hasComputed) => { - const startPosition = hasComputed.startPosition; - const endPosition = hasComputed.endPosition; + fix: (computedDecorator) => { + const startPosition = computedDecorator.startPosition; + const endPosition = computedDecorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -71,15 +71,15 @@ function validateComputedOnClassProperty(member: arkts.ClassProperty, context: U function validateComputedMethodKind( member: arkts.MethodDefinition, - hasComputed: arkts.AnnotationUsage | undefined, + computedDecorator: arkts.AnnotationUsage | undefined, methodName: string, computedGetters: Map, context: UISyntaxRuleContext ): void { - if (hasComputed) { + if (computedDecorator) { const isGetter = member.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; if (!isGetter) { - reportValidateComputedMethodKind(hasComputed, context); + reportValidateComputedMethodKind(computedDecorator, context); } else { computedGetters.set(methodName, member); } @@ -87,18 +87,18 @@ function validateComputedMethodKind( } function reportValidateComputedMethodKind( - hasComputed: arkts.AnnotationUsage | undefined, + computedDecorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { - if (!hasComputed) { + if (!computedDecorator) { return; } context.report({ - node: hasComputed, + node: computedDecorator, message: rule.messages.onlyOnGetter, - fix: (hasComputed) => { - const startPosition = hasComputed.startPosition; - const endPosition = hasComputed.endPosition; + fix: (computedDecorator) => { + const startPosition = computedDecorator.startPosition; + const endPosition = computedDecorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -211,21 +211,21 @@ function validateClassBody( computedGetters: Map, context: UISyntaxRuleContext ): void { - const hasObservedV2 = node.definition?.annotations.find(annotation => + const observedV2Decorator = node.definition?.annotations.find(annotation => getAnnotationName(annotation) === PresetDecorators.OBSERVED_V2 ); node.definition?.body.forEach((member) => { if (arkts.isMethodDefinition(member)) { - validateComputedInClass(node, member, hasObservedV2, context); + validateComputedInClass(node, member, observedV2Decorator, context); if (!arkts.isIdentifier(member.name)) { return; } const methodName = getIdentifierName(member.name); - const hasComputed = member.scriptFunction.annotations?.find(annotation => + const computedDecorator = member.scriptFunction.annotations?.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.COMPUTED ); - validateComputedMethodKind(member, hasComputed, methodName, computedGetters, context); + validateComputedMethodKind(member, computedDecorator, methodName, computedGetters, context); } }); } @@ -234,29 +234,29 @@ function validateComponentV2InStruct( node: arkts.StructDeclaration, context: UISyntaxRuleContext ): void { - const hasComponentV2 = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - const hasComponent = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); + const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + const componentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); node.definition?.body.forEach((member) => { if (arkts.isMethodDefinition(member)) { - reportComponentV2InStruct(node, member, hasComponentV2, hasComponent, context); + checkComponentV2InStruct(node, member, componentV2Decorator, componentDecorator, context); } }); } -function reportComponentV2InStruct( +function checkComponentV2InStruct( node: arkts.StructDeclaration | arkts.ClassDeclaration, member: arkts.MethodDefinition, - hasComponentV2: arkts.AnnotationUsage | undefined, - hasComponent: arkts.AnnotationUsage | undefined, + componentV2Decorator: arkts.AnnotationUsage | undefined, + componentDecorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { - const hasComputed = member.scriptFunction.annotations?.find(annotation => + const computedDecorator = member.scriptFunction.annotations?.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.COMPUTED ); - if (hasComputed && !hasComponentV2 && !hasComponent) { + if (computedDecorator && !componentV2Decorator && !componentDecorator) { context.report({ - node: hasComputed, + node: computedDecorator, message: rule.messages.componentV2InStruct, fix: () => { const startPosition = node.startPosition; @@ -268,13 +268,13 @@ function reportComponentV2InStruct( }, }); } - if (hasComputed && !hasComponentV2 && hasComponent) { + if (computedDecorator && !componentV2Decorator && componentDecorator) { context.report({ - node: hasComputed, + node: computedDecorator, message: rule.messages.componentV2InStruct, fix: () => { - const startPosition = hasComponent.startPosition; - const endPosition = hasComponent.endPosition; + const startPosition = componentDecorator.startPosition; + const endPosition = componentDecorator.endPosition; return { range: [startPosition, endPosition], code: `@${PresetDecorators.COMPONENT_V2}`, @@ -287,17 +287,17 @@ function reportComponentV2InStruct( function validateComputedInClass( node: arkts.AstNode, member: arkts.MethodDefinition, - hasObservedV2: arkts.AnnotationUsage | undefined, + observedV2Decorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { - const hasComputed = member.scriptFunction.annotations?.find(annotation => + const computedDecorator = member.scriptFunction.annotations?.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.COMPUTED ); - if (hasComputed && !hasObservedV2 && + if (computedDecorator && !observedV2Decorator && arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET === member.kind) { context.report({ - node: hasComputed, + node: computedDecorator, message: rule.messages.onlyInObservedV2, fix: () => { const startPosition = node.startPosition; diff --git a/arkui-plugins/ui-syntax-plugins/rules/consumer-provider-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/consumer-provider-decorator-check.ts index d95a5104b..058c215e8 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/consumer-provider-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/consumer-provider-decorator-check.ts @@ -27,11 +27,11 @@ function processStructMembers( node.definition.body.forEach((member) => { // When a member variable is @consumer modified, it is stored to mark fields that cannot be initialized if (arkts.isClassProperty(member)) { - const hasComsumerDecorator = member?.annotations.some(annotation => + const comsumerDecorator = member?.annotations.some(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.CONSUMER ); - const hasProviderDecorator = member?.annotations.some(annotation => + const providerDecorator = member?.annotations.some(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.PROVIDER ); @@ -39,11 +39,11 @@ function processStructMembers( return; } const memberName = getIdentifierName(member?.key); - if (hasComsumerDecorator && structName && memberName) { + if (comsumerDecorator && structName && memberName) { componentv2WithConsumer.add(structName, memberName); } - if (hasProviderDecorator && structName && memberName) { + if (providerDecorator && structName && memberName) { componentv2WithProvider.add(structName, memberName); } } @@ -132,16 +132,16 @@ function validateMultipleBuiltInDecorators( context: UISyntaxRuleContext, ): void { - const hasDecorator = findDecorator(member, decorateName); + const decorator = findDecorator(member, decorateName); const otherDecorators = findOtherDecorator(member, decorateName); - if (!hasDecorator || !otherDecorators) { + if (!decorator || !otherDecorators) { return; } context.report({ - node: hasDecorator, + node: decorator, message: rule.messages.multipleBuiltInDecorators, data: { - decorator: getAnnotationName(hasDecorator) + decorator: getAnnotationName(decorator) }, fix: (decorator) => { const startPosition = otherDecorators.startPosition; @@ -154,67 +154,11 @@ function validateMultipleBuiltInDecorators( }); } -// Report a bug where @Provider is missing @ComponentV2 -function reportProviderRequiresComponentV2( - decorator: arkts.AnnotationUsage, - hasComponent: arkts.AnnotationUsage | undefined, - node: arkts.AstNode, - context: UISyntaxRuleContext, -): void { - if (hasComponent) { - context.report({ - node: decorator, - message: rule.messages.providerRequiresComponentV2, - data: { - decorator: getAnnotationName(decorator), - }, - fix: (hasProviderDecorator) => { - const startPosition = hasComponent.startPosition; - const endPosition = hasComponent.endPosition; - return { - range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V2}`, - }; - } - }); - } else { - context.report({ - node: decorator, - message: rule.messages.providerRequiresComponentV2, - data: { - decorator: getAnnotationName(decorator), - }, - fix: (hasProviderDecorator) => { - const startPosition = node.startPosition; - const endPosition = startPosition; - return { - range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V2}\n`, - }; - } - }); - } -} - -function validateDecoratorWithComponentV2Requirement( - member: arkts.ClassProperty, - hasComponentV2: arkts.AnnotationUsage | undefined, - hasComponent: arkts.AnnotationUsage | undefined, - node: arkts.AstNode, - context: UISyntaxRuleContext, - decoratorName: string -): void { - const decorator = findDecorator(member, decoratorName); - if (decorator && !hasComponentV2) { - reportProviderRequiresComponentV2(decorator, hasComponent, node, context); - } -} - // Verify decorator conflicts on member variables function validateMemberDecorators( member: arkts.ClassProperty, - hasComponentV2: arkts.AnnotationUsage | undefined, - hasComponent: arkts.AnnotationUsage | undefined, + componentV2Decorator: arkts.AnnotationUsage | undefined, + componentDecorator: arkts.AnnotationUsage | undefined, node: arkts.AstNode, context: UISyntaxRuleContext ): void { @@ -223,15 +167,6 @@ function validateMemberDecorators( // Check that the @Provider is mixed with other decorators validateMultipleBuiltInDecorators(member, PresetDecorators.PROVIDER, context); - - // Check if the @Consumer is in a @ComponentV2-modified structure - validateDecoratorWithComponentV2Requirement( - member, hasComponentV2, hasComponent, node, context, PresetDecorators.CONSUMER - ); - // Check if the @Provider is in a @ComponentV2-modified structure - validateDecoratorWithComponentV2Requirement( - member, hasComponentV2, hasComponent, node, context, PresetDecorators.PROVIDER - ); } // Verify that @Provider is being used in the class @@ -250,9 +185,9 @@ function validateDecoratorInClass( data: { decorator: getAnnotationName(decorator), }, - fix: (hasProviderDecorator) => { - const startPosition = hasProviderDecorator.startPosition; - const endPosition = hasProviderDecorator.endPosition; + fix: (providerDecorator) => { + const startPosition = providerDecorator.startPosition; + const endPosition = providerDecorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -262,15 +197,16 @@ function validateDecoratorInClass( } // Verify that the current identifier is an illegally initialized @Consumer member variable -function reportValidateConsumer( +function checkInvalidConsumerUsage( currentNode: arkts.Identifier, callExpName: string, componentv2WithConsumer: MultiMap, context: UISyntaxRuleContext ): void { - if (componentv2WithConsumer.get(callExpName).includes(getIdentifierName(currentNode))) { + const parent = currentNode.parent; + if (parent && componentv2WithConsumer.get(callExpName).includes(getIdentifierName(currentNode))) { context.report({ - node: currentNode.parent, + node: parent, message: rule.messages.forbiddenInitialization, data: { decorator: PresetDecorators.CONSUMER, @@ -278,8 +214,8 @@ function reportValidateConsumer( structName: callExpName }, fix: () => { - const startPosition = currentNode.parent.startPosition; - const endPosition = currentNode.parent.endPosition; + const startPosition = parent.startPosition; + const endPosition = parent.endPosition; return { range: [startPosition, endPosition], code: '', @@ -289,15 +225,16 @@ function reportValidateConsumer( } } -function reportValidateProvider( +function checkInvalidProviderUsage( currentNode: arkts.Identifier, callExpName: string, componentv2WithProvider: MultiMap, context: UISyntaxRuleContext ): void { - if (componentv2WithProvider.get(callExpName).includes(getIdentifierName(currentNode))) { + const parent = currentNode.parent; + if (parent && componentv2WithProvider.get(callExpName)?.includes(getIdentifierName(currentNode))) { context.report({ - node: currentNode.parent, + node: parent, message: rule.messages.forbiddenInitialization, data: { decorator: PresetDecorators.PROVIDER, @@ -305,8 +242,8 @@ function reportValidateProvider( structName: callExpName }, fix: () => { - const startPosition = currentNode.parent.startPosition; - const endPosition = currentNode.parent.endPosition; + const startPosition = parent.startPosition; + const endPosition = parent.endPosition; return { range: [startPosition, endPosition], code: '', @@ -328,7 +265,7 @@ function validateConsumerInitialization(node: arkts.CallExpression, componentv2W while (queue.length > 0) { const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; if (arkts.isIdentifier(currentNode)) { - reportValidateConsumer(currentNode, callExpName, componentv2WithConsumer, context); + checkInvalidConsumerUsage(currentNode, callExpName, componentv2WithConsumer, context); } const children = currentNode.getChildren(); for (const child of children) { @@ -349,7 +286,7 @@ function validateProviderInitialization(node: arkts.CallExpression, componentv2W while (queue.length > 0) { const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; if (arkts.isIdentifier(currentNode)) { - reportValidateProvider(currentNode, callExpName, componentv2WithProvider, context); + checkInvalidProviderUsage(currentNode, callExpName, componentv2WithProvider, context); } const children = currentNode.getChildren(); for (const child of children) { @@ -382,14 +319,14 @@ function collectStructsWithConsumerAndProvider( function validateStructDecoratorsAndMembers(node: arkts.AstNode, context: UISyntaxRuleContext): void { if (arkts.isStructDeclaration(node)) { - const hasComponentV2 = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - const hasComponent = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); + const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + const componentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); node.definition.body.forEach(member => { if (arkts.isMethodDefinition(member)) { validateDecoratorOnMethod(member, context); } if (arkts.isClassProperty(member)) { - validateMemberDecorators(member, hasComponentV2, hasComponent, node, context); + validateMemberDecorators(member, componentV2Decorator, componentDecorator, node, context); } }); } @@ -411,7 +348,6 @@ const rule: UISyntaxRule = { messages: { consumerOnlyOnMember: `'@{{decorator}}' can only decorate member property.`, multipleBuiltInDecorators: `The struct member variable can not be decorated by multiple built-in decorators.`, - providerRequiresComponentV2: `The '@{{decorator}}' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`, providerOnlyInStruct: `The '@{{decorator}}' decorator can only be used with 'struct'.`, forbiddenInitialization: `The '@{{decorator}}' property '{{value}}' in the custom component '{{structName}}' cannot be initialized here (forbidden to specify).`, }, @@ -433,4 +369,4 @@ const rule: UISyntaxRule = { }, }; -export default rule; \ No newline at end of file +export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/custom-dialog-missing-controller.ts b/arkui-plugins/ui-syntax-plugins/rules/custom-dialog-missing-controller.ts index f7721812c..f175c218a 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/custom-dialog-missing-controller.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/custom-dialog-missing-controller.ts @@ -14,17 +14,45 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getClassPropertyType, PresetDecorators, getAnnotationUsage } from '../utils'; +import { getClassPropertyType, PresetDecorators, getAnnotationUsage, isClassPropertyOptional } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; const CUSTOM_DIALOG_CONTROLLER: string = 'CustomDialogController'; +function hasCustomDialogControllerInUnion( + property: arkts.ClassProperty, + CUSTOM_DIALOG_CONTROLLER: string +): boolean { + if (!isClassPropertyOptional(property)) { + return false; + } + if (!property.typeAnnotation || !arkts.isETSUnionType(property.typeAnnotation)) { + return false; + } + for (const type of property.typeAnnotation.types) { + if (!arkts.isETSTypeReference(type)) { + continue; + } + + const part = type.part; + if (!part || !arkts.isETSTypeReferencePart(part)) { + continue; + } + + const name = part.name; + if (name && arkts.isIdentifier(name) && name.name === CUSTOM_DIALOG_CONTROLLER) { + return true; + } + } + return false; +} + function missingController( node: arkts.StructDeclaration, context: UISyntaxRuleContext ): void { // Check for the @CustomDialog decorator - const hasCustomDialogDecorator = getAnnotationUsage(node, PresetDecorators.CUSTOM_DIALOG); + const customDialogDecorator = getAnnotationUsage(node, PresetDecorators.CUSTOM_DIALOG); const structName = node.definition.ident; if (!structName) { return; @@ -33,13 +61,14 @@ function missingController( let hasControllerProperty = false; node.definition.body.forEach((property) => { if (arkts.isClassProperty(property)) { + hasControllerProperty = hasCustomDialogControllerInUnion(property, CUSTOM_DIALOG_CONTROLLER); const propertyType = getClassPropertyType(property); if (propertyType === CUSTOM_DIALOG_CONTROLLER) { hasControllerProperty = true; } } }); - if (!hasControllerProperty && hasCustomDialogDecorator) { + if (!hasControllerProperty && customDialogDecorator) { context.report({ node: structName, message: rule.messages.missingController, diff --git a/arkui-plugins/ui-syntax-plugins/rules/decorators-in-ui-component-only.ts b/arkui-plugins/ui-syntax-plugins/rules/decorators-in-ui-component-only.ts deleted file mode 100644 index fe7fb6ae0..000000000 --- a/arkui-plugins/ui-syntax-plugins/rules/decorators-in-ui-component-only.ts +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as arkts from '@koalaui/libarkts'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; -import { PresetDecorators } from '../utils/index'; - -// Constants for allowed decorators on struct and within struct using PresetDecorators -const DECORATORS_ALLOWED_ON_STRUCT = [ - PresetDecorators.COMPONENT_V1, - PresetDecorators.ENTRY, - PresetDecorators.PREVIEW, - PresetDecorators.CUSTOM_DIALOG, - PresetDecorators.REUSABLE_V2, -]; - -const DECORATORS_ALLOWED_IN_STRUCT = [ - PresetDecorators.STATE, - PresetDecorators.PROP, - PresetDecorators.LINK, - PresetDecorators.PROVIDE, - PresetDecorators.CONSUME, - PresetDecorators.OBJECT_LINK, - PresetDecorators.STORAGE_LINK, - PresetDecorators.STORAGE_PROP, - PresetDecorators.LOCAL_STORAGE_LINK, - PresetDecorators.LOCAL_STORAGE_PROP, - PresetDecorators.WATCH, - PresetDecorators.BUILDER_PARAM, -]; - -// Helper function to find the decorator in a ClassDeclaration and report errors. -function findInvalidDecorator(context: UISyntaxRuleContext, node: arkts.ClassDeclaration): void { - node.definition!.annotations?.forEach(annotation => { - if (annotation.expr && arkts.isIdentifier(annotation.expr) && - DECORATORS_ALLOWED_ON_STRUCT.includes(annotation.expr.name)) { - reportDecoratorError(context, annotation, rule.messages.invalidDecoratorOnStruct); - } - }); -} - -// Rule 2: Check for 'decorator' on MethodDefinition -function checkinvalidDecoratorInStruct(context: UISyntaxRuleContext, node: arkts.MethodDefinition): void { - node.scriptFunction.annotations?.forEach(annotation => { - if (annotation.expr && arkts.isIdentifier(annotation.expr) && - DECORATORS_ALLOWED_IN_STRUCT.includes(annotation.expr.name)) { - reportDecoratorError(context, annotation, rule.messages.invalidDecoratorInStruct); - } - }); -}; - -// Rule 3: Check for 'decorator' on ClassProperty within a ClassDeclaration -function checkDecoratorOnClassProperty(context: UISyntaxRuleContext, node: arkts.ClassProperty, - currentNode: arkts.AstNode): void { - node.annotations?.forEach(annotation => { - if (annotation.expr && arkts.isIdentifier(annotation.expr) && - DECORATORS_ALLOWED_IN_STRUCT.includes(annotation.expr.name)) { - reportIfDecoratorInClassDeclaration(context, annotation, currentNode); - } - }); -}; - -function reportIfDecoratorInClassDeclaration(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage, - currentNode: arkts.AstNode): void { - while (arkts.nodeType(currentNode) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - currentNode = currentNode.parent; - if (annotation && arkts.isClassDeclaration(currentNode)) { - reportDecoratorError(context, annotation, rule.messages.invalidDecoratorInStruct); - } - } -} - -function reportDecoratorError(context: UISyntaxRuleContext, decorator: arkts.AnnotationUsage, message: string -): void { - if (!decorator.expr || !arkts.isIdentifier(decorator.expr)) { - return; - } - const decoratorName = decorator.expr.name; - context.report({ - node: decorator, - message: message, - data: { decoratorName }, - fix: () => { - const startPosition = decorator.startPosition; - const endPosition = decorator.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); -} - -function checkDecoratorWithFunctionDeclaration(node: arkts.FunctionDeclaration, context: UISyntaxRuleContext): void { - node.annotations?.forEach(annotation => { - if (annotation.expr && arkts.isIdentifier(annotation.expr) && - DECORATORS_ALLOWED_ON_STRUCT.includes(annotation.expr.name)) { - reportDecoratorError(context, annotation, rule.messages.invalidDecoratorOnStruct); - } - if (annotation.expr && arkts.isIdentifier(annotation.expr) && - DECORATORS_ALLOWED_IN_STRUCT.includes(annotation.expr.name)) { - reportDecoratorError(context, annotation, rule.messages.invalidDecoratorInStruct); - } - }); -} - -const rule: UISyntaxRule = { - name: 'decorators-in-ui-component-only', - messages: { - invalidDecoratorOnStruct: `The '@{{decoratorName}}' decorator can only be used with 'struct'.`, - invalidDecoratorInStruct: `'@{{decoratorName}}' can not decorate the method.`, - }, - setup(context) { - return { - parsed: (node: arkts.AstNode): void => { - if (arkts.isFunctionDeclaration(node)) { - checkDecoratorWithFunctionDeclaration(node, context); - } - if (arkts.isClassDeclaration(node)) { - findInvalidDecorator(context, node); - } - if (arkts.isMethodDefinition(node)) { - checkinvalidDecoratorInStruct(context, node); - } - let currentNode = node; - if (arkts.isClassProperty(node)) { - checkDecoratorOnClassProperty(context, node, currentNode); - } - }, - }; - }, -}; - -export default rule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/index.ts b/arkui-plugins/ui-syntax-plugins/rules/index.ts index 827a4c2cf..92b50232d 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/index.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/index.ts @@ -26,12 +26,11 @@ import ConstructParameter from './construct-parameter'; import ConsumerProviderDecoratorCheck from './consumer-provider-decorator-check'; import ComponentV2StateUsageValidation from './componentV2-state-usage-validation'; import CustomDialogMissingController from './custom-dialog-missing-controller'; -import DecoratorsInUIComponentOnly from './decorators-in-ui-component-only'; import EntryLoacalStorageCheck from './entry-localstorage-check'; import EntryStructNoExport from './entry-struct-no-export'; -import LocalBuilderCheck from './local-builder-check'; import MonitorDecoratorCheck from './monitor-decorator-check'; import NestedRelationship from './nested-relationship'; +import NestedReuseComponentCheck from './nested-reuse-component-check'; import NoChildInButton from './no-child-in-button'; import NoDuplicateDecorators from './no-duplicate-decorators'; import NoDuplicateEntry from './no-duplicate-entry'; @@ -48,8 +47,6 @@ import StructVariableInitialization from './struct-variable-initialization'; import TrackDecoratorCheck from './track-decorator-check'; import TypeDecoratorCheck from './type-decorator-check'; import ValidateBuildInStruct from './validate-build-in-struct'; -import ValidateDecoratorTarget from './validate-decorator-target'; -import VariableInitializationViaComponentCons from './variable-initialization-via-component-cons'; import WatchDecoratorFunction from './watch-decorator-function'; import WatchDecoratorRegular from './watch-decorator-regular'; import WrapBuilderCheck from './wrap-builder-check'; @@ -81,12 +78,11 @@ const rules: Array = [ ConsumerProviderDecoratorCheck, ComponentV2StateUsageValidation, CustomDialogMissingController, - DecoratorsInUIComponentOnly, EntryLoacalStorageCheck, EntryStructNoExport, - LocalBuilderCheck, MonitorDecoratorCheck, NestedRelationship, + NestedReuseComponentCheck, NoChildInButton, NoDuplicateDecorators, NoDuplicateEntry, @@ -103,8 +99,6 @@ const rules: Array = [ TrackDecoratorCheck, TypeDecoratorCheck, ValidateBuildInStruct, - ValidateDecoratorTarget, - VariableInitializationViaComponentCons, WatchDecoratorFunction, WatchDecoratorRegular, WrapBuilderCheck, diff --git a/arkui-plugins/ui-syntax-plugins/rules/local-builder-check.ts b/arkui-plugins/ui-syntax-plugins/rules/local-builder-check.ts deleted file mode 100644 index cbfeef0c3..000000000 --- a/arkui-plugins/ui-syntax-plugins/rules/local-builder-check.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as arkts from '@koalaui/libarkts'; -import { PresetDecorators } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; - -function checkLocalBuilder(node: arkts.ClassDeclaration, context: UISyntaxRuleContext): void { - node.definition?.body.forEach(body => { - if (!arkts.isMethodDefinition(body)) { - return; - } - const localBuilder = body.scriptFunction?.annotations?.find( - annotation => annotation.expr && - annotation.expr.dumpSrc() === PresetDecorators.LOCAL_BUILDER); - if (!localBuilder) { - return; - } - context.report({ - node: localBuilder, - message: rule.messages.invalidUsage, - fix: (localBuilder) => { - const startPosition = arkts.getStartPosition(localBuilder); - const endPosition = arkts.getEndPosition(localBuilder); - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); - }); -} - -const rule: UISyntaxRule = { - name: 'local-builder-check', - messages: { - invalidUsage: `The '@LocalBuilder' decorator can only be used in 'struct'.`, - }, - setup(context) { - return { - parsed: (node): void => { - if (!arkts.isClassDeclaration(node)) { - return; - } - checkLocalBuilder(node, context); - }, - }; - }, -}; - -export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/nested-reuse-component-check.ts b/arkui-plugins/ui-syntax-plugins/rules/nested-reuse-component-check.ts index 90d19982b..a734b040e 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/nested-reuse-component-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/nested-reuse-component-check.ts @@ -111,6 +111,9 @@ function checkNoReusableV1InReusableV2( // Traverse upwards to find the custom component. let struceNode: arkts.AstNode = node; while (!arkts.isStructDeclaration(struceNode)) { + if (!struceNode.parent) { + return; + } struceNode = struceNode.parent; } const annotationsList = struceNode.definition.annotations; @@ -160,6 +163,9 @@ function checkNestedReuseComponent( let struceNode: arkts.AstNode = node; let hasReportedError = false; while (!arkts.isStructDeclaration(struceNode)) { + if (!struceNode.parent) { + return; + } struceNode = struceNode.parent; if (!hasReportedError) { hasReportedError = checkNoReusableV2InRepeatTemplate(struceNode, node, context); diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-child-in-button.ts b/arkui-plugins/ui-syntax-plugins/rules/no-child-in-button.ts index e6a93e3d7..9dab0b46e 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-child-in-button.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-child-in-button.ts @@ -18,6 +18,9 @@ import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; import { getIdentifierName } from '../utils'; function isInsideStructAndBuild(node: arkts.AstNode): boolean { + if (!node.parent) { + return false; + } let parentNode = node.parent; let isInStruct = false; let isInBuild = false; @@ -28,6 +31,9 @@ function isInsideStructAndBuild(node: arkts.AstNode): boolean { if (arkts.isScriptFunction(parentNode) && parentNode.id?.name === 'build') { isInBuild = true; } + if (!parentNode.parent) { + return false; + } parentNode = parentNode.parent; } return isInStruct && isInBuild; @@ -74,6 +80,9 @@ const rule: UISyntaxRule = { if (!isInsideStructAndBuild(node)) { return; } + if (!node.parent) { + return; + } // Obtain the information of the parent node of the current node let parentNode = node.parent; if (!arkts.isCallExpression(parentNode)) { diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-entry.ts b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-entry.ts index 5dc533729..8cdf3d2ce 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-entry.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-entry.ts @@ -38,27 +38,25 @@ function checkDuplicateEntry( return; } if (entryData.count === MAX_ENTRY_DECORATOR_COUNT) { + const entryDocoratorUsage = entryDecoratorUsages.at(0)!; context.report({ - node: entryDecoratorUsages.at(0)!, + node: entryDocoratorUsage, message: rule.messages.duplicateEntry, - fix: (entryDocoratorUsage) => { - const startPosition = entryDocoratorUsage.startPosition; - const endPosition = entryDocoratorUsage.endPosition; + fix: () => { return { - range: [startPosition, endPosition], + range: [entryDocoratorUsage.startPosition, entryDocoratorUsage.endPosition], code: '', }; } }); } + const entryDocoratorUsage = entryDecoratorUsages.at(entryData.count)!; context.report({ - node: entryDecoratorUsages.at(entryData.count)!, + node: entryDocoratorUsage, message: rule.messages.duplicateEntry, - fix: (entryDocoratorUsage) => { - const startPosition = entryDocoratorUsage.startPosition; - const endPosition = entryDocoratorUsage.endPosition; + fix: () => { return { - range: [startPosition, endPosition], + range: [entryDocoratorUsage.startPosition, entryDocoratorUsage.endPosition], code: '', }; } diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-id.ts b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-id.ts index bca4ccdf6..de6b12991 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-id.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-id.ts @@ -86,7 +86,7 @@ function getPath(): string | undefined { const contextPtr = arkts.arktsGlobal.compilerContext?.peer; if (!!contextPtr) { let program = arkts.getOrUpdateGlobalContext(contextPtr).program; - return program.programGlobalAbsName; + return program.globalAbsName; } return undefined; } diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-preview.ts b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-preview.ts index 544c8155a..ed5149173 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-preview.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-preview.ts @@ -21,11 +21,9 @@ function reportError(context: UISyntaxRuleContext, errorNode: arkts.AnnotationUs context.report({ node: errorNode, message: rule.messages.duplicateEntry, - fix: (previewDecoratorUsage) => { - const startPosition = previewDecoratorUsage.startPosition; - const endPosition = previewDecoratorUsage.endPosition; + fix: () => { return { - range: [startPosition, endPosition], + range: [errorNode.startPosition, errorNode.endPosition], code: '', }; } diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-state-manager.ts b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-state-manager.ts index 725bcbba5..f32635b3d 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-state-manager.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-state-manager.ts @@ -62,7 +62,7 @@ function duplicateState( const rule: UISyntaxRule = { name: 'no-duplicate-state-manager', messages: { - duplicateState: `This property '{{attributeName}}' cannot have mutilate state management decorators.`, + duplicateState: `The property '{{attributeName}}' cannot have multiple state management decorators.`, }, setup(context) { return { @@ -70,8 +70,8 @@ const rule: UISyntaxRule = { if (!arkts.isStructDeclaration(node)) { return; } - const hasComponentV2 = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - if (hasComponentV2) { + const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + if (componentV2Decorator) { return; } duplicateState(node, context); diff --git a/arkui-plugins/ui-syntax-plugins/rules/observedV2-trace-usage-validation.ts b/arkui-plugins/ui-syntax-plugins/rules/observedV2-trace-usage-validation.ts index 3cba69c81..3d664040f 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/observedV2-trace-usage-validation.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/observedV2-trace-usage-validation.ts @@ -83,11 +83,11 @@ function tracePerportyRule( reportTraceDecoratorError(context, traceDecorator); } else if (arkts.isClassDeclaration(currentNode) && currentNode.definition) { const observedDecorator = getObservedDecorator(currentNode); - const observedV2 = currentNode.definition.annotations.some(annotation => { + const observedV2 = currentNode.definition.annotations.some(annotation => annotation.expr && - arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.OBSERVED_V2; - }); + arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.OBSERVED_V2 + ); if (!observedV2 && !observedDecorator) { reportTraceMustUsedWithObservedV2(context, traceDecorator, currentNode); } else if (!observedV2 && observedDecorator) { @@ -153,16 +153,7 @@ function validateTraceDecoratorUsage(node: arkts.AstNode, context: UISyntaxRuleC } } if (arkts.isClassProperty(node)) { - const traceDecorator = node.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.TRACE); - if (traceDecorator) { - // Iterate up the parent node to check whether it is a class or a custom component - while (!arkts.isStructDeclaration(currentNode) && !arkts.isClassDeclaration(currentNode)) { - currentNode = currentNode.parent; - } - // The '@Trace' decorator can only be used in 'class' - tracePerportyRule(context, currentNode, traceDecorator); - } + checkTraceDecoratorUsageInClassProperty(context, node, currentNode); } if (arkts.isMethodDefinition(node)) { // Check that @Trace is in the correct location @@ -174,4 +165,23 @@ function validateTraceDecoratorUsage(node: arkts.AstNode, context: UISyntaxRuleC } } +function checkTraceDecoratorUsageInClassProperty( + context: UISyntaxRuleContext, + node: arkts.ClassProperty, + currentNode: arkts.AstNode,): void { + const traceDecorator = node.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.TRACE); + if (traceDecorator) { + // Iterate up the parent node to check whether it is a class or a custom component + while (!arkts.isStructDeclaration(currentNode) && !arkts.isClassDeclaration(currentNode)) { + if (!currentNode.parent) { + return; + } + currentNode = currentNode.parent; + } + // The '@Trace' decorator can only be used in 'class' + tracePerportyRule(context, currentNode, traceDecorator); + } +} + export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/once-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/once-decorator-check.ts index 6d5970ecb..db056e55f 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/once-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/once-decorator-check.ts @@ -14,7 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getAnnotationUsage, getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; +import { getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; function findDecorator(member: arkts.ClassProperty, decorator: string): arkts.AnnotationUsage | undefined { @@ -114,66 +114,15 @@ function validateMethodAnnotations(body: arkts.MethodDefinition, context: UISynt } } - -function reportInvalidOnceUsage( - onceDecorator: arkts.AnnotationUsage | undefined, - node: arkts.StructDeclaration, - componentDocoratorUsage: arkts.AnnotationUsage | undefined, - context: UISyntaxRuleContext, -): void { - if (!onceDecorator) { - return; - } - context.report({ - node: onceDecorator, - message: rule.messages.invalidUsage, - fix: () => { - if (componentDocoratorUsage) { - const startPosition = componentDocoratorUsage.startPosition; - const endPosition = componentDocoratorUsage.endPosition; - return { - range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V2}` - }; - } else { - const startPosition = node.startPosition; - const endPosition = node.startPosition; - return { - range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V2}\n` - }; - } - } - }); -} - -function invalidComponentUsage( - node: arkts.StructDeclaration, - body: arkts.ClassProperty, - onceDecorator: arkts.AnnotationUsage | undefined, - componentV2DocoratorUsage: arkts.AnnotationUsage | undefined, - componentDocoratorUsage: arkts.AnnotationUsage | undefined, - context: UISyntaxRuleContext -): void { - onceDecorator = findDecorator(body, PresetDecorators.ONCE); - if (onceDecorator && !componentV2DocoratorUsage) { - reportInvalidOnceUsage(onceDecorator, node, componentDocoratorUsage, context); - } -} - function validateDecorater( node: arkts.StructDeclaration, onceDecorator: arkts.AnnotationUsage | undefined, - componentV2DocoratorUsage: arkts.AnnotationUsage | undefined, - componentDocoratorUsage: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext, ): void { node.definition?.body.forEach(body => { // Check if @Once is used on a property and if @Param is used with if (arkts.isClassProperty(body)) { validatePropertyAnnotations(body, context, onceDecorator); - // If @Once is used but not in a @ComponentV2 struct, report an error - invalidComponentUsage(node, body, onceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context); } if (!arkts.isMethodDefinition(body)) { return; @@ -214,7 +163,6 @@ function checkClassForInvalidDecorator( const rule: UISyntaxRule = { name: 'once-decorator-check', messages: { - invalidUsage: `The '@Once' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`, invalidMemberDecorate: `@Once can only decorate member property.`, invalidDecorator: `When a variable decorated with '@Once', it must also be decorated with '@Param'.`, invalidNOtInStruct: `'@Once' decorator can only be used with 'struct'.` @@ -228,10 +176,7 @@ const rule: UISyntaxRule = { checkClassForInvalidDecorator(node, onceDecorator, context); } if (arkts.isStructDeclaration(node)) { - // Check if the struct is decorated with @ComponentV2 - const componentV2DocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - const componentDocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); - validateDecorater(node, onceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context); + validateDecorater(node, onceDecorator, context); } }, }; diff --git a/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts b/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts index 8ca843c94..5db0f6d76 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts @@ -41,12 +41,6 @@ function reportNoReusableV1InComponentV2(node: arkts.AstNode, context: UISyntaxR context.report({ node: node, message: rule.messages.noReusableV1InComponentV2, - fix: (node) => { - return { - range: [node.startPosition, node.endPosition], - code: '', - }; - } }); } @@ -62,6 +56,9 @@ function checkNoReusableV1InComponentV2( // Traverse upwards to find the custom component. let struceNode: arkts.AstNode = node; while (!arkts.isStructDeclaration(struceNode)) { + if (!struceNode.parent) { + return; + } struceNode = struceNode.parent; } const annotationsList = struceNode.definition.annotations; diff --git a/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts b/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts index 9cc5dc5ab..d614ba776 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts @@ -25,9 +25,9 @@ function findStructsWithReusableAndComponentV2(node: arkts.AstNode, reusableV2Co continue; } // Check that the current component has @ComponentV2 and @ReusableV2 decorators - const hasReusableV2Decorator = getAnnotationUsage(childNode, PresetDecorators.REUSABLE_V2); - const hasComponentV2Decorator = getAnnotationUsage(childNode, PresetDecorators.COMPONENT_V2); - if (hasReusableV2Decorator && hasComponentV2Decorator) { + const reusableV2Decorator = getAnnotationUsage(childNode, PresetDecorators.REUSABLE_V2); + const componentV2Decorator = getAnnotationUsage(childNode, PresetDecorators.COMPONENT_V2); + if (reusableV2Decorator && componentV2Decorator) { const struceName = childNode.definition?.ident?.name ?? ''; reusableV2ComponentV2Struct.push(struceName); } diff --git a/arkui-plugins/ui-syntax-plugins/rules/track-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/track-decorator-check.ts index b2a2b1ce0..0102159b6 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/track-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/track-decorator-check.ts @@ -43,18 +43,18 @@ function checkInvalidTrackAnnotations(context: UISyntaxRuleContext, node: arkts. node.definition.body.forEach((member) => { // Check whether it is a member variable if (arkts.isClassProperty(member)) { - const hasTrackDecorator = findClassPropertyAnnotation(member, PresetDecorators.TRACK); + const trackDecorator = findClassPropertyAnnotation(member, PresetDecorators.TRACK); // If a member variable is decorated with @Track, an error is reported immediately - if (hasTrackDecorator) { - reportInvalidTarget(context, hasTrackDecorator); + if (trackDecorator) { + reportInvalidTarget(context, trackDecorator); } } // Check whether this is the method if (arkts.isMethodDefinition(member)) { - const hasTrackDecorator = getMethodAnnotation(member, PresetDecorators.TRACK); + const trackDecorator = getMethodAnnotation(member, PresetDecorators.TRACK); // If the method is decorated with @Track, an error is reported immediately - if (hasTrackDecorator) { - reportInvalidTarget(context, hasTrackDecorator); + if (trackDecorator) { + reportInvalidTarget(context, trackDecorator); } } },); @@ -62,7 +62,7 @@ function checkInvalidTrackAnnotations(context: UISyntaxRuleContext, node: arkts. function checkTrackUsedWithObservedV2(context: UISyntaxRuleContext, node: arkts.ClassDeclaration): void { // Check if the class is decorated with @Observed - const hasObservedV2Decorator = node.definition?.annotations?.find( + const observedV2Decorator = node.definition?.annotations?.find( annotations => annotations.expr && arkts.isIdentifier(annotations.expr) && @@ -72,30 +72,30 @@ function checkTrackUsedWithObservedV2(context: UISyntaxRuleContext, node: arkts. node.definition?.body.forEach((member) => { // Check whether it is a class attribute if (arkts.isClassProperty(member)) { - const hasTrackDecorator = findClassPropertyAnnotation(member, PresetDecorators.TRACK); + const trackDecorator = findClassPropertyAnnotation(member, PresetDecorators.TRACK); // If the class is not decorated with @Observed and has decorators, an error is reported - if (hasObservedV2Decorator && !(node.definition?.annotations.length === 0) && hasTrackDecorator) { - reportInvalidClass(context, hasTrackDecorator); + if (observedV2Decorator && !(node.definition?.annotations.length === 0) && trackDecorator) { + reportInvalidClass(context, trackDecorator); } } // Check whether this is the method if (arkts.isMethodDefinition(member)) { - const hasTrackDecorator = getMethodAnnotation(member, PresetDecorators.TRACK); + const trackDecorator = getMethodAnnotation(member, PresetDecorators.TRACK); // If the method is decorated with @Track, an error is reported immediately - if (hasTrackDecorator) { - reportInvalidTarget(context, hasTrackDecorator); + if (trackDecorator) { + reportInvalidTarget(context, trackDecorator); } } }); } -function reportInvalidClass(context: UISyntaxRuleContext, hasTrackDecorator: arkts.AnnotationUsage): void { +function reportInvalidClass(context: UISyntaxRuleContext, trackDecorator: arkts.AnnotationUsage): void { context.report({ - node: hasTrackDecorator, + node: trackDecorator, message: rule.messages.trackMustUsedWithObserved, - fix: (hasTrackDecorator) => { - const startPosition = hasTrackDecorator.startPosition; - const endPosition = hasTrackDecorator.endPosition; + fix: (trackDecorator) => { + const startPosition = trackDecorator.startPosition; + const endPosition = trackDecorator.endPosition; return { range: [startPosition, endPosition], code: '', diff --git a/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-cons.ts b/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-cons.ts deleted file mode 100644 index 7660e6624..000000000 --- a/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-cons.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as arkts from '@koalaui/libarkts'; -import { getIdentifierName, getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; - -// Define a function to add property data to the property map -function addProperty(propertyMap: Map>, structName: string, - propertyName: string, annotationName: string): void { - if (!propertyMap.has(structName)) { - propertyMap.set(structName, new Map()); - } - const structProperties = propertyMap.get(structName); - if (structProperties) { - structProperties.set(propertyName, annotationName); - } -} -// categorizePropertyBasedOnAnnotations -function checkPropertyByAnnotations( - item: arkts.AstNode, - structName: string, - mustInitMap: Map>, - cannotInitMap: Map> = new Map(), - mustInitArray: string[][], - cannotInitArray: string[][] -): void { - if (!arkts.isClassProperty(item)) { - return; - } - const propertyName: string = item.key?.dumpSrc() ?? ''; - if (item.annotations.length === 0 || propertyName === '') { - return; - } - const annotationArray: string[] = getClassPropertyAnnotationNames(item); - // If the member variable is decorated, it is added to the corresponding map - mustInitArray.forEach(arr => { - if (arr.every(annotation => annotationArray.includes(annotation))) { - const annotationName: string = arr[0]; - addProperty(mustInitMap, structName, propertyName, annotationName); - } - }); - cannotInitArray.forEach(arr => { - if (arr.every(annotation => annotationArray.includes(annotation))) { - const annotationName: string = arr[0]; - addProperty(cannotInitMap, structName, propertyName, annotationName); - } - }); -} - -function initMap( - node: arkts.AstNode, - mustInitMap: Map>, - cannotInitMap: Map> = new Map(), - mustInitArray: string[][], - cannotInitArray: string[][] -): void { - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; - } - node.getChildren().forEach((member) => { - if (!arkts.isStructDeclaration(member)) { - return; - } - const structName: string = member.definition.ident?.name ?? ''; - if (structName === '') { - return; - } - member.definition?.body.forEach((item) => { - checkPropertyByAnnotations(item, structName, mustInitMap, cannotInitMap, mustInitArray, cannotInitArray); - }); - }); -} - -function getChildKeyNameArray(member: arkts.AstNode): string[] { - const childkeyNameArray: string[] = []; - member.getChildren().forEach((property) => { - if (arkts.isProperty(property)) { - const childkeyName = property.key?.dumpSrc() ?? ''; - if (childkeyName !== '') { - childkeyNameArray.push(childkeyName); - } - } - }); - return childkeyNameArray; -} - -function checkMustInitialize( - node: arkts.AstNode, - context: UISyntaxRuleContext, - mustInitMap: Map> -): void { - if (!arkts.isIdentifier(node)) { - return; - } - const structName: string = getIdentifierName(node); - if (!mustInitMap.has(structName)) { - return; - } - const parentNode: arkts.AstNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; - } - // Get all the properties of a record via StructName - const mustInitName: Map = mustInitMap.get(structName)!; - parentNode.arguments?.forEach((member) => { - const childkeyNameArray: string[] = getChildKeyNameArray(member); - mustInitName.forEach((value, key) => { - // If an attribute that must be initialized is not initialized, an error is reported - if (!childkeyNameArray.includes(key)) { - context.report({ - node: parentNode, - message: rule.messages.mustInitializeRule, - data: { - annotationName: value, - propertyName: key, - }, - }); - } - }); - }); -} - -function checkCannotInitialize( - node: arkts.AstNode, - context: UISyntaxRuleContext, - cannotInitMap: Map> -): void { - if (!arkts.isIdentifier(node)) { - return; - } - const structName: string = getIdentifierName(node); - if (!cannotInitMap.has(structName)) { - return; - } - const parentNode: arkts.AstNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; - } - // Get all the properties of a record via StructName - const cannotInitName: Map = cannotInitMap.get(structName)!; - parentNode.arguments.forEach((member) => { - member.getChildren().forEach((property) => { - if (!arkts.isProperty(property)) { - return; - } - if (!property.key) { - return; - } - const propertyName = property.key.dumpSrc(); - // If a property that cannot be initialized is initialized, an error is reported - if (cannotInitName.has(propertyName)) { - context.report({ - node: property.key, - message: rule.messages.cannotInitializeRule, - data: { - annotationName: cannotInitName.get(propertyName)!, - propertyName: propertyName, - }, - }); - } - }); - }); -} - -const rule: UISyntaxRule = { - name: 'variable-initialization-via-component-cons', - messages: { - mustInitializeRule: `'@{{annotationName}}' decorated '{{propertyName}}' must be initialized through the component constructor.`, - cannotInitializeRule: `'@{{annotationName}}' decorated '{{propertyName}}' cannot be initialized through the component constructor.`, - }, - setup(context) { - let mustInitMap: Map> = new Map(); - let cannotInitMap: Map> = new Map(); - const mustInitArray: string[][] = [ - [PresetDecorators.REQUIRE, PresetDecorators.PROP], - [PresetDecorators.REQUIRE, PresetDecorators.BUILDER_PARAM], - [PresetDecorators.LINK], - [PresetDecorators.OBJECT_LINK] - ]; - const cannotInitArray: string[][] = [ - [PresetDecorators.STORAGE_LINK], - [PresetDecorators.STORAGE_PROP], - [PresetDecorators.CONSUME], - [PresetDecorators.LOCAL_STORAGE_LINK], - [PresetDecorators.LOCAL_STORAGE_PROP] - ]; - return { - parsed: (node): void => { - initMap(node, mustInitMap, cannotInitMap, mustInitArray, cannotInitArray); - checkMustInitialize(node, context, mustInitMap); - checkCannotInitialize(node, context, cannotInitMap); - }, - }; - }, -}; - -export default rule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-constructor.ts b/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-constructor.ts index b33b1ce8e..e46684e51 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-constructor.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/variable-initialization-via-component-constructor.ts @@ -22,13 +22,8 @@ const mustInitInConstructorDecorators: string[][] = [ [PresetDecorators.REQUIRE, PresetDecorators.STATE], [PresetDecorators.REQUIRE, PresetDecorators.PROVIDE], [PresetDecorators.REQUIRE, PresetDecorators.PROP], - [PresetDecorators.REQUIRE, PresetDecorators.BUILDER_PARAM] -]; -const shouldInitInConstructorDecorators: string[][] = [ - [PresetDecorators.PROP], - [PresetDecorators.BUILDER_PARAM], - [PresetDecorators.LINK], - [PresetDecorators.OBJECT_LINK] + [PresetDecorators.REQUIRE, PresetDecorators.BUILDER_PARAM], + [PresetDecorators.REQUIRE, PresetDecorators.PARAM] ]; const notAllowInitInConstructorDecorators: string[][] = [ [PresetDecorators.STORAGE_LINK], @@ -54,7 +49,6 @@ function checkPropertyByAnnotations( item: arkts.AstNode, structName: string, mustInitMap: Map>, - shouldInitMap: Map>, cannotInitMap: Map> ): void { if (!arkts.isClassProperty(item) || !item.key || !arkts.isIdentifier(item.key)) { @@ -78,19 +72,11 @@ function checkPropertyByAnnotations( addProperty(cannotInitMap, structName, propertyName, annotationName); } }); - shouldInitInConstructorDecorators.forEach(arr => { - if (arr.every(annotation => annotationArray.includes(annotation)) && - !annotationArray.includes(PresetDecorators.REQUIRE)) { - const annotationName: string = arr[0]; - addProperty(shouldInitMap, structName, propertyName, annotationName); - } - }); } function initMap( node: arkts.AstNode, mustInitMap: Map>, - shouldInitMap: Map>, cannotInitMap: Map> ): void { if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { @@ -108,7 +94,7 @@ function initMap( return; } member.definition?.body.forEach((item) => { - checkPropertyByAnnotations(item, structName, mustInitMap, shouldInitMap, cannotInitMap); + checkPropertyByAnnotations(item, structName, mustInitMap, cannotInitMap); }); }); } @@ -141,7 +127,7 @@ function checkMustInitialize( return; } const structName: string = getIdentifierName(node); - if (!mustInitMap.has(structName)) { + if (!mustInitMap.has(structName) || !node.parent) { return; } const parentNode: arkts.AstNode = node.parent; @@ -165,40 +151,6 @@ function checkMustInitialize( }); } -function checkShouldInitialize( - node: arkts.AstNode, - context: UISyntaxRuleContext, - shouldInitMap: Map>, -): void { - if (!arkts.isIdentifier(node)) { - return; - } - const structName: string = getIdentifierName(node); - if (!shouldInitMap.has(structName)) { - return; - } - const parentNode: arkts.AstNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; - } - // Get all the properties of a record via StructName - const shouldInitProperty: Map = shouldInitMap.get(structName)!; - const childkeyNameArray: string[] = getChildKeyNameArray(parentNode); - // If the attribute that should be initialized is not initialized, an error is reported - shouldInitProperty.forEach((value, key) => { - if (!childkeyNameArray.includes(key)) { - context.report({ - node: parentNode, - message: rule.messages.shouldInitializeViaComponentConstructor, - data: { - varName: `@${key}`, - customComponentName: structName, - }, - }); - } - }); -} - function checkCannotInitialize( node: arkts.AstNode, context: UISyntaxRuleContext, @@ -208,7 +160,7 @@ function checkCannotInitialize( return; } const structName: string = getIdentifierName(node); - if (!cannotInitMap.has(structName)) { + if (!cannotInitMap.has(structName) || !node.parent) { return; } const parentNode: arkts.AstNode = node.parent; @@ -230,7 +182,7 @@ function checkCannotInitialize( if (cannotInitName.has(propertyName)) { const propertyType: string = cannotInitName.get(propertyName)!; context.report({ - node: property.key, + node: property, message: rule.messages.disallowVariableInitializationViaComponentConstructor, data: { decoratorName: `@${propertyType}`, @@ -247,18 +199,15 @@ const rule: UISyntaxRule = { name: 'variable-initialization-via-component-constructor', messages: { requireVariableInitializationViaComponentConstructor: `'@Require' decorated '{{varName}}' must be initialized through the component constructor.`, - shouldInitializeViaComponentConstructor: `The property '{{varName}}' in the custom component '{{customComponentName}}' is missing (mandatory to specify).`, disallowVariableInitializationViaComponentConstructor: `The '{{decoratorName}}' property '{{varName}}' in the custom component '{{customComponentName}}' cannot be initialized here (forbidden to specify).`, }, setup(context) { let mustInitMap: Map> = new Map(); - let shouldInitMap: Map> = new Map(); let cannotInitMap: Map> = new Map(); return { parsed: (node): void => { - initMap(node, mustInitMap, shouldInitMap, cannotInitMap); + initMap(node, mustInitMap, cannotInitMap); checkMustInitialize(node, context, mustInitMap); - checkShouldInitialize(node, context, shouldInitMap); checkCannotInitialize(node, context, cannotInitMap); }, }; diff --git a/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-function.ts b/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-function.ts index 901e208bf..c2a8886b0 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-function.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-function.ts @@ -68,11 +68,11 @@ function getPrivateNames(node: arkts.StructDeclaration): string[] { function reportInvalidWatch( member: arkts.ClassProperty, parameterName: string, - hasWatchDecorator: arkts.AnnotationUsage, + watchDecorator: arkts.AnnotationUsage, context: UISyntaxRuleContext ): void { context.report({ - node: hasWatchDecorator, + node: watchDecorator, message: rule.messages.invalidWatch, data: { parameterName }, fix: () => { @@ -89,14 +89,14 @@ function reportInvalidWatch( function reportStringOnly( parameters: arkts.Expression | undefined, privateNames: string[], - hasWatchDecorator: arkts.AnnotationUsage, + watchDecorator: arkts.AnnotationUsage, context: UISyntaxRuleContext ): void { if (!parameters) { return; } context.report({ - node: hasWatchDecorator, + node: watchDecorator, message: rule.messages.stringOnly, data: { parameterName: getExpressionValue(parameters, privateNames) }, fix: () => { @@ -114,11 +114,11 @@ function validateWatchDecorator( member: arkts.ClassProperty, methodNames: string[], privateNames: string[], - hasWatchDecorator: arkts.AnnotationUsage | undefined, + watchDecorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { member.annotations.forEach((annotation) => { - validateWatchProperty(annotation, member, methodNames, privateNames, hasWatchDecorator, context); + validateWatchProperty(annotation, member, methodNames, privateNames, watchDecorator, context); }); } @@ -127,7 +127,7 @@ function validateWatchProperty( member: arkts.ClassProperty, methodNames: string[], privateNames: string[], - hasWatchDecorator: arkts.AnnotationUsage | undefined, + watchDecorator: arkts.AnnotationUsage | undefined, context: UISyntaxRuleContext ): void { if ( @@ -145,15 +145,15 @@ function validateWatchProperty( return; } if (!arkts.isStringLiteral(element.value)) { - if (!hasWatchDecorator) { + if (!watchDecorator) { return; } - reportStringOnly(element.value, privateNames, hasWatchDecorator, context); + reportStringOnly(element.value, privateNames, watchDecorator, context); return; } const parameterName = element.value.str; - if (hasWatchDecorator && parameterName && !methodNames.includes(parameterName)) { - reportInvalidWatch(member, parameterName, hasWatchDecorator, context); + if (watchDecorator && parameterName && !methodNames.includes(parameterName)) { + reportInvalidWatch(member, parameterName, watchDecorator, context); } }); @@ -169,12 +169,12 @@ function validateWatch( if (!arkts.isClassProperty(member)) { return; } - const hasWatchDecorator = member.annotations?.find(annotation => + const watchDecorator = member.annotations?.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.WATCH ); // Determine whether it contains @watch decorators - validateWatchDecorator(member, methodNames, privateNames, hasWatchDecorator, context); + validateWatchDecorator(member, methodNames, privateNames, watchDecorator, context); }); } diff --git a/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-regular.ts b/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-regular.ts index fa88f5b99..339ba276e 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-regular.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/watch-decorator-regular.ts @@ -24,7 +24,7 @@ function validateWatchDecorator(node: arkts.StructDeclaration, context: UISyntax if (!arkts.isClassProperty(member)) { return; } - const hasWatchDecorator = member.annotations?.find(annotation => + const watchDecorator = member.annotations?.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.WATCH ); @@ -32,9 +32,9 @@ function validateWatchDecorator(node: arkts.StructDeclaration, context: UISyntax const propertyName = getClassPropertyName(member); // Determine if there are any decorations other than @watch decorations // rule1: The @Watch decorator must be used with other decorators - if (hasWatchDecorator && propertyAnnotationNames.length < PROPERTY_ANNOTATION_NUM) { + if (watchDecorator && propertyAnnotationNames.length < PROPERTY_ANNOTATION_NUM) { context.report({ - node: hasWatchDecorator, + node: watchDecorator, message: rule.messages.invalidWatch, data: { propertyName: propertyName diff --git a/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts b/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts index c956e903b..c86bb28da 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts @@ -52,7 +52,7 @@ function validateWrapBuilderInIdentifier( return; } const parentNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { + if (!parentNode || !arkts.isCallExpression(parentNode)) { return; } let functionName: string = ''; @@ -72,38 +72,6 @@ function validateWrapBuilderInIdentifier( } } -function validateWrapBuilderInReturnStatement( - node: arkts.AstNode, - builderFunctionNames: string[], - context: UISyntaxRuleContext -): void { - if (!arkts.isReturnStatement(node)) { - return; - } - if (!node.argument || !arkts.isCallExpression(node.argument)) { - return; - } - if (!node.argument.expression || !arkts.isIdentifier(node.argument.expression) || - node.argument.expression.name !== WRAP_BUILDER) { - return; - } - let functionName: string = ''; - // Get the parameters of the wrap builder - node.argument.arguments.forEach(argument => { - if (arkts.isIdentifier(argument)) { - functionName = argument.name; - } - }); - // If the function name is not empty and not decorated by the @builder, an error is reported - if (functionName === '' || !builderFunctionNames.includes(functionName)) { - const errorNode = node.argument.arguments[0]; - context.report({ - node: errorNode, - message: rule.messages.invalidWrapBuilderCheck, - }); - } -} - const rule: UISyntaxRule = { name: 'wrap-builder-check', messages: { @@ -117,7 +85,6 @@ const rule: UISyntaxRule = { collectBuilderFunctions(node, builderFunctionNames); } validateWrapBuilderInIdentifier(node, builderFunctionNames, context); - validateWrapBuilderInReturnStatement(node, builderFunctionNames, context); }, }; }, diff --git a/arkui-plugins/ui-syntax-plugins/utils/index.ts b/arkui-plugins/ui-syntax-plugins/utils/index.ts index 91694abaf..5dffe68af 100644 --- a/arkui-plugins/ui-syntax-plugins/utils/index.ts +++ b/arkui-plugins/ui-syntax-plugins/utils/index.ts @@ -100,6 +100,7 @@ export const PresetDecorators = { REGULAR: 'regular', VARIABLE: 'variable', PARAMETER: 'parameter', + ANIMATABLE_EXTEND: 'AnimatableExtend', }; export const TOGGLE_TYPE: string = 'ToggleType'; @@ -120,6 +121,16 @@ export const ReuseConstants = { REUSE_ID: 'reuseId', }; +const OPTIONAL_MASK = 1 << 7; + +export function isClassPropertyOptional(node: arkts.ClassProperty): boolean { + if ((node.modifiers & OPTIONAL_MASK) !== 0) { + return true; + } else { + return false; + } +} + export function getIdentifierName(node: arkts.AstNode): string { if (!arkts.isIdentifier(node)) { throw new Error(`Except a Identifier type!`); -- Gitee