From e73309a24d4dbc9551fdf6578d70256e1b20988e Mon Sep 17 00:00:00 2001 From: wangweiyuan Date: Fri, 20 Jun 2025 21:12:55 +0800 Subject: [PATCH] ui-syntax-plugins add level 0620 Signed-off-by: wangweiyuan --- .../rules/build-root-node.ts | 154 ++--- .../rules/check-decorated-property-type.ts | 104 ++-- .../rules/component-componentV2-init-check.ts | 115 ++-- .../component-componentV2-mix-use-check.ts | 292 +++++----- .../rules/componentV2-mix-check.ts | 79 +-- .../componentV2-state-usage-validation.ts | 525 ++++++++--------- .../rules/computed-decorator-check.ts | 514 ++++++++--------- .../rules/construct-parameter.ts | 444 +++++++------- .../consumer-provider-decorator-check.ts | 546 ++++++++---------- .../rules/custom-dialog-missing-controller.ts | 133 +++-- .../ui-syntax-plugins/rules/index.ts | 78 +-- .../rules/monitor-decorator-check.ts | 332 +++++------ .../rules/nested-relationship.ts | 275 +++++---- .../rules/nested-reuse-component-check.ts | 309 +++++----- .../rules/no-child-in-button.ts | 138 ++--- .../rules/no-duplicate-entry.ts | 99 ++-- .../rules/no-duplicate-preview.ts | 98 ++-- .../rules/no-duplicate-state-manager.ts | 80 ++- .../rules/no-same-as-built-in-attribute.ts | 60 +- .../observed-heritage-compatible-check.ts | 178 +++--- .../rules/observed-observedV2-check.ts | 52 +- 21 files changed, 2244 insertions(+), 2361 deletions(-) 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 ce8a01e04..86ac32a63 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts @@ -17,85 +17,99 @@ 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 parsed(node: arkts.AstNode): void { - if (!arkts.isStructDeclaration(node)) { - return; + 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.`, + }; } - 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 | undefined = this.getComponentName(callExpression); + if (!componentName) { + return; + } + 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; + private isContainerComponent(componentName: string): boolean { + const loadedContainerComponents = this.context.componentsInfo.containerComponents; + if (!componentName || !loadedContainerComponents) { + return false; + } + return loadedContainerComponents.includes(componentName); } - 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); + private getComponentName(node: arkts.AstNode): string | undefined { + let children = node.getChildren(); + let componentName: string | undefined; + + while (true) { + if (!children || children.length === 0) { + return undefined; + } + + const firstChild = children[0]; + + if (arkts.isIdentifier(firstChild)) { + componentName = getIdentifierName(firstChild); + return componentName; + } + + if (!arkts.isMemberExpression(firstChild) && !arkts.isCallExpression(firstChild)) { + return undefined; + } + + children = firstChild.getChildren(); + } } - return ''; - } } + export default BuildRootNodeRule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/check-decorated-property-type.ts b/arkui-plugins/ui-syntax-plugins/rules/check-decorated-property-type.ts index 05563bbcc..e3d8f3446 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/check-decorated-property-type.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/check-decorated-property-type.ts @@ -14,7 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; import { forbiddenUseStateType, getAnnotationUsage, getClassPropertyAnnotationNames, getClassPropertyName, @@ -35,62 +35,58 @@ const forbiddenUseStateTypeForDecorators: string[] = [ PresetDecorators.LOCAL_STORAGE_LINK, ]; -function checkDecoratedPropertyType( - member: arkts.AstNode, - context: UISyntaxRuleContext, - annoList: string[], - typeList: string[] -): void { - if (!arkts.isClassProperty(member)) { - return; - } - const propertyName = getClassPropertyName(member); - const propertyType = getClassPropertyType(member); - if (!propertyName || !propertyType) { - return; - } - const propertyAnnotationNames: string[] = getClassPropertyAnnotationNames(member); - const decoratorName: string | undefined = - propertyAnnotationNames.find((annotation) => annoList.includes(annotation)); - const isType: boolean = typeList.includes(propertyType); - if (!member.key) { - return; +class CheckDecoratedPropertyTypeRule extends AbstractUISyntaxRule { + public setup(): Record { + return { + invalidDecoratedPropertyType: `The '@{{decoratorName}}' property '{{propertyName}}' cannot be a '{{propertyType}}' object.`, + }; } - const errorNode = member.key; - if (decoratorName && isType) { - context.report({ - node: errorNode, - message: rule.messages.invalidDecoratedPropertyType, - data: { decoratorName, propertyName, propertyType }, + + public parsed(node: arkts.StructDeclaration): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + if (!node.definition) { + return; + } + const componentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); + if (!componentDecorator) { + return; + } + node.definition.body.forEach(member => { + this.checkDecoratedPropertyType(member, forbiddenUseStateTypeForDecorators, forbiddenUseStateType); }); } + private checkDecoratedPropertyType( + member: arkts.AstNode, + annoList: string[], + typeList: string[] + ): void { + if (!arkts.isClassProperty(member)) { + return; + } + const propertyName = getClassPropertyName(member); + const propertyType = getClassPropertyType(member); + if (!propertyName || !propertyType) { + return; + } + const propertyAnnotationNames: string[] = getClassPropertyAnnotationNames(member); + const decoratorName: string | undefined = + propertyAnnotationNames.find((annotation) => annoList.includes(annotation)); + const isType: boolean = typeList.includes(propertyType); + if (!member.key) { + return; + } + const errorNode = member.key; + if (decoratorName && isType) { + this.report({ + node: errorNode, + message: this.messages.invalidDecoratedPropertyType, + data: { decoratorName, propertyName, propertyType }, + }); + } + } } -const rule: UISyntaxRule = { - name: 'check-decorated-property-type', - messages: { - invalidDecoratedPropertyType: `The '@{{decoratorName}}' property '{{propertyName}}' cannot be a '{{propertyType}}' object.`, - }, - setup(context) { - return { - parsed: (node): void => { - if (!arkts.isStructDeclaration(node)) { - return; - } - if (!node.definition) { - return; - } - const componentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); - if (!componentDecorator) { - return; - } - node.definition.body.forEach(member => { - checkDecoratedPropertyType(member, context, forbiddenUseStateTypeForDecorators, forbiddenUseStateType); - }); - }, - }; - }, -}; - -export default rule; \ No newline at end of file +export default CheckDecoratedPropertyTypeRule; \ No newline at end of file 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 e7d90dc82..7388e6599 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 @@ -15,81 +15,74 @@ import * as arkts from '@koalaui/libarkts'; import { getAnnotationUsage, getIdentifierName, hasAnnotation, PresetDecorators } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -function initComponentV1WithLinkList(node: arkts.AstNode, componentV1WithLinkList: string[]): void { - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; +class ComponentComponentV2InitCheckRule extends AbstractUISyntaxRule { + private componentV1WithLinkList: string[] = []; + + public setup(): Record { + return { + componentInitLinkCheck: `A V2 component cannot be used with any member property decorated by '@Link' in a V1 component.`, + }; } - node.getChildren().forEach((member) => { - if (!arkts.isStructDeclaration(member) || !member.definition.ident || - !hasAnnotation(member?.definition.annotations, PresetDecorators.COMPONENT_V1)) { + public parsed(node: arkts.StructDeclaration): void { + this.initComponentV1WithLinkList(node); + this.checkComponentInitLink(node); + } + + private initComponentV1WithLinkList(node: arkts.AstNode): void { + if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { return; } - let structName: string = member.definition.ident?.name ?? ''; - member.definition?.body?.forEach((item) => { - if (!arkts.isClassProperty(item) || !item.key) { + node.getChildren().forEach((member) => { + if (!arkts.isStructDeclaration(member) || !member.definition.ident || + !hasAnnotation(member?.definition.annotations, PresetDecorators.COMPONENT_V1)) { return; } - if (item.annotations.some(annotation => annotation.expr && - getIdentifierName(annotation.expr) === PresetDecorators.LINK)) { - componentV1WithLinkList.push(structName); - } + let structName: string = member.definition.ident?.name ?? ''; + member.definition?.body?.forEach((item) => { + if (!arkts.isClassProperty(item) || !item.key) { + return; + } + if (item.annotations.some(annotation => annotation.expr && + getIdentifierName(annotation.expr) === PresetDecorators.LINK)) { + this.componentV1WithLinkList.push(structName); + } + }); }); - }); -} - -function checkComponentInitLink( - node: arkts.AstNode, - context: UISyntaxRuleContext, - componentV1WithLinkList: string[] -): void { - if (!arkts.isIdentifier(node) || !componentV1WithLinkList.includes(getIdentifierName(node))) { - return; } - if (!node.parent) { - return; - } - let structNode = node.parent; - while (!arkts.isStructDeclaration(structNode)) { - if (!structNode.parent) { + + private checkComponentInitLink(node: arkts.AstNode): void { + if (!arkts.isIdentifier(node) || !this.componentV1WithLinkList.includes(getIdentifierName(node))) { return; } - structNode = structNode.parent; - } - if (getAnnotationUsage(structNode, PresetDecorators.COMPONENT_V2) !== undefined) { if (!node.parent) { return; } - const parentNode = node.parent; - context.report({ - node: parentNode, - message: rule.messages.componentInitLinkCheck, - fix: () => { - return { - range: [parentNode.startPosition, parentNode.endPosition], - code: '', - }; + let structNode = node.parent; + while (!arkts.isStructDeclaration(structNode)) { + if (!structNode.parent) { + return; } - }); + structNode = structNode.parent; + } + if (getAnnotationUsage(structNode, PresetDecorators.COMPONENT_V2) !== undefined) { + if (!node.parent) { + return; + } + const parentNode = node.parent; + this.report({ + node: parentNode, + message: this.messages.componentInitLinkCheck, + fix: () => { + return { + range: [parentNode.startPosition, parentNode.endPosition], + code: '', + }; + } + }); + } } } -const rule: UISyntaxRule = { - name: 'component-componentV2-init-check', - messages: { - componentInitLinkCheck: `A V2 component cannot be used with any member property decorated by '@Link' in a V1 component.`, - }, - setup(context) { - // Decorated by component v1 and uses the link attribute - let componentV1WithLinkList: string[] = []; - return { - parsed: (node): void => { - initComponentV1WithLinkList(node, componentV1WithLinkList); - checkComponentInitLink(node, context, componentV1WithLinkList); - }, - }; - }, -}; - -export default rule; \ No newline at end of file +export default ComponentComponentV2InitCheckRule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-mix-use-check.ts b/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-mix-use-check.ts index 70f6bf631..58140c91e 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-mix-use-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/component-componentV2-mix-use-check.ts @@ -15,7 +15,7 @@ import * as arkts from '@koalaui/libarkts'; import { PresetDecorators, getIdentifierName } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; const v1ComponentDecorators: string[] = [ PresetDecorators.STATE, @@ -28,6 +28,7 @@ const v1ComponentDecorators: string[] = [ PresetDecorators.LOCAL_STORAGE_LINK, PresetDecorators.LOCAL_STORAGE_PROP, ]; + const v2ComponentDecorators: string[] = [ PresetDecorators.LOCAL, PresetDecorators.PARAM, @@ -36,174 +37,175 @@ const v2ComponentDecorators: string[] = [ PresetDecorators.CONSUMER, ]; -// Report an Observed version violation error -function checkObservedConflict( - node: arkts.ClassProperty, - context: UISyntaxRuleContext, - componentDecorators: string[], - message: string -): void { - node.annotations.forEach((anno) => { - if (!anno.expr) { - return; +class ComponentComponentV2MixUseCheckRule extends AbstractUISyntaxRule { + private observedV1Names: Set = new Set(); + private observedV2Names: Set = new Set(); + + public setup(): Record { + return { + observedv1_v2: `The type of the @{{annotation}} property cannot be a class decorated with '@Observed'.`, + observedv2_v1: `The type of the @{{annotation}} property cannot be a class decorated with '@ObservedV2'.` + }; + } + + public parsed(node: arkts.AstNode): void { + if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + this.findAllObserved(node); + this.findAllTSTypeAliasDeclaration(node); + } + + if (arkts.isStructDeclaration(node)) { + this.processComponentAnnotations(node); } - const annotationName = getIdentifierName(anno.expr); - if (annotationName && componentDecorators.includes(annotationName)) { - context.report({ - node: anno, - message: message, - data: { - annotation: annotationName, + } + + private findAllObserved(node: arkts.AstNode): void { + if (arkts.isClassDeclaration(node)) { + node.definition?.annotations.forEach((anno) => { + if (!anno.expr) { + return; + } + + const annotationName = getIdentifierName(anno.expr); + if (annotationName === PresetDecorators.OBSERVED_V1) { + const componentV1Name = node?.definition?.ident?.name; + componentV1Name ? this.observedV1Names.add(componentV1Name) : null; + } + + if (annotationName === PresetDecorators.OBSERVED_V2) { + const componentV2Name = node?.definition?.ident?.name; + componentV2Name ? this.observedV2Names.add(componentV2Name) : null; } }); } - }); -} -function processNode( - node: arkts.ClassProperty, - annotationName: string, - observedV1Name: Set, - observedV2Name: Set, - context: UISyntaxRuleContext -): void { - const queue: Array = [node]; - while (queue.length > 0) { - const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; - if (arkts.isIdentifier(currentNode)) { - if (observedV1Name.has(getIdentifierName(currentNode)) && annotationName === PresetDecorators.COMPONENT_V2) { - checkObservedConflict(node, context, v2ComponentDecorators, rule.messages.observedv1_v2); - break; - } - if (observedV2Name.has(getIdentifierName(currentNode)) && annotationName === PresetDecorators.COMPONENT_V1) { - checkObservedConflict(node, context, v1ComponentDecorators, rule.messages.observedv2_v1); - break; + for (const child of node.getChildren()) { + this.findAllObserved(child); + } + } + + private findAllTSTypeAliasDeclaration(node: arkts.AstNode): void { + if ( + arkts.nodeType(node) === + arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_ALIAS_DECLARATION + ) { + for (const child of node.getChildren()) { + if (arkts.isIdentifier(child)) { + const typeName = getIdentifierName(child); + this.findAllObservedType(node, typeName); + } } } - const children = currentNode.getChildren(); - for (const child of children) { - queue.push(child); + + for (const child of node.getChildren()) { + this.findAllTSTypeAliasDeclaration(child); } } -} -function traverseTree( - node: arkts.AstNode, - annotationName: string, - observedV1Name: Set, - observedV2Name: Set, - context: UISyntaxRuleContext -): void { - if (arkts.isClassProperty(node)) { - processNode(node, annotationName, observedV1Name, observedV2Name, context); - } - const children = node.getChildren(); - for (const child of children) { - traverseTree(child, annotationName, observedV1Name, observedV2Name, context); + private findAllObservedType( + node: arkts.AstNode, + typeName: string + ): void { + if (arkts.isIdentifier(node)) { + const name = getIdentifierName(node); + if (this.observedV1Names.has(name)) { + this.observedV1Names.add(typeName); + } + if (this.observedV2Names.has(name)) { + this.observedV2Names.add(typeName); + } + } + + for (const child of node.getChildren()) { + this.findAllObservedType(child, typeName); + } } -} -function findAllObserved(node: arkts.AstNode, observedV1Name: Set, observedV2Name: Set): void { - if (arkts.isClassDeclaration(node)) { - node.definition?.annotations.forEach((anno) => { + private processComponentAnnotations( + node: arkts.StructDeclaration + ): void { + node.definition.annotations.forEach((anno) => { if (!anno.expr) { return; } const annotationName = getIdentifierName(anno.expr); - if (annotationName === PresetDecorators.OBSERVED_V1) { - const componentV1Name = node?.definition?.ident?.name; - componentV1Name ? observedV1Name.add(componentV1Name) : null; - } - if (annotationName === PresetDecorators.OBSERVED_V2) { - const componentV2Name = node?.definition?.ident?.name; - componentV2Name ? observedV2Name.add(componentV2Name) : null; + if ( + annotationName === PresetDecorators.COMPONENT_V2 || + annotationName === PresetDecorators.COMPONENT_V1 + ) { + this.traverseTree(node, annotationName); } }); } - const children = node.getChildren(); - for (const child of children) { - findAllObserved(child, observedV1Name, observedV2Name); + + private traverseTree( + node: arkts.AstNode, + annotationName: string + ): void { + if (arkts.isClassProperty(node)) { + this.processNode(node, annotationName); + } + + for (const child of node.getChildren()) { + this.traverseTree(child, annotationName); + } } -} -function findAllTSTypeAliasDeclaration( - node: arkts.AstNode, - observedV1Name: Set, - observedV2Name: Set -): void { - if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_TS_TYPE_ALIAS_DECLARATION) { - node.getChildren().forEach((child) => { - if (arkts.isIdentifier(child)) { - const typeName = getIdentifierName(child); - findAllObservedType(node, typeName, observedV1Name, observedV2Name); + private processNode( + node: arkts.ClassProperty, + annotationName: string + ): void { + const queue: Array = [node]; + while (queue.length > 0) { + const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; + if (arkts.isIdentifier(currentNode)) { + const name = getIdentifierName(currentNode); + if ( + annotationName === PresetDecorators.COMPONENT_V2 && + this.observedV1Names.has(name) + ) { + this.checkObservedConflict(node, v2ComponentDecorators); + break; + } + + if ( + annotationName === PresetDecorators.COMPONENT_V1 && + this.observedV2Names.has(name) + ) { + this.checkObservedConflict(node, v1ComponentDecorators); + break; + } } - }); - } - const children = node.getChildren(); - for (const child of children) { - findAllTSTypeAliasDeclaration(child, observedV1Name, observedV2Name); + const children = currentNode.getChildren(); + for (const child of children) { + queue.push(child); + } + } } -} -function findAllObservedType( - node: arkts.AstNode, - typeName: string, - observedV1Name: Set, - observedV2Name: Set -): void { - if (arkts.isIdentifier(node) && observedV1Name.has(getIdentifierName(node))) { - observedV1Name.add(typeName); - } - if (arkts.isIdentifier(node) && observedV2Name.has(getIdentifierName(node))) { - observedV2Name.add(typeName); - } - const children = node.getChildren(); - for (const child of children) { - findAllObservedType(child, typeName, observedV1Name, observedV2Name); - } -} + private checkObservedConflict( + node: arkts.ClassProperty, + componentDecorators: string[] + ): void { + node.annotations.forEach((anno) => { + if (!anno.expr) { + return; + } -function processComponentAnnotations( - node: arkts.StructDeclaration, - observedV1Name: Set, - observedV2Name: Set, - context: UISyntaxRuleContext -): void { - node.definition.annotations.forEach((anno) => { - if (!anno.expr) { - return; - } - const annotationName = getIdentifierName(anno.expr); - if (annotationName === PresetDecorators.COMPONENT_V2) { - traverseTree(node, PresetDecorators.COMPONENT_V2, observedV1Name, observedV2Name, context); - } - if (annotationName === PresetDecorators.COMPONENT_V1) { - traverseTree(node, PresetDecorators.COMPONENT_V1, observedV1Name, observedV2Name, context); - } - }); + const annotationName = getIdentifierName(anno.expr); + if (annotationName && componentDecorators.includes(annotationName)) { + this.report({ + node: anno, + message: this.messages[ + v1ComponentDecorators.includes(annotationName) ? 'observedv2_v1' : 'observedv1_v2' + ], + data: { + annotation: annotationName, + }, + }); + } + }); + } } -const rule: UISyntaxRule = { - name: 'component-componentV2-mix-use-check', - messages: { - observedv1_v2: `The type of the @{{annotation}} property cannot be a class decorated with '@Observed'.`, - observedv2_v1: `The type of the @{{annotation}} property cannot be a class decorated with '@ObservedV2'.` - }, - setup(context) { - let observedV1Name: Set = new Set(); - let observedV2Name: Set = new Set(); - return { - parsed: (node): void => { - if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - findAllObserved(node, observedV1Name, observedV2Name); - findAllTSTypeAliasDeclaration(node, observedV1Name, observedV2Name); - } - if (arkts.isStructDeclaration(node)) { - processComponentAnnotations(node, observedV1Name, observedV2Name, context); - } - }, - }; - }, -}; - -export default rule; +export default ComponentComponentV2MixUseCheckRule; \ No newline at end of file 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 93f8a023a..7db394582 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/componentV2-mix-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/componentV2-mix-check.ts @@ -15,46 +15,47 @@ import * as arkts from '@koalaui/libarkts'; import { getAnnotationUsage, PresetDecorators } from '../utils'; -import { UISyntaxRule } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -const rule: UISyntaxRule = { - name: 'componentV2-mix-check', - messages: { - conflictWithComponentV2: `The struct '{{structName}}' can not be decorated with '@ComponentV2' and '@Component', '@Reusable', '@CustomDialog' at the same time.`, - }, - setup(context) { +class ComponentV2MixCheckRule extends AbstractUISyntaxRule { + public setup(): Record { return { - parsed: (node): void => { - if (!arkts.isStructDeclaration(node)) { - return; - } - if (!node.definition) { - return; - } - const structName = node.definition.ident?.name ?? ''; - const structNameNode = node.definition.ident; - if (!structNameNode) { - return; - } - // Check if the struct has the '@ComponentV2' annotation - const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - if (!componentV2Decorator) { - return; - } - // Check for the presence of conflicting decorators: '@Component', '@Reusable', '@CustomDialog' - 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, - data: { structName }, - }); - } - }, + conflictWithComponentV2: `The struct '{{structName}}' can not be decorated with '@ComponentV2' and '@Component', '@Reusable', '@CustomDialog' at the same time.`, }; - }, -}; + } -export default rule; + public parsed(node: arkts.AstNode): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + const definition = node.definition; + if (!definition) { + return; + } + const structNameNode = definition.ident; + if (!structNameNode) { + return; + } + const structName = structNameNode.name ?? ''; + // Check if the struct has the '@ComponentV2' annotation + const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + if (!componentV2Decorator) { + return; + } + + // Check for conflicting decorators + 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) { + this.report({ + node: structNameNode, + message: this.messages.conflictWithComponentV2, + data: { structName }, + }); + } + } +} + +export default ComponentV2MixCheckRule; \ No newline at end of file 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 345b9058d..cbe92f650 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 @@ -17,72 +17,125 @@ import * as arkts from '@koalaui/libarkts'; import { getClassPropertyAnnotationNames, PresetDecorators, getAnnotationUsage, getClassPropertyName, getIdentifierName } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; const builtInDecorators = [PresetDecorators.LOCAL, PresetDecorators.PARAM, PresetDecorators.EVENT]; -// Helper functions for rules -const hasisComponentV2 = (node: arkts.StructDeclaration): boolean => !!getAnnotationUsage(node, - PresetDecorators.COMPONENT_V2); -const hasComponent = (node: arkts.StructDeclaration): boolean => !!getAnnotationUsage(node, - PresetDecorators.COMPONENT_V1); -function checkMultipleBuiltInDecorators(context: UISyntaxRuleContext, member: arkts.ClassProperty, - propertyDecorators: string[]): void { - const appliedBuiltInDecorators = propertyDecorators.filter(d => builtInDecorators.includes(d)); - if (appliedBuiltInDecorators.length > 1) { - member.annotations?.forEach(annotation => { - if (annotation.expr && arkts.isIdentifier(annotation.expr)) { - const annotationsName = annotation.expr.name; - reportMultipleBuiltInDecoratorsError(context, annotation, annotationsName, builtInDecorators); - } - }); +class ComponentV2StateUsageValidationRule extends AbstractUISyntaxRule { + private componentV2PropertyMap: Map> = new Map(); + public setup(): Record { + return { + multipleBuiltInDecorators: `The member property or method cannot be decorated by multiple built-in decorators.`, + 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' or '@BuilderParam'.`, + localNeedNoInit: `The '{{decoratorName}}' property '{{key}}' in the custom component '{{componentName}}' cannot be initialized here (forbidden to specify).`, + useStateDecoratorsWithProperty: `'@{{annotationName}}' can only decorate member property.`, + }; } -}; -function reportMultipleBuiltInDecoratorsError(context: UISyntaxRuleContext, annotation: arkts.AstNode, - annotationsName: string | undefined, builtInDecorators: string[]): void { - if (annotationsName && builtInDecorators.includes(annotationsName)) { - context.report({ - node: annotation, - message: rule.messages.multipleBuiltInDecorators, - }); + public parsed(node: arkts.AstNode): void { + this.initComponentV2PropertyMap(node, this.componentV2PropertyMap); + this.checkInitializeRule(node, this.componentV2PropertyMap); + if (arkts.isMethodDefinition(node)) { + // Rule 5: Local, Param, Event decorators must be used with Property + this.checkuseStateDecoratorsWithProperty(node); + } + if (!arkts.isStructDeclaration(node)) { + return; + } + this.validateClassPropertyDecorators(node); } -} -function checkParamRequiresRequire(context: UISyntaxRuleContext, member: arkts.ClassProperty, - propertyDecorators: string[]): void { - if (propertyDecorators.includes(PresetDecorators.PARAM) && !member.value && - !propertyDecorators.includes(PresetDecorators.REQUIRE) && member.key) { - const memberKey = member.key; - context.report({ - node: memberKey, - message: rule.messages.paramRequiresRequire, - fix: (memberKey) => { - const startPosition = memberKey.startPosition; - return { - range: [startPosition, startPosition], - code: `@${PresetDecorators.REQUIRE} `, - }; - }, + private hasisComponentV2 = (node: arkts.StructDeclaration): boolean => !!getAnnotationUsage(node, + PresetDecorators.COMPONENT_V2); + + private hasComponent = (node: arkts.StructDeclaration): boolean => !!getAnnotationUsage(node, + PresetDecorators.COMPONENT_V1); + + private checkMultipleBuiltInDecorators(member: arkts.ClassProperty, + propertyDecorators: string[]): void { + const appliedBuiltInDecorators = propertyDecorators.filter(d => builtInDecorators.includes(d)); + if (appliedBuiltInDecorators.length > 1) { + member.annotations?.forEach(annotation => { + if (annotation.expr && arkts.isIdentifier(annotation.expr)) { + const annotationsName = annotation.expr.name; + this.reportMultipleBuiltInDecoratorsError(annotation, annotationsName, builtInDecorators); + } + }); + } + }; + + private reportMultipleBuiltInDecoratorsError(annotation: arkts.AstNode, + annotationsName: string | undefined, builtInDecorators: string[]): void { + if (annotationsName && builtInDecorators.includes(annotationsName)) { + this.report({ + node: annotation, + message: this.messages.multipleBuiltInDecorators, + }); + } + } + + private checkParamRequiresRequire(member: arkts.ClassProperty, + propertyDecorators: string[]): void { + if (propertyDecorators.includes(PresetDecorators.PARAM) && !member.value && + !propertyDecorators.includes(PresetDecorators.REQUIRE) && member.key) { + const memberKey = member.key; + this.report({ + node: memberKey, + message: this.messages.paramRequiresRequire, + fix: (memberKey) => { + const startPosition = memberKey.startPosition; + return { + range: [startPosition, startPosition], + code: `@${PresetDecorators.REQUIRE} `, + }; + }, + }); + } + }; + + private checkRequireOnlyWithParam(member: arkts.ClassProperty, + propertyDecorators: string[], isComponentV2: boolean): void { + const requireDecorator = member.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.REQUIRE + ); + if (isComponentV2 && + requireDecorator && + !propertyDecorators.includes(PresetDecorators.PARAM) && + !propertyDecorators.includes(PresetDecorators.BUILDER_PARAM)) { + this.report({ + node: requireDecorator, + message: this.messages.requireOnlyWithParam, + fix: (requireDecorator) => { + const startPosition = requireDecorator.startPosition; + const endPosition = requireDecorator.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); + } + }; + + private checkuseStateDecoratorsWithProperty(method: arkts.MethodDefinition): void { + method.scriptFunction.annotations?.forEach(annotation => { + if (annotation.expr && arkts.isIdentifier(annotation.expr) && builtInDecorators.includes(annotation.expr.name)) { + const annotationName = annotation.expr.name; + this.reportInvalidDecoratorOnMethod(annotation, annotationName); + } }); } -}; -function checkRequireOnlyWithParam(context: UISyntaxRuleContext, member: arkts.ClassProperty, - propertyDecorators: string[], isComponentV2: boolean): void { - const requireDecorator = member.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.REQUIRE - ); - if (isComponentV2 && - requireDecorator && - !propertyDecorators.includes(PresetDecorators.PARAM) && - !propertyDecorators.includes(PresetDecorators.BUILDER_PARAM)) { - context.report({ - node: requireDecorator, - message: rule.messages.requireOnlyWithParam, - fix: (requireDecorator) => { - const startPosition = requireDecorator.startPosition; - const endPosition = requireDecorator.endPosition; + private reportInvalidDecoratorOnMethod(annotation: arkts.AnnotationUsage, + annotationName: string): void { + this.report({ + node: annotation, + message: this.messages.useStateDecoratorsWithProperty, + data: { annotationName }, + fix: (annotation) => { + const startPosition = annotation.startPosition; + const endPosition = annotation.endPosition; return { range: [startPosition, endPosition], code: '', @@ -90,247 +143,195 @@ function checkRequireOnlyWithParam(context: UISyntaxRuleContext, member: arkts.C }, }); } -}; - -function checkuseStateDecoratorsWithProperty(context: UISyntaxRuleContext, method: arkts.MethodDefinition): void { - method.scriptFunction.annotations?.forEach(annotation => { - if (annotation.expr && arkts.isIdentifier(annotation.expr) && builtInDecorators.includes(annotation.expr.name)) { - const annotationName = annotation.expr.name; - reportInvalidDecoratorOnMethod(context, annotation, annotationName); - } - }); -} -function reportInvalidDecoratorOnMethod(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage, - annotationName: string): void { - context.report({ - node: annotation, - message: rule.messages.useStateDecoratorsWithProperty, - data: { annotationName }, - fix: (annotation) => { - const startPosition = annotation.startPosition; - const endPosition = annotation.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); -} + private validateClassPropertyDecorators(node: arkts.StructDeclaration): void { + const isComponentV2 = this.hasisComponentV2(node); + const isComponent = this.hasComponent(node); + node.definition.body.forEach(member => { + if (!arkts.isClassProperty(member)) { + return; + } + const propertyDecorators = getClassPropertyAnnotationNames(member); + // Rule 1: Multiple built-in decorators + this.checkMultipleBuiltInDecorators(member, propertyDecorators); -function validateClassPropertyDecorators(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void { - const isComponentV2 = hasisComponentV2(node); - const isComponent = hasComponent(node); - node.definition.body.forEach(member => { - if (!arkts.isClassProperty(member)) { - return; - } - const propertyDecorators = getClassPropertyAnnotationNames(member); - // Rule 1: Multiple built-in decorators - checkMultipleBuiltInDecorators(context, member, propertyDecorators); + // Rule 2: @Param without default value must be combined with @Require + this.checkParamRequiresRequire(member, propertyDecorators); - // Rule 2: @Param without default value must be combined with @Require - checkParamRequiresRequire(context, member, propertyDecorators); + // Rule 3: @Require must be used together with @Param + this.checkRequireOnlyWithParam(member, propertyDecorators, isComponentV2); + }); + } - // Rule 3: @Require must be used together with @Param - checkRequireOnlyWithParam(context, member, propertyDecorators, isComponentV2); - }); -} + private checkDecorator(annoArray: readonly arkts.AnnotationUsage[], decorator: string): boolean { + let flag = false; + annoArray.forEach((anno) => { + if (!anno.expr) { + return; + } + const annoName = getIdentifierName(anno.expr); + if (annoName === decorator) { + flag = true; + } + }); + return flag; + } -function checkDecorator(annoArray: readonly arkts.AnnotationUsage[], decorator: string): boolean { - let flag = false; - annoArray.forEach((anno) => { - if (!anno.expr) { - return; + // Define a function to add property data to the property map + private addPropertyMap( + structName: string, + propertyName: string, + annotationName: string, + propertyMap: Map> + ): void { + if (!propertyMap.has(structName)) { + propertyMap.set(structName, new Map()); } - const annoName = getIdentifierName(anno.expr); - if (annoName === decorator) { - flag = true; + const structProperties = propertyMap.get(structName); + if (structProperties) { + structProperties.set(propertyName, annotationName); } - }); - return flag; -} - -// Define a function to add property data to the property map -function addPropertyMap( - structName: string, - propertyName: string, - annotationName: string, - propertyMap: Map> -): void { - if (!propertyMap.has(structName)) { - propertyMap.set(structName, new Map()); } - const structProperties = propertyMap.get(structName); - if (structProperties) { - structProperties.set(propertyName, annotationName); - } -} -// Iterate through the incoming componentv2 node to see if there are any state variables for decorator decorations -function initComponentV2PropertyMap( - node: arkts.AstNode, - componentV2PropertyMap: Map> -): void { - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; - } - node.getChildren().forEach((member) => { - if (!arkts.isStructDeclaration(member) || !member.definition.ident || - !checkDecorator(member?.definition.annotations, PresetDecorators.COMPONENT_V2)) { + // Iterate through the incoming componentv2 node to see if there are any state variables for decorator decorations + private initComponentV2PropertyMap( + node: arkts.AstNode, + componentV2PropertyMap: Map> + ): void { + if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { return; } - let structName: string = member.definition.ident?.name ?? ''; - member.definition?.body?.forEach((item) => { - processClassPropertyAnnotations(item, structName, componentV2PropertyMap); + node.getChildren().forEach((member) => { + if (!arkts.isStructDeclaration(member) || !member.definition.ident || + !this.checkDecorator(member?.definition.annotations, PresetDecorators.COMPONENT_V2)) { + return; + } + let structName: string = member.definition.ident?.name ?? ''; + member.definition?.body?.forEach((item) => { + this.processClassPropertyAnnotations(item, structName, componentV2PropertyMap); + }); }); - }); -} - -function processClassPropertyAnnotations( - item: arkts.AstNode, - structName: string, - componentV2PropertyMap: Map> -): void { - if (!arkts.isClassProperty(item) || !item.key) { - return; - } - let propertyName: string = getIdentifierName(item.key); - // If there is no decorator, it is a regular type - if (item.annotations.length === 0) { - let annotationName: string = PresetDecorators.REGULAR; - addPropertyMap(structName, propertyName, annotationName, componentV2PropertyMap); } - item.annotations.forEach((annotation) => { - if (!annotation.expr) { + + private processClassPropertyAnnotations( + item: arkts.AstNode, + structName: string, + componentV2PropertyMap: Map> + ): void { + if (!arkts.isClassProperty(item) || !item.key) { return; } - let annotationName: string = getIdentifierName(annotation.expr); - if (annotationName === PresetDecorators.LOCAL) { - addPropertyMap(structName, propertyName, annotationName, componentV2PropertyMap); + let propertyName: string = getIdentifierName(item.key); + // If there is no decorator, it is a regular type + if (item.annotations.length === 0) { + let annotationName: string = PresetDecorators.REGULAR; + this.addPropertyMap(structName, propertyName, annotationName, componentV2PropertyMap); } - }); -} + item.annotations.forEach((annotation) => { + if (!annotation.expr) { + return; + } + let annotationName: string = getIdentifierName(annotation.expr); + if (annotationName === PresetDecorators.LOCAL) { + this.addPropertyMap(structName, propertyName, annotationName, componentV2PropertyMap); + } + }); + } + private checkInitializeRule( + node: arkts.AstNode, -function checkInitializeRule( - node: arkts.AstNode, - context: UISyntaxRuleContext, - componentV2PropertyMap: Map>, -): void { - if (!arkts.isIdentifier(node) || !componentV2PropertyMap.has(getIdentifierName(node)) || !node.parent) { - return; - } - let structName: string = getIdentifierName(node); - let parentNode: arkts.AstNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; - } - let structNode = node.parent; - while (!arkts.isStructDeclaration(structNode)) { - if (!structNode.parent) { + componentV2PropertyMap: Map>, + ): void { + if (!arkts.isIdentifier(node) || !componentV2PropertyMap.has(getIdentifierName(node)) || !node.parent) { return; } - structNode = structNode.parent; - } - let parentPropertyMap: Map = new Map(); - structNode.definition.body.forEach((property) => { - if (!arkts.isClassProperty(property)) { + let structName: string = getIdentifierName(node); + let parentNode: arkts.AstNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { return; } - let propertyArray: string[] = []; - property.annotations.forEach((annotation) => { - if (!annotation.expr || !arkts.isIdentifier(annotation.expr)) { + let structNode = node.parent; + while (!arkts.isStructDeclaration(structNode)) { + if (!structNode.parent) { return; } - propertyArray.push(annotation.expr.name); - }); - parentPropertyMap.set(getClassPropertyName(property), propertyArray); - }); - // Gets all the properties recorded by the struct - const childPropertyName: Map = componentV2PropertyMap.get(structName)!; - parentNode.arguments.forEach((argument) => { - if (!arkts.isObjectExpression(argument)) { - return; + structNode = structNode.parent; } - argument.getChildren().forEach((property) => { - if (!arkts.isProperty(property) || !property.key) { + let parentPropertyMap: Map = new Map(); + structNode.definition.body.forEach((property) => { + if (!arkts.isClassProperty(property)) { return; } - const childkeyName = getIdentifierName(property.key); - if (!childPropertyName.has(childkeyName)) { + let propertyArray: string[] = []; + property.annotations.forEach((annotation) => { + if (!annotation.expr || !arkts.isIdentifier(annotation.expr)) { + return; + } + propertyArray.push(annotation.expr.name); + }); + const classPropertyName = getClassPropertyName(property); + if (!classPropertyName) { return; } - reportLocalNeedInit(childPropertyName, childkeyName, context, property, structName); - }); - }); -} - -function reportLocalNeedInit(childPropertyName: Map, childkeyName: string, context: UISyntaxRuleContext, - property: arkts.Property, structName: string): void { - if (childPropertyName.get(childkeyName) === PresetDecorators.LOCAL) { - context.report({ - node: property, - message: rule.messages.localNeedNoInit, - data: { - decoratorName: '@' + childPropertyName.get(childkeyName)!, - key: childkeyName, - componentName: structName, - }, - fix: (property) => { - return { - range: [property.startPosition, property.endPosition], - code: '', - }; - } + parentPropertyMap.set(classPropertyName, propertyArray); }); - } - if (childPropertyName.get(childkeyName) === PresetDecorators.REGULAR) { - context.report({ - node: property, - message: rule.messages.localNeedNoInit, - data: { - decoratorName: PresetDecorators.REGULAR, - key: childkeyName, - componentName: structName, - }, - fix: (property) => { - return { - range: [property.startPosition, property.endPosition], - code: '', - }; + // Gets all the properties recorded by the struct + const childPropertyName: Map = componentV2PropertyMap.get(structName)!; + parentNode.arguments.forEach((argument) => { + if (!arkts.isObjectExpression(argument)) { + return; } + argument.getChildren().forEach((property) => { + if (!arkts.isProperty(property) || !property.key) { + return; + } + const childkeyName = getIdentifierName(property.key); + if (!childPropertyName.has(childkeyName)) { + return; + } + this.reportLocalNeedInit(childPropertyName, childkeyName, property, structName); + }); }); } -} -const rule: UISyntaxRule = { - name: 'componentV2-state-usage-validation', - messages: { - multipleBuiltInDecorators: `The member property or method cannot be decorated by multiple built-in decorators.`, - 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' or '@BuilderParam'.`, - localNeedNoInit: `The '{{decoratorName}}' property '{{key}}' in the custom component '{{componentName}}' cannot be initialized here (forbidden to specify).`, - useStateDecoratorsWithProperty: `'@{{annotationName}}' can only decorate member property.`, - }, - - setup(context) { - let componentV2PropertyMap: Map> = new Map(); - return { - parsed: (node): void => { - initComponentV2PropertyMap(node, componentV2PropertyMap); - checkInitializeRule(node, context, componentV2PropertyMap); - if (arkts.isMethodDefinition(node)) { - // Rule 5: Local, Param, Event decorators must be used with Property - checkuseStateDecoratorsWithProperty(context, node); + private reportLocalNeedInit(childPropertyName: Map, childkeyName: string, + property: arkts.Property, structName: string): void { + if (childPropertyName.get(childkeyName) === PresetDecorators.LOCAL) { + this.report({ + node: property, + message: this.messages.localNeedNoInit, + data: { + decoratorName: '@' + childPropertyName.get(childkeyName)!, + key: childkeyName, + componentName: structName, + }, + fix: (property) => { + return { + range: [property.startPosition, property.endPosition], + code: '', + }; } - if (!arkts.isStructDeclaration(node)) { - return; + }); + } + if (childPropertyName.get(childkeyName) === PresetDecorators.REGULAR) { + this.report({ + node: property, + message: this.messages.localNeedNoInit, + data: { + decoratorName: PresetDecorators.REGULAR, + key: childkeyName, + componentName: structName, + }, + fix: (property) => { + return { + range: [property.startPosition, property.endPosition], + code: '', + }; } - validateClassPropertyDecorators(context, node); - }, - }; - }, + }); + } + } }; -export default rule; \ No newline at end of file +export default ComponentV2StateUsageValidationRule; \ No newline at end of file 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 19215a47a..f0d9fa2c8 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/computed-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/computed-decorator-check.ts @@ -15,48 +15,106 @@ import * as arkts from '@koalaui/libarkts'; import { getIdentifierName, PresetDecorators, getAnnotationName, getAnnotationUsage, BUILD_NAME } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; - -function validateStructBody( - node: arkts.StructDeclaration, - computedGetters: Map, - computedSetters: Map, - context: UISyntaxRuleContext -): void { - let computedDecorator: arkts.AnnotationUsage | undefined; - node.definition.body.forEach((member) => { - if (arkts.isClassProperty(member)) { - validateComputedOnClassProperty(member, context); - return; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; + + +class ComputedDecoratorCheckRule extends AbstractUISyntaxRule { + private computedGetters: Map = new Map(); + private computedSetters: Map = new Map(); + + public setup(): Record { + return { + onlyOnGetter: `@Computed can only decorate 'GetAccessor'.`, + onlyInObservedV2: `The '@Computed' can decorate only member method within a 'class' decorated with ObservedV2.`, + componentV2InStruct: `The '@Computed' decorator can only be used in a 'struct' decorated with ComponentV2.`, + noTwoWayBinding: `A property decorated by '@Computed' cannot be used with two-way bind syntax.`, + computedMethodDefineSet: `A property decorated by '@Computed' cannot define a set method.` + }; + } + + public parsed(node: arkts.AstNode): void { + if (arkts.isStructDeclaration(node)) { + this.validateComponentV2InStruct(node); + this.validateStructBody(node); + } + + if (arkts.isClassDeclaration(node)) { + this.validateClassBody(node); } - if (arkts.isMethodDefinition(member)) { - const methodName = getIdentifierName(member.name); - computedDecorator = member.scriptFunction.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.COMPUTED - ); - validateComputedMethodKind(member, computedDecorator, methodName, computedGetters, context); - const isSetter = member.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET; - if (isSetter) { - computedSetters.set(methodName, member); + } + + private validateStructBody(node: arkts.StructDeclaration): void { + let computedDecorator: arkts.AnnotationUsage | undefined; + node.definition.body.forEach((member) => { + if (arkts.isClassProperty(member)) { + this.validateComputedOnClassProperty(member); + return; } - if (methodName === BUILD_NAME) { - validateBuildMethod(member, computedGetters, context); + + if (arkts.isMethodDefinition(member)) { + const methodName = getIdentifierName(member.name); + computedDecorator = member.scriptFunction.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.COMPUTED + ); + + this.validateComputedMethodKind(member, computedDecorator, methodName); + if (member.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET) { + this.computedSetters.set(methodName, member); + } + + if (methodName === BUILD_NAME) { + this.validateBuildMethod(member); + } } + }); + + this.validateGetterSetterConflict(); + } + + private validateComputedOnClassProperty(member: arkts.ClassProperty): void { + const computedDecorator = member.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.COMPUTED + ); + if (computedDecorator) { + this.report({ + node: computedDecorator, + message: this.messages.onlyOnGetter, + fix: (computedDecorator) => { + const startPosition = computedDecorator.startPosition; + const endPosition = computedDecorator.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); } - }); - validateGetterSetterConflict(computedGetters, computedSetters, context); -} + } + + private validateComputedMethodKind( + member: arkts.MethodDefinition, + computedDecorator: arkts.AnnotationUsage | undefined, + methodName: string + ): void { + if (computedDecorator) { + const isGetter = member.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; + if (!isGetter) { + this.reportValidateComputedMethodKind(computedDecorator); + } else { + this.computedGetters.set(methodName, member); + } + } + } -function validateComputedOnClassProperty(member: arkts.ClassProperty, context: UISyntaxRuleContext): void { - const computedDecorator = member.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.COMPUTED - ); - if (computedDecorator) { - context.report({ + private reportValidateComputedMethodKind(computedDecorator: arkts.AnnotationUsage | undefined): void { + if (!computedDecorator) { + return; + } + this.report({ node: computedDecorator, - message: rule.messages.onlyOnGetter, + message: this.messages.onlyOnGetter, fix: (computedDecorator) => { const startPosition = computedDecorator.startPosition; const endPosition = computedDecorator.endPosition; @@ -67,273 +125,185 @@ function validateComputedOnClassProperty(member: arkts.ClassProperty, context: U }, }); } -} -function validateComputedMethodKind( - member: arkts.MethodDefinition, - computedDecorator: arkts.AnnotationUsage | undefined, - methodName: string, - computedGetters: Map, - context: UISyntaxRuleContext -): void { - if (computedDecorator) { - const isGetter = member.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET; - if (!isGetter) { - reportValidateComputedMethodKind(computedDecorator, context); - } else { - computedGetters.set(methodName, member); - } - } -} + private validateBuildMethod(member: arkts.MethodDefinition): void { + member.scriptFunction.body?.getChildren().forEach((childNode) => { + if (!arkts.isExpressionStatement(childNode)) { + return; + } -function reportValidateComputedMethodKind( - computedDecorator: arkts.AnnotationUsage | undefined, - context: UISyntaxRuleContext -): void { - if (!computedDecorator) { - return; + const queue: Array = [childNode]; + while (queue.length > 0) { + const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; + if (arkts.isCallExpression(currentNode)) { + this.validateCallExpression(currentNode); + } + // Continue traversing the child nodes + const children = currentNode.getChildren(); + for (const child of children) { + queue.push(child); + } + } + }); } - context.report({ - node: computedDecorator, - message: rule.messages.onlyOnGetter, - fix: (computedDecorator) => { - const startPosition = computedDecorator.startPosition; - const endPosition = computedDecorator.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); -} -function validateBuildMethod( - member: arkts.MethodDefinition, - computedGetters: Map, - context: UISyntaxRuleContext -): void { - member.scriptFunction.body?.getChildren().forEach((childNode) => { - if (!arkts.isExpressionStatement(childNode)) { + private validateCallExpression(currentNode: arkts.CallExpression): void { + if (!arkts.isIdentifier(currentNode.expression) || getIdentifierName(currentNode.expression) !== '$$') { return; } - const queue: Array = [childNode]; - while (queue.length > 0) { - const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; - // Check if it's a CallExpression (function call) - if (arkts.isCallExpression(currentNode)) { - // Check if it's a $$(...) call - validateCallExpression(currentNode, computedGetters, context); - } - // Continue traversing the child nodes - const children = currentNode.getChildren(); - for (const child of children) { - queue.push(child); - } - } - }); -} -function validateCallExpression( - currentNode: arkts.CallExpression, - computedGetters: Map, - context: UISyntaxRuleContext -): void { - // Check if it's a $$(...) call - if (!arkts.isIdentifier(currentNode.expression)) { - return; - } - if (getIdentifierName(currentNode.expression) === '$$') { - currentNode.arguments.forEach(argument => { + currentNode.arguments.forEach((argument) => { if (arkts.isMemberExpression(argument)) { const getterName = getIdentifierName(argument.property); - reportValidateCallExpression(currentNode, getterName, computedGetters, context); + this.reportValidateCallExpression(currentNode, getterName); } }); } -} -function reportValidateCallExpression( - currentNode: arkts.CallExpression, - getterName: string, - computedGetters: Map, - context: UISyntaxRuleContext -): void { - if (computedGetters.has(getterName)) { - context.report({ - node: currentNode, - message: rule.messages.noTwoWayBinding, - fix: (currentNode) => { - const startPosition = currentNode.startPosition; - const endPosition = currentNode.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); - } -} - -// Check for the presence of a @Computed property that defines both a getter and a setter -function validateGetterSetterConflict( - computedGetters: Map, - computedSetters: Map, - context: UISyntaxRuleContext -): void { - for (const [name] of computedGetters) { - if (computedSetters.has(name)) { - reportValidateGetterSetterConflict(computedSetters, name, context); + private reportValidateCallExpression( + currentNode: arkts.CallExpression, + getterName: string + ): void { + if (this.computedGetters.has(getterName)) { + this.report({ + node: currentNode, + message: this.messages.noTwoWayBinding, + fix: (currentNode) => { + const startPosition = currentNode.startPosition; + const endPosition = currentNode.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); } } -} -function reportValidateGetterSetterConflict( - computedSetters: Map, - name: string, - context: UISyntaxRuleContext -): void { - context.report({ - node: computedSetters.get(name)!, - message: rule.messages.computedMethodDefineSet, - fix: (node) => { - const startPosition = node.startPosition; - const endPosition = node.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); -} - -function validateClassBody( - node: arkts.ClassDeclaration, - computedGetters: Map, - context: UISyntaxRuleContext -): void { - const observedV2Decorator = node.definition?.annotations.find(annotation => - getAnnotationName(annotation) === PresetDecorators.OBSERVED_V2 - ); - node.definition?.body.forEach((member) => { - if (arkts.isMethodDefinition(member)) { - validateComputedInClass(node, member, observedV2Decorator, context); - if (!arkts.isIdentifier(member.name)) { - return; + private validateGetterSetterConflict(): void { + for (const [name] of this.computedGetters) { + if (this.computedSetters.has(name)) { + this.reportValidateGetterSetterConflict(name); } - const methodName = getIdentifierName(member.name); - const computedDecorator = member.scriptFunction.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.COMPUTED - ); - validateComputedMethodKind(member, computedDecorator, methodName, computedGetters, context); - } - }); -} - -function validateComponentV2InStruct( - node: arkts.StructDeclaration, - context: UISyntaxRuleContext -): void { - const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - const componentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); - node.definition?.body.forEach((member) => { - if (arkts.isMethodDefinition(member)) { - checkComponentV2InStruct(node, member, componentV2Decorator, componentDecorator, context); } - }); -} + } -function checkComponentV2InStruct( - node: arkts.StructDeclaration | arkts.ClassDeclaration, - member: arkts.MethodDefinition, - componentV2Decorator: arkts.AnnotationUsage | undefined, - componentDecorator: arkts.AnnotationUsage | undefined, - context: UISyntaxRuleContext -): void { - const computedDecorator = member.scriptFunction.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.COMPUTED - ); - if (computedDecorator && !componentV2Decorator && !componentDecorator) { - context.report({ - node: computedDecorator, - message: rule.messages.componentV2InStruct, - fix: () => { + private reportValidateGetterSetterConflict(name: string): void { + const setter = this.computedSetters.get(name)!; + this.report({ + node: setter, + message: this.messages.computedMethodDefineSet, + fix: (node) => { const startPosition = node.startPosition; - const endPosition = node.startPosition; + const endPosition = node.endPosition; return { range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V2}\n`, + code: '', }; }, }); } - if (computedDecorator && !componentV2Decorator && componentDecorator) { - context.report({ - node: computedDecorator, - message: rule.messages.componentV2InStruct, - fix: () => { - const startPosition = componentDecorator.startPosition; - const endPosition = componentDecorator.endPosition; - return { - range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V2}`, - }; - }, + + private validateClassBody(node: arkts.ClassDeclaration): void { + const observedV2Decorator = node.definition?.annotations.find(annotation => + getAnnotationName(annotation) === PresetDecorators.OBSERVED_V2 + ); + + node.definition?.body.forEach((member) => { + if (arkts.isMethodDefinition(member)) { + + this.validateComputedInClass(node, member, observedV2Decorator); + const computedDecorator = member.scriptFunction.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.COMPUTED + ); + if (!arkts.isIdentifier(member.name)) { + return; + } + const methodName = getIdentifierName(member.name); + + this.validateComputedMethodKind(member, computedDecorator, methodName); + } }); } -} -function validateComputedInClass( - node: arkts.AstNode, - member: arkts.MethodDefinition, - observedV2Decorator: arkts.AnnotationUsage | undefined, - context: UISyntaxRuleContext -): void { - const computedDecorator = member.scriptFunction.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.COMPUTED - ); - if (computedDecorator && !observedV2Decorator && - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET === member.kind) { - context.report({ - node: computedDecorator, - message: rule.messages.onlyInObservedV2, - fix: () => { - const startPosition = node.startPosition; - return { - range: [startPosition, startPosition], - code: `@${PresetDecorators.OBSERVED_V2}\n`, - }; - }, + private validateComponentV2InStruct(node: arkts.StructDeclaration): void { + const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + const componentDecorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); + + node.definition?.body.forEach((member) => { + if (arkts.isMethodDefinition(member)) { + this.checkComponentV2InStruct(node, member, componentV2Decorator, componentDecorator); + } }); } + + private checkComponentV2InStruct( + node: arkts.StructDeclaration | arkts.ClassDeclaration, + member: arkts.MethodDefinition, + componentV2Decorator: arkts.AnnotationUsage | undefined, + componentDecorator: arkts.AnnotationUsage | undefined + ): void { + const computedDecorator = member.scriptFunction.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.COMPUTED + ); + if (computedDecorator && !componentV2Decorator && !componentDecorator) { + this.report({ + node: computedDecorator, + message: this.messages.componentV2InStruct, + fix: () => { + const startPosition = node.startPosition; + const endPosition = node.startPosition; + return { + range: [startPosition, endPosition], + code: `@${PresetDecorators.COMPONENT_V2}\n`, + }; + }, + }); + } + + if (computedDecorator && !componentV2Decorator && componentDecorator) { + this.report({ + node: computedDecorator, + message: this.messages.componentV2InStruct, + fix: () => { + const startPosition = componentDecorator.startPosition; + const endPosition = componentDecorator.endPosition; + return { + range: [startPosition, endPosition], + code: `@${PresetDecorators.COMPONENT_V2}`, + }; + }, + }); + } + } + + private validateComputedInClass( + node: arkts.AstNode, + member: arkts.MethodDefinition, + observedV2Decorator: arkts.AnnotationUsage | undefined, + ): void { + const computedDecorator = member.scriptFunction.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.COMPUTED + ); + if (computedDecorator && !observedV2Decorator && + arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET === member.kind) { + this.report({ + node: computedDecorator, + message: this.messages.onlyInObservedV2, + fix: () => { + const startPosition = node.startPosition; + return { + range: [startPosition, startPosition], + code: `@${PresetDecorators.OBSERVED_V2}\n`, + }; + }, + }); + } + } } -const rule: UISyntaxRule = { - name: 'computed-decorator-check', - messages: { - onlyOnGetter: `@Computed can only decorate 'GetAccessor'.`, - onlyInObservedV2: `The '@Computed' can decorate only member method within a 'class' decorated with ObservedV2.`, - componentV2InStruct: `The '@Computed' decorator can only be used in a 'struct' decorated with ComponentV2.`, - noTwoWayBinding: `A property decorated by '@Computed' cannot be used with two-way bind syntax.`, - computedMethodDefineSet: `A property decorated by '@Computed' cannot define a set method.` - }, - setup(context) { - const computedGetters = new Map(); - const computedSetters = new Map(); - return { - parsed: (node): void => { - if (arkts.isStructDeclaration(node)) { - validateComponentV2InStruct(node, context); - validateStructBody(node, computedGetters, computedSetters, context); - } - if (arkts.isClassDeclaration(node)) { - validateClassBody(node, computedGetters, context); - } - }, - }; - }, -}; +export default ComputedDecoratorCheckRule; -export default rule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/construct-parameter.ts b/arkui-plugins/ui-syntax-plugins/rules/construct-parameter.ts index 7327b9097..50ff52aa7 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/construct-parameter.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/construct-parameter.ts @@ -21,7 +21,7 @@ import { PresetDecorators, getAnnotationName, } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; // When a specific decorator is used as a parameter, the assigned decorator is not allowed const disallowAssignedDecorators: string[] = [ @@ -46,277 +46,265 @@ const decoratorsFilter: string[] = [ PresetDecorators.LOCAL_STORAGE_LINK, PresetDecorators.LOCAL_STORAGE_PROP, PresetDecorators.BUILDER_PARAM, ]; -function getPropertyAnnotationName(node: arkts.AstNode, propertyName: string): string { - while (!arkts.isStructDeclaration(node)) { - node = node.parent; +class ConstructParameterRule extends AbstractUISyntaxRule { + private propertyMap: Map> = new Map(); + private regularVariableList: string[] = []; + private builderFunctionList: string[] = []; + + public setup(): Record { + return { + constructParameter: `The '{{initializer}}' property '{{initializerName}}' cannot be assigned to the '{{parameter}}' property '{{parameterName}}'.`, + initializerIsBuilder: `'@Builder' function '{{initializerName}}' can only initialize '@BuilderParam' attribute.`, + parameterIsBuilderParam: `'@BuilderParam' attribute '{{parameterName}}' can only initialized by '@Builder' function or '@Builder' method in struct.`, + }; } - let annotationNames: string[] = []; - node.definition.body.forEach((item) => { - if (arkts.isClassProperty(item) && getClassPropertyName(item) === propertyName) { - annotationNames = getClassPropertyAnnotationNames(item); + public parsed(node: arkts.StructDeclaration): void { + this.initList(node); + this.initPropertyMap(node); + this.checkConstructParameter(node); + } + + private getPropertyAnnotationName(node: arkts.AstNode, propertyName: string): string { + while (!arkts.isStructDeclaration(node)) { + if (!node.parent) { + return ''; + } + node = node.parent; } - if (arkts.isMethodDefinition(item) && getIdentifierName(item.name) === propertyName) { - annotationNames = item.scriptFunction.annotations.map((annotation) => - getAnnotationName(annotation) - ); + let annotationNames: string[] = []; + node.definition.body.forEach((item) => { + if (arkts.isClassProperty(item) && getClassPropertyName(item) === propertyName) { + annotationNames = getClassPropertyAnnotationNames(item); + } + if (arkts.isMethodDefinition(item) && getIdentifierName(item.name) === propertyName) { + annotationNames = item.scriptFunction.annotations.map((annotation) => + getAnnotationName(annotation) + ); + } + }); + if (annotationNames.length === 0) { + return PresetDecorators.REGULAR; } - }); - if (annotationNames.length === 0) { - return PresetDecorators.REGULAR; - } - const annotationName = annotationNames.find((item) => { return decoratorsFilter.includes(item) }); - if (annotationName) { - return annotationName; + const annotationName = annotationNames.find((item) => { return decoratorsFilter.includes(item) }); + if (annotationName) { + return annotationName; + } + return ''; } - return ''; -} -// Define a function to add property data to the property map -function addProperty( - structName: string, - propertyName: string, - annotationName: string, - propertyMap: Map> -): void { - if (!propertyMap.has(structName)) { - propertyMap.set(structName, new Map()); - } - const structProperties = propertyMap.get(structName); - if (!structProperties) { - return; + // Define a function to add property data to the property map + private addProperty( + structName: string, + propertyName: string, + annotationName: string + ): void { + if (!this.propertyMap.has(structName)) { + this.propertyMap.set(structName, new Map()); + } + const structProperties = this.propertyMap.get(structName); + if (!structProperties) { + return; + } + structProperties.set(propertyName, annotationName); } - structProperties.set(propertyName, annotationName); -} -function collectBuilderFunctions(member: arkts.AstNode, builderFunctionList: string[]): void { - if (!arkts.isFunctionDeclaration(member) || !member.annotations) { - return; - } - member.annotations.forEach(annotation => { - if (annotation.expr && getIdentifierName(annotation.expr) === PresetDecorators.BUILDER && - member.scriptFunction.id) { - builderFunctionList.push(member.scriptFunction.id.name); + private collectBuilderFunctions(member: arkts.AstNode): void { + if (!arkts.isFunctionDeclaration(member) || !member.annotations) { + return; } - }); -} - -function collectRegularVariables(member: arkts.AstNode, regularVariableList: string[]): void { - if (!arkts.isVariableDeclaration(member) || !member.declarators) { - return; + member.annotations.forEach(annotation => { + if (annotation.expr && getIdentifierName(annotation.expr) === PresetDecorators.BUILDER && + member.scriptFunction.id) { + this.builderFunctionList.push(member.scriptFunction.id.name); + } + }); } - member.getChildren().forEach((item) => { - if (!arkts.isVariableDeclarator(item) || !item.name || - (item.initializer && arkts.isArrowFunctionExpression(item.initializer))) { + + private collectRegularVariables(member: arkts.AstNode): void { + if (!arkts.isVariableDeclaration(member) || !member.declarators) { return; } - regularVariableList.push(item.name.name); - }); -} - -function initList(node: arkts.AstNode, regularVariableList: string[], builderFunctionList: string[]): void { - // Record variables and functions that @builder decorate - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; + member.getChildren().forEach((item) => { + if (!arkts.isVariableDeclarator(item) || !item.name || + (item.initializer && arkts.isArrowFunctionExpression(item.initializer))) { + return; + } + this.regularVariableList.push(item.name.name); + }); } - node.getChildren().forEach((member) => { - collectBuilderFunctions(member, builderFunctionList); - collectRegularVariables(member, regularVariableList); - }); -} -function recordRestrictedDecorators( - item: arkts.AstNode, - structName: string, - propertyMap: Map> -): void { - if (!arkts.isClassProperty(item) || !item.key) { - return; - } - let propertyName: string = getIdentifierName(item.key); - // If there is no decorator, it is a regular type - if (item.annotations.length === 0) { - let annotationName: string = PresetDecorators.REGULAR; - addProperty(structName, propertyName, annotationName, propertyMap); - } - // Iterate through the decorator of the property, and when the decorator is in the disallowAssignedDecorators array, the property is recorded - item.annotations.forEach((annotation) => { - if (!annotation.expr) { + private initList(node: arkts.AstNode): void { + // Record variables and functions that @builder decorate + if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { return; } - let annotationName: string = getIdentifierName(annotation.expr); - if (disallowAssignedDecorators.includes(annotationName)) { - addProperty(structName, propertyName, annotationName, propertyMap); - } - }); -} - -function initPropertyMap(node: arkts.AstNode, propertyMap: Map>): void { - // Iterate through the root node ahead of time, noting the structure name, variable name, and corresponding decorator type - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; + node.getChildren().forEach((member) => { + this.collectBuilderFunctions(member); + this.collectRegularVariables(member); + }); } - node.getChildren().forEach((member) => { - if (!arkts.isStructDeclaration(member) || !member.definition.ident) { + + private recordRestrictedDecorators( + item: arkts.AstNode, + structName: string, + ): void { + if (!arkts.isClassProperty(item) || !item.key) { return; } - let structName: string = member.definition.ident?.name ?? ''; - if (structName === '') { - return; + let propertyName: string = getIdentifierName(item.key); + // If there is no decorator, it is a regular type + if (item.annotations.length === 0) { + let annotationName: string = PresetDecorators.REGULAR; + this.addProperty(structName, propertyName, annotationName); } - member.definition?.body?.forEach((item) => { - recordRestrictedDecorators(item, structName, propertyMap); + // Iterate through the decorator of the property, and when the decorator is in the disallowAssignedDecorators array, the property is recorded + item.annotations.forEach((annotation) => { + if (!annotation.expr) { + return; + } + let annotationName: string = getIdentifierName(annotation.expr); + if (disallowAssignedDecorators.includes(annotationName)) { + this.addProperty(structName, propertyName, annotationName); + } }); - }); -} - -function reportRegularVariableError( - property: arkts.AstNode, - context: UISyntaxRuleContext, - childType: string, - childName: string, - regularVariableList: string[] -): void { - if (!arkts.isProperty(property) || !property.value) { - return; - } - if (childType !== PresetDecorators.LINK) { - return; } - if (arkts.isIdentifier(property.value) && regularVariableList.includes(property.value.name)) { - context.report({ - node: property, - message: rule.messages.constructParameter, - data: { - initializer: PresetDecorators.REGULAR, - initializerName: property.value.name, - parameter: `@${childType}`, - parameterName: childName, - }, + + private initPropertyMap(node: arkts.AstNode): void { + // Iterate through the root node ahead of time, noting the structure name, variable name, and corresponding decorator type + if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + return; + } + node.getChildren().forEach((member) => { + if (!arkts.isStructDeclaration(member) || !member.definition.ident) { + return; + } + let structName: string = member.definition.ident?.name ?? ''; + if (structName === '') { + return; + } + member.definition?.body?.forEach((item) => { + this.recordRestrictedDecorators(item, structName); + }); }); } -} -function reportBuilderError( - property: arkts.AstNode, - context: UISyntaxRuleContext, - childType: string, - childName: string, - builderFunctionList: string[], -): void { - if (!arkts.isProperty(property) || !property.value) { - return; - } - let isBuilderInStruct: boolean = false; - if (arkts.isMemberExpression(property.value) && arkts.isIdentifier(property.value.property)) { - const parentName = property.value.property.name; - const parentType: string = getPropertyAnnotationName(property, parentName); - if (parentType === '') { + private reportRegularVariableError( + property: arkts.AstNode, + childType: string, + childName: string, + ): void { + if (!arkts.isProperty(property) || !property.value) { return; } - isBuilderInStruct = parentType === PresetDecorators.BUILDER; - } - let isBuilder: boolean = false; - if (arkts.isIdentifier(property.value)) { - isBuilder = builderFunctionList.includes(property.value.name); - if (builderFunctionList.includes(property.value.name) && childType !== PresetDecorators.BUILDER_PARAM) { - context.report({ + if (childType !== PresetDecorators.LINK) { + return; + } + if (arkts.isIdentifier(property.value) && this.regularVariableList.includes(property.value.name)) { + this.context.report({ node: property, - message: rule.messages.initializerIsBuilder, + message: this.messages.constructParameter, data: { + initializer: PresetDecorators.REGULAR, initializerName: property.value.name, + parameter: `@${childType}`, parameterName: childName, }, }); } } - if (childType === PresetDecorators.BUILDER_PARAM && !isBuilder && !isBuilderInStruct) { - context.report({ - node: property, - message: rule.messages.parameterIsBuilderParam, - data: { - parameterName: childName, - }, - }); - } -} -function checkConstructParameter( - node: arkts.AstNode, - context: UISyntaxRuleContext, - propertyMap: Map>, - regularVariableList: string[], - builderFunctionList: string[], -): void { - if (!arkts.isIdentifier(node) || !propertyMap.has(getIdentifierName(node))) { - return; - } - let structName: string = getIdentifierName(node); - if (!node.parent) { - return; - } - let parentNode: arkts.AstNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; - } - // Gets all the properties recorded by the struct - const childPropertyName: Map = propertyMap.get(structName)!; - parentNode.arguments.forEach((member) => { - member.getChildren().forEach((property) => { - if (!arkts.isProperty(property) || !property.key) { - return; - } - const childName = getIdentifierName(property.key); - if (!childPropertyName.has(childName) || !property.value) { - return; - } - const childType: string = childPropertyName.get(childName)!; - reportRegularVariableError(property, context, childType, childName, regularVariableList); - reportBuilderError(property, context, childType, childName, builderFunctionList); - if (!arkts.isMemberExpression(property.value) || !arkts.isThisExpression(property.value.object)) { - return; - } - const parentName = getIdentifierName(property.value.property); - const parentType: string = getPropertyAnnotationName(node, parentName); + private reportBuilderError( + property: arkts.AstNode, + childType: string, + childName: string, + ): void { + if (!arkts.isProperty(property) || !property.value) { + return; + } + let isBuilderInStruct: boolean = false; + if (arkts.isMemberExpression(property.value) && arkts.isIdentifier(property.value.property)) { + const parentName = property.value.property.name; + const parentType: string = this.getPropertyAnnotationName(property, parentName); if (parentType === '') { return; } - if (restrictedDecoratorInitializations.has(parentType) && - restrictedDecoratorInitializations.get(parentType)!.includes(childType)) { - context.report({ + isBuilderInStruct = parentType === PresetDecorators.BUILDER; + } + let isBuilder: boolean = false; + if (arkts.isIdentifier(property.value)) { + isBuilder = this.builderFunctionList.includes(property.value.name); + if (this.builderFunctionList.includes(property.value.name) && childType !== PresetDecorators.BUILDER_PARAM) { + this.context.report({ node: property, - message: rule.messages.constructParameter, + message: this.messages.initializerIsBuilder, data: { - initializer: parentType === PresetDecorators.REGULAR ? PresetDecorators.REGULAR : `@${parentType}`, - initializerName: parentName, - parameter: childType === PresetDecorators.REGULAR ? PresetDecorators.REGULAR : `@${childType}`, + initializerName: property.value.name, parameterName: childName, }, }); } + } + if (childType === PresetDecorators.BUILDER_PARAM && !isBuilder && !isBuilderInStruct) { + this.context.report({ + node: property, + message: this.messages.parameterIsBuilderParam, + data: { + parameterName: childName, + }, + }); + } + } + + private checkConstructParameter(node: arkts.AstNode): void { + if (!arkts.isIdentifier(node) || !this.propertyMap.has(getIdentifierName(node))) { + return; + } + let structName: string = getIdentifierName(node); + if (!node.parent) { + return; + } + let parentNode: arkts.AstNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; + } + // Gets all the properties recorded by the struct + const childPropertyName: Map = this.propertyMap.get(structName)!; + parentNode.arguments.forEach((member) => { + member.getChildren().forEach((property) => { + if (!arkts.isProperty(property) || !property.key) { + return; + } + const childName = getIdentifierName(property.key); + if (!childPropertyName.has(childName) || !property.value) { + return; + } + const childType: string = childPropertyName.get(childName)!; + this.reportRegularVariableError(property, childType, childName); + this.reportBuilderError(property, childType, childName); + if (!arkts.isMemberExpression(property.value) || !arkts.isThisExpression(property.value.object)) { + return; + } + const parentName = getIdentifierName(property.value.property); + const parentType: string = this.getPropertyAnnotationName(node, parentName); + if (parentType === '') { + return; + } + if (restrictedDecoratorInitializations.has(parentType) && + restrictedDecoratorInitializations.get(parentType)!.includes(childType)) { + this.context.report({ + node: property, + message: this.messages.constructParameter, + data: { + initializer: parentType === PresetDecorators.REGULAR ? PresetDecorators.REGULAR : `@${parentType}`, + initializerName: parentName, + parameter: childType === PresetDecorators.REGULAR ? PresetDecorators.REGULAR : `@${childType}`, + parameterName: childName, + }, + }); + } + }); }); - }); + } } -const rule: UISyntaxRule = { - name: 'construct-parameter', - messages: { - constructParameter: `The '{{initializer}}' property '{{initializerName}}' cannot be assigned to the '{{parameter}}' property '{{parameterName}}'.`, - initializerIsBuilder: `'@Builder' function '{{initializerName}}' can only initialize '@BuilderParam' attribute.`, - parameterIsBuilderParam: `'@BuilderParam' attribute '{{parameterName}}' can only initialized by '@Builder' function or '@Builder' method in struct.`, - }, - setup(context) { - let propertyMap: Map> = new Map(); - let regularVariableList: string[] = []; - let builderFunctionList: string[] = []; - - return { - parsed: (node): void => { - initList(node, regularVariableList, builderFunctionList); - initPropertyMap(node, propertyMap); - checkConstructParameter(node, context, propertyMap, regularVariableList, builderFunctionList); - }, - }; - }, -}; - -export default rule; \ No newline at end of file +export default ConstructParameterRule; 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 058c215e8..9c86d806e 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 @@ -14,208 +14,179 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getAnnotationUsage, getIdentifierName, MultiMap, PresetDecorators, getAnnotationName } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { getIdentifierName, MultiMap, PresetDecorators, getAnnotationName } from '../utils'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -// Traverse the member variables of the struct, recording the members of the @Consumer or @Provider modifications -function processStructMembers( - node: arkts.StructDeclaration, - structName: string, - componentv2WithConsumer: MultiMap, - componentv2WithProvider: MultiMap -): void { - 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 comsumerDecorator = member?.annotations.some(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.CONSUMER - ); - const providerDecorator = member?.annotations.some(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.PROVIDER - ); - if (!member?.key) { - return; - } - const memberName = getIdentifierName(member?.key); - if (comsumerDecorator && structName && memberName) { - componentv2WithConsumer.add(structName, memberName); - } +class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { + private componentv2WithConsumer: MultiMap = new MultiMap(); + private componentv2WithProvider: MultiMap = new MultiMap(); + + public setup(): Record { + return { + consumerOnlyOnMember: `'@{{decorator}}' can only decorate member property.`, + multipleBuiltInDecorators: `The struct member variable can not be decorated by multiple built-in decorators.`, + 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).`, + }; + } - if (providerDecorator && structName && memberName) { - componentv2WithProvider.add(structName, memberName); + public parsed(node: arkts.AstNode): void { + this.collectStructsWithConsumerAndProvider(node); + this.validateStructDecoratorsAndMembers(node); + this.validateInClass(node); + + if (arkts.isCallExpression(node)) { + this.validateConsumerInitialization(node); + this.validateProviderInitialization(node); + } + } + + private collectStructsWithConsumerAndProvider(node: arkts.AstNode): void { + if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + // Breadth traversal is done through while and queues + const queue: Array = [node]; + while (queue.length > 0) { + const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; + // Filter and record the nodes of the tree + this.rememberStructName(currentNode); + const children = currentNode.getChildren(); + for (const child of children) { + queue.push(child); + } } } - }); -} + } -function rememberStructName( - node: arkts.AstNode, - componentv2WithConsumer: MultiMap, - componentv2WithProvider: MultiMap, -): void { - // First it has to be of the struct type - if (arkts.isStructDeclaration(node)) { - node?.definition?.annotations.forEach((anno) => { - if (!anno.expr) { - return; - } - const annoName = getIdentifierName(anno.expr); - // Second, it must be decorated with a @component v2 decorator - if (annoName === PresetDecorators.COMPONENT_V2) { - const structName = node.definition.ident?.name ?? ''; - processStructMembers(node, structName, componentv2WithConsumer, componentv2WithProvider); - } - }); + private rememberStructName(node: arkts.AstNode): void { + if (arkts.isStructDeclaration(node)) { + node?.definition?.annotations.forEach((anno) => { + if (!anno.expr) { + return; + } + const annoName = getIdentifierName(anno.expr); + // Second, it must be decorated with a @component v2 decorator + if (annoName === PresetDecorators.COMPONENT_V2) { + const structName = node.definition.ident?.name ?? ''; + this.processStructMembers(node, structName); + } + }); + } } -} -function findDecorator(member: arkts.ClassProperty, decorator: string): arkts.AnnotationUsage | undefined { - return member.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === decorator - ); -} + private processStructMembers(node: arkts.StructDeclaration, structName: string): void { + 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 comsumerDecorator = member?.annotations.some(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.CONSUMER + ); + const providerDecorator = member?.annotations.some(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.PROVIDER + ); + if (!member?.key) { + return; + } + const memberName = getIdentifierName(member?.key); + if (comsumerDecorator && structName && memberName) { + this.componentv2WithConsumer.add(structName, memberName); + } -function findOtherDecorator(member: arkts.ClassProperty, decorator: string): arkts.AnnotationUsage | undefined { - return member.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name !== decorator - ); -} + if (providerDecorator && structName && memberName) { + this.componentv2WithProvider.add(structName, memberName); + } + } + }); + } -function findDecoratorInMethod(member: arkts.MethodDefinition, decorator: string): arkts.AnnotationUsage | undefined { - return member.scriptFunction.annotations?.find(annotation => - annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === decorator - ); -} + private validateStructDecoratorsAndMembers(node: arkts.AstNode): void { + if (arkts.isStructDeclaration(node)) { + node.definition.body.forEach(member => { + if (arkts.isMethodDefinition(member)) { + this.validateDecoratorOnMethod(member); + } + if (arkts.isClassProperty(member)) { + this.validateMemberDecorators(member); + } + }); + } + } -// Verify that the @Consumer decorator is used on the method -function validateDecoratorOnMethod(member: arkts.MethodDefinition, context: UISyntaxRuleContext): void { - validateDecorator(member, PresetDecorators.CONSUMER, context); - validateDecorator(member, PresetDecorators.PROVIDER, context); -} + private validateMemberDecorators( + member: arkts.ClassProperty, + ): void { + // Check that the @Consumer is not mixed with other decorators + this.validateMultipleBuiltInDecorators(member, PresetDecorators.CONSUMER); -function validateDecorator( - member: arkts.MethodDefinition, - decoratorName: string, - context: UISyntaxRuleContext, -): void { - const decorator = findDecoratorInMethod(member, decoratorName); - if (!decorator) { - return; + // Check that the @Provider is mixed with other decorators + this.validateMultipleBuiltInDecorators(member, PresetDecorators.PROVIDER); } - context.report({ - node: decorator, - message: rule.messages.consumerOnlyOnMember, - data: { - decorator: getAnnotationName(decorator), - }, - fix: (decorator) => { - const startPosition = decorator.startPosition; - const endPosition = decorator.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; + private validateMultipleBuiltInDecorators(member: arkts.ClassProperty, decoratorName: string): void { + const decorator = this.findDecorator(member, decoratorName); + const otherDecorators = this.findOtherDecorator(member, decoratorName); + if (!decorator || !otherDecorators) { + return; } - }); -} - -// @Consumer Bugs that conflict with other decorators -function validateMultipleBuiltInDecorators( - member: arkts.ClassProperty, - decorateName: string, - context: UISyntaxRuleContext, -): void { + this.report({ + node: decorator, + message: this.messages.multipleBuiltInDecorators, + data: { + decorator: getAnnotationName(decorator) + }, + fix: () => { + const startPosition = otherDecorators.startPosition; + const endPosition = otherDecorators.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + } + }); + } - const decorator = findDecorator(member, decorateName); - const otherDecorators = findOtherDecorator(member, decorateName); - if (!decorator || !otherDecorators) { - return; + private findDecorator(member: arkts.ClassProperty, decoratorName: string): arkts.AnnotationUsage | undefined { + return member.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === decoratorName + ); } - context.report({ - node: decorator, - message: rule.messages.multipleBuiltInDecorators, - data: { - decorator: getAnnotationName(decorator) - }, - fix: (decorator) => { - const startPosition = otherDecorators.startPosition; - const endPosition = otherDecorators.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - } - }); -} -// Verify decorator conflicts on member variables -function validateMemberDecorators( - member: arkts.ClassProperty, - componentV2Decorator: arkts.AnnotationUsage | undefined, - componentDecorator: arkts.AnnotationUsage | undefined, - node: arkts.AstNode, - context: UISyntaxRuleContext -): void { - // Check that the @Consumer is not mixed with other decorators - validateMultipleBuiltInDecorators(member, PresetDecorators.CONSUMER, context); + private findOtherDecorator(member: arkts.ClassProperty, decoratorName: string): arkts.AnnotationUsage | undefined { + return member.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name !== decoratorName + ); + } - // Check that the @Provider is mixed with other decorators - validateMultipleBuiltInDecorators(member, PresetDecorators.PROVIDER, context); -} + private findDecoratorInMethod(member: arkts.MethodDefinition, decoratorName: string): arkts.AnnotationUsage | undefined { + return member.scriptFunction.annotations?.find(annotation => + annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === decoratorName + ); + } -// Verify that @Provider is being used in the class -function validateDecoratorInClass( - member: arkts.ClassProperty, - decoratorName: string, - context: UISyntaxRuleContext -): void { - const decorator = findDecorator(member, decoratorName); - if (!decorator) { - return; + private validateDecoratorOnMethod(member: arkts.MethodDefinition): void { + this.validateDecorator(member, PresetDecorators.CONSUMER); + this.validateDecorator(member, PresetDecorators.PROVIDER); } - context.report({ - node: decorator, - message: rule.messages.providerOnlyInStruct, - data: { - decorator: getAnnotationName(decorator), - }, - fix: (providerDecorator) => { - const startPosition = providerDecorator.startPosition; - const endPosition = providerDecorator.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; + + private validateDecorator(member: arkts.MethodDefinition, decoratorName: string): void { + const decorator = this.findDecoratorInMethod(member, decoratorName); + if (!decorator) { + return; } - }); -} -// Verify that the current identifier is an illegally initialized @Consumer member variable -function checkInvalidConsumerUsage( - currentNode: arkts.Identifier, - callExpName: string, - componentv2WithConsumer: MultiMap, - context: UISyntaxRuleContext -): void { - const parent = currentNode.parent; - if (parent && componentv2WithConsumer.get(callExpName).includes(getIdentifierName(currentNode))) { - context.report({ - node: parent, - message: rule.messages.forbiddenInitialization, + this.report({ + node: decorator, + message: this.messages.consumerOnlyOnMember, data: { - decorator: PresetDecorators.CONSUMER, - value: getIdentifierName(currentNode), - structName: callExpName + decorator: getAnnotationName(decorator), }, - fix: () => { - const startPosition = parent.startPosition; - const endPosition = parent.endPosition; + fix: (decorator) => { + const startPosition = decorator.startPosition; + const endPosition = decorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -223,27 +194,32 @@ function checkInvalidConsumerUsage( } }); } -} -function checkInvalidProviderUsage( - currentNode: arkts.Identifier, - callExpName: string, - componentv2WithProvider: MultiMap, - context: UISyntaxRuleContext -): void { - const parent = currentNode.parent; - if (parent && componentv2WithProvider.get(callExpName)?.includes(getIdentifierName(currentNode))) { - context.report({ - node: parent, - message: rule.messages.forbiddenInitialization, + private validateInClass(node: arkts.AstNode): void { + if (arkts.isClassDeclaration(node)) { + node.definition?.body.forEach(member => { + if (arkts.isClassProperty(member)) { + this.validateDecoratorInClass(member, PresetDecorators.CONSUMER); + this.validateDecoratorInClass(member, PresetDecorators.PROVIDER); + } + }); + } + } + + private validateDecoratorInClass(member: arkts.ClassProperty, decoratorName: string): void { + const decorator = this.findDecorator(member, decoratorName); + if (!decorator) { + return; + } + this.report({ + node: decorator, + message: this.messages.providerOnlyInStruct, data: { - decorator: PresetDecorators.PROVIDER, - value: getIdentifierName(currentNode), - structName: callExpName + decorator: getAnnotationName(decorator), }, - fix: () => { - const startPosition = parent.startPosition; - const endPosition = parent.endPosition; + fix: (providerDecorator) => { + const startPosition = providerDecorator.startPosition; + const endPosition = providerDecorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -251,122 +227,92 @@ function checkInvalidProviderUsage( } }); } -} -// Verify that the @Consumer-decorated property is initialized -function validateConsumerInitialization(node: arkts.CallExpression, componentv2WithConsumer: MultiMap, - context: UISyntaxRuleContext): void { - if (!arkts.isIdentifier(node.expression)) { - return; - } - const callExpName: string = node.expression.name; - if (componentv2WithConsumer.has(callExpName)) { - const queue: Array = [node]; - while (queue.length > 0) { - const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; - if (arkts.isIdentifier(currentNode)) { - checkInvalidConsumerUsage(currentNode, callExpName, componentv2WithConsumer, context); - } - const children = currentNode.getChildren(); - for (const child of children) { - queue.push(child); - } + private validateConsumerInitialization(node: arkts.CallExpression): void { + if (!arkts.isIdentifier(node.expression)) { + return; } - } -} - -function validateProviderInitialization(node: arkts.CallExpression, componentv2WithProvider: MultiMap, - context: UISyntaxRuleContext): void { - if (!arkts.isIdentifier(node.expression)) { - return; - } - const callExpName: string = node.expression.name; - if (componentv2WithProvider.has(callExpName)) { - const queue: Array = [node]; - while (queue.length > 0) { - const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; - if (arkts.isIdentifier(currentNode)) { - checkInvalidProviderUsage(currentNode, callExpName, componentv2WithProvider, context); - } - const children = currentNode.getChildren(); - for (const child of children) { - queue.push(child); + const callExpName: string = node.expression.name; + if (this.componentv2WithConsumer.has(callExpName)) { + const queue: Array = [node]; + while (queue.length > 0) { + const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; + if (arkts.isIdentifier(currentNode)) { + this.checkInvalidConsumerUsage(currentNode, callExpName); + } + const children = currentNode.getChildren(); + for (const child of children) { + queue.push(child); + } } } } -} -function collectStructsWithConsumerAndProvider( - node: arkts.AstNode, - componentv2WithConsumer: MultiMap, - componentv2WithProvider: MultiMap, -): void { - // Used to document all V2 structs that use '@Consumer' - if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - // Breadth traversal is done through while and queues - const queue: Array = [node]; - while (queue.length > 0) { - const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; - // Filter and record the nodes of the tree - rememberStructName(currentNode, componentv2WithConsumer, componentv2WithProvider); - const children = currentNode.getChildren(); - for (const child of children) { - queue.push(child); + private validateProviderInitialization(node: arkts.CallExpression): void { + if (!arkts.isIdentifier(node.expression)) { + return; + } + const callExpName: string = node.expression.name; + if (this.componentv2WithProvider.has(callExpName)) { + const queue: Array = [node]; + while (queue.length > 0) { + const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; + if (arkts.isIdentifier(currentNode)) { + this.checkInvalidProviderUsage(currentNode, callExpName); + } + const children = currentNode.getChildren(); + for (const child of children) { + queue.push(child); + } } } } -} -function validateStructDecoratorsAndMembers(node: arkts.AstNode, context: UISyntaxRuleContext): void { - if (arkts.isStructDeclaration(node)) { - 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, componentV2Decorator, componentDecorator, node, context); - } - }); + private checkInvalidConsumerUsage(currentNode: arkts.Identifier, callExpName: string): void { + const parent = currentNode.parent; + if (parent && this.componentv2WithConsumer.get(callExpName).includes(getIdentifierName(currentNode))) { + this.report({ + node: parent, + message: this.messages.forbiddenInitialization, + data: { + decorator: PresetDecorators.CONSUMER, + value: getIdentifierName(currentNode), + structName: callExpName + }, + fix: () => { + const startPosition = parent.startPosition; + const endPosition = parent.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + } + }); + } } -} -function validateInClass(node: arkts.AstNode, context: UISyntaxRuleContext): void { - if (arkts.isClassDeclaration(node)) { - node.definition?.body.forEach(member => { - if (arkts.isClassProperty(member)) { - validateDecoratorInClass(member, PresetDecorators.CONSUMER, context); - validateDecoratorInClass(member, PresetDecorators.PROVIDER, context); - } - }); + private checkInvalidProviderUsage(currentNode: arkts.Identifier, callExpName: string): void { + const parent = currentNode.parent; + if (parent && this.componentv2WithProvider.get(callExpName)?.includes(getIdentifierName(currentNode))) { + this.report({ + node: parent, + message: this.messages.forbiddenInitialization, + data: { + decorator: PresetDecorators.PROVIDER, + value: getIdentifierName(currentNode), + structName: callExpName + }, + fix: () => { + const startPosition = parent.startPosition; + const endPosition = parent.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + } + }); + } } } -const rule: UISyntaxRule = { - name: 'consumer-provider-decorator-check', - messages: { - consumerOnlyOnMember: `'@{{decorator}}' can only decorate member property.`, - multipleBuiltInDecorators: `The struct member variable can not be decorated by multiple built-in decorators.`, - 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).`, - }, - setup(context) { - // Used to record the names of the corresponding structs and member variables that are @consumer modified - let componentv2WithConsumer: MultiMap = new MultiMap(); - let componentv2WithProvider: MultiMap = new MultiMap(); - return { - parsed: (node): void => { - collectStructsWithConsumerAndProvider(node, componentv2WithConsumer, componentv2WithProvider); - validateStructDecoratorsAndMembers(node, context); - validateInClass(node, context); - if (arkts.isCallExpression(node)) { - validateConsumerInitialization(node, componentv2WithConsumer, context); - validateProviderInitialization(node, componentv2WithProvider, context); - } - }, - }; - }, -}; - -export default rule; +export default ConsumerProviderDecoratorCheckRule; \ No newline at end of file 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 f175c218a..d35cf99f2 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 @@ -15,83 +15,90 @@ import * as arkts from '@koalaui/libarkts'; import { getClassPropertyType, PresetDecorators, getAnnotationUsage, isClassPropertyOptional } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } 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; +class CustomDialogMissingControllerRule extends AbstractUISyntaxRule { + public setup(): Record { + return { + missingController: `The @CustomDialog decorated custom component must contain a property of the CustomDialogController type.`, + }; } - if (!property.typeAnnotation || !arkts.isETSUnionType(property.typeAnnotation)) { - return false; + + public parsed(node: arkts.AstNode): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + this.checkMissingController(node); } - for (const type of property.typeAnnotation.types) { - if (!arkts.isETSTypeReference(type)) { - continue; + + // Check if the @CustomDialog-decorated struct contains a property of type CustomDialogController + private checkMissingController(node: arkts.StructDeclaration): void { + const customDialogDecorator = getAnnotationUsage(node, PresetDecorators.CUSTOM_DIALOG); + + if (!customDialogDecorator) { + return; } - const part = type.part; - if (!part || !arkts.isETSTypeReferencePart(part)) { - continue; + const structName = node.definition.ident; + if (!structName) { + return; } - const name = part.name; - if (name && arkts.isIdentifier(name) && name.name === CUSTOM_DIALOG_CONTROLLER) { - return true; + let hasControllerProperty = false; + + node.definition.body.forEach((property) => { + if (arkts.isClassProperty(property)) { + // Check if it's a union type, such as CustomDialogController | undefined + if (this.hasCustomDialogControllerInUnion(property)) { + hasControllerProperty = true; + return; + } + + // Check if it's directly of the CustomDialogController type + const propertyType = getClassPropertyType(property); + if (propertyType === CUSTOM_DIALOG_CONTROLLER) { + hasControllerProperty = true; + } + } + }); + + if (!hasControllerProperty) { + this.report({ + node: structName, + message: this.messages.missingController, + }); } } - return false; -} -function missingController( - node: arkts.StructDeclaration, - context: UISyntaxRuleContext -): void { - // Check for the @CustomDialog decorator - const customDialogDecorator = getAnnotationUsage(node, PresetDecorators.CUSTOM_DIALOG); - const structName = node.definition.ident; - if (!structName) { - return; - } - // Check if there is an attribute of type CustomDialogController in the class - 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; + // Check that the property is of a form that contains a CustomDialogController in the union type (for example: CustomDialogController | undefined) + private hasCustomDialogControllerInUnion(property: arkts.ClassProperty): 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; } } - }); - if (!hasControllerProperty && customDialogDecorator) { - context.report({ - node: structName, - message: rule.messages.missingController, - }); + return false; } } -const rule: UISyntaxRule = { - name: 'custom-dialog-missing-controller', - messages: { - missingController: `The @CustomDialog decorated custom component must contain a property of the CustomDialogController type.`, - }, - setup(context) { - return { - parsed: (node): void => { - if (!arkts.isStructDeclaration(node)) { - return; - } - missingController(node, context); - }, - }; - }, -}; - -export default rule; - +export default CustomDialogMissingControllerRule; \ 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 92b50232d..c7c4f7cf4 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/index.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/index.ts @@ -18,27 +18,28 @@ import AttributeNoInvoke from './attribute-no-invoke'; import BuilderparamDecoratorCheck from './builderparam-decorator-check'; import BuildRootNodeRule from './build-root-node'; import CheckConstructPrivateParameter from './check-construct-private-parameter'; -import CheckDecoratedPropertyType from './check-decorated-property-type'; -import ComponentComponentV2MixUseCheck from './component-componentV2-mix-use-check'; -import ComponentV2MixCheck from './componentV2-mix-check'; +import CheckDecoratedPropertyTypeRule from './check-decorated-property-type'; +import ComponentComponentV2MixUseCheckRule from './component-componentV2-mix-use-check'; +import ComponentV2MixCheckRule from './componentV2-mix-check'; import ConstructParameterLiteral from './construct-parameter-literal'; -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 ConstructParameterRule from './construct-parameter'; +import ConsumerProviderDecoratorCheckRule from './consumer-provider-decorator-check'; +import ComputedDecoratorCheckRule from './computed-decorator-check'; +import ComponentV2StateUsageValidationRule from './componentV2-state-usage-validation'; +import CustomDialogMissingControllerRule from './custom-dialog-missing-controller'; import EntryLoacalStorageCheck from './entry-localstorage-check'; import EntryStructNoExport from './entry-struct-no-export'; -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 MonitorDecoratorCheckRule from './monitor-decorator-check'; +import NestedRelationshipRule from './nested-relationship'; +import NestedReuseComponentCheckRule from './nested-reuse-component-check'; +import NoChildInButtonRule from './no-child-in-button'; import NoDuplicateDecorators from './no-duplicate-decorators'; -import NoDuplicateEntry from './no-duplicate-entry'; +import NoDuplicateEntryRule from './no-duplicate-entry'; import NoDuplicateId from './no-duplicate-id'; -import NoDuplicatePreview from './no-duplicate-preview'; -import NoDuplicateStateManager from './no-duplicate-state-manager'; +import NoDuplicatePreviewRule from './no-duplicate-preview'; +import NoDuplicateStateManagerRule from './no-duplicate-state-manager'; import NoPropLinkObjectlinkInEntry from './no-prop-link-objectlink-in-entry'; -import NoSameAsBuiltInAttribute from './no-same-as-built-in-attribute'; +import NoSameAsBuiltInAttributeRule from './no-same-as-built-in-attribute'; import ReuseAttributeCheck from './reuse-attribute-check'; import StructMissingDecorator from './struct-missing-decorator'; import StructPropertyDecorator from './struct-property-decorator'; @@ -47,21 +48,21 @@ 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 WatchDecoratorFunction from './watch-decorator-function'; import WatchDecoratorRegular from './watch-decorator-regular'; import WrapBuilderCheck from './wrap-builder-check'; -import ObservedHeritageCompatibleCheck from './observed-heritage-compatible-check'; -import ObservedObservedV2 from './observed-observedV2-check'; +import ObservedHeritageCompatibleCheckRule from './observed-heritage-compatible-check'; +import ObservedObservedV2Rule from './observed-observedV2-check'; import ObservedV2TraceUsageValidation from './observedV2-trace-usage-validation'; import OnceDecoratorCheck from './once-decorator-check'; import OneDecoratorOnFunctionMethod from './one-decorator-on-function-method'; import OldNewDecoratorMixUseCheck from './old-new-decorator-mix-use-check'; -import ComputedDecoratorCheck from './computed-decorator-check'; import ReusableV2DecoratorCheck from './reusableV2-decorator-check'; import RequireDecoratorRegular from './require-decorator-regular'; import ReusableComponentInV2Check from './reusable-component-in-V2-check'; import VariableInitializationViaComponentConstructor from './variable-initialization-via-component-constructor'; -import ComponentComponentV2InitCheck from './component-componentV2-init-check'; +import ComponentComponentV2InitCheckRule from './component-componentV2-init-check'; import SpecificComponentChildren from './specific-component-children'; import StructNoExtends from './struct-no-extends'; @@ -70,27 +71,27 @@ const rules: Array = [ BuilderparamDecoratorCheck, [BuildRootNodeRule, 'error'], CheckConstructPrivateParameter, - CheckDecoratedPropertyType, - ComponentComponentV2MixUseCheck, - ComponentV2MixCheck, + [CheckDecoratedPropertyTypeRule, 'error'], + [ComponentComponentV2MixUseCheckRule, 'error'], + [ComponentV2MixCheckRule, 'error'], ConstructParameterLiteral, - ConstructParameter, - ConsumerProviderDecoratorCheck, - ComponentV2StateUsageValidation, - CustomDialogMissingController, + [ConstructParameterRule, 'error'], + [ConsumerProviderDecoratorCheckRule, 'error'], + [ComponentV2StateUsageValidationRule, 'error'], + [CustomDialogMissingControllerRule, 'error'], EntryLoacalStorageCheck, EntryStructNoExport, - MonitorDecoratorCheck, - NestedRelationship, - NestedReuseComponentCheck, - NoChildInButton, + [MonitorDecoratorCheckRule, 'error'], + [NestedRelationshipRule, 'error'], + [NestedReuseComponentCheckRule, 'error'], + [NoChildInButtonRule, 'error'], NoDuplicateDecorators, - NoDuplicateEntry, + [NoDuplicateEntryRule, 'error'], NoDuplicateId, - NoDuplicatePreview, - NoDuplicateStateManager, + [NoDuplicatePreviewRule, 'error'], + [NoDuplicateStateManagerRule, 'error'], NoPropLinkObjectlinkInEntry, - NoSameAsBuiltInAttribute, + [NoSameAsBuiltInAttributeRule, 'error'], ReuseAttributeCheck, StructMissingDecorator, StructPropertyDecorator, @@ -99,21 +100,22 @@ const rules: Array = [ TrackDecoratorCheck, TypeDecoratorCheck, ValidateBuildInStruct, + ValidateDecoratorTarget, WatchDecoratorFunction, WatchDecoratorRegular, WrapBuilderCheck, - ObservedHeritageCompatibleCheck, - ObservedObservedV2, + [ObservedHeritageCompatibleCheckRule, 'error'], + [ObservedObservedV2Rule, 'error'], ObservedV2TraceUsageValidation, OnceDecoratorCheck, OneDecoratorOnFunctionMethod, OldNewDecoratorMixUseCheck, - ComputedDecoratorCheck, + [ComputedDecoratorCheckRule, 'error'], ReusableV2DecoratorCheck, RequireDecoratorRegular, ReusableComponentInV2Check, VariableInitializationViaComponentConstructor, - ComponentComponentV2InitCheck, + [ComponentComponentV2InitCheckRule, 'error'], SpecificComponentChildren, StructNoExtends, ]; diff --git a/arkui-plugins/ui-syntax-plugins/rules/monitor-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/monitor-decorator-check.ts index 1b08c9b6a..d1d95c461 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/monitor-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/monitor-decorator-check.ts @@ -14,194 +14,202 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { PresetDecorators } from '../utils'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; const MONITOR_COUNT_LIMIT = 1; -// Function declarations moved to the top with explicit return types -function getLocalMonitorUsed(body: arkts.MethodDefinition): arkts.AnnotationUsage | undefined { - const localMonitorUsed = body.scriptFunction.annotations?.find( - annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.MONITOR - ); - return localMonitorUsed; -} +class MonitorDecoratorCheckRule extends AbstractUISyntaxRule { + public setup(): Record { + return { + monitorUsedAlone: + `The member property or method can not be decorated by multiple built-in decorators.`, + monitorUsedInObservedV2Class: + `The '@Monitor' can decorate only member method within a 'class' decorated with @ObservedV2.`, + monitorUsedInComponentV2Struct: + `The '@Monitor' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`, + monitorDecorateMethod: + `@Monitor can only decorate method.`, + duplicatedMonitor: `Duplicate decorators for method are not allowed.`, + }; + } -function getMonitor(body: arkts.MethodDefinition): arkts.AnnotationUsage[] { - const monitor = body.scriptFunction.annotations?.filter( - annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.MONITOR - ); - return monitor; -} + public parsed(node: arkts.AstNode): void { + if (!arkts.isClassDeclaration(node) && !arkts.isStructDeclaration(node)) { + return; + } -function checkConflictingDecorators(context: UISyntaxRuleContext, body: arkts.MethodDefinition, - localMonitorUsed: arkts.AnnotationUsage): boolean { - const conflictingDecorators = body.scriptFunction.annotations?.filter( - annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name !== PresetDecorators.MONITOR - ); - if (conflictingDecorators?.length > 0) { - reportConflictingDecorators(context, localMonitorUsed, conflictingDecorators); - return true; - } - return false; -} + let monitorUsed = false; + + const isObservedV2 = arkts.isClassDeclaration(node) && this.checkIfClassIsObservedV2(node); + const isComponentV2 = arkts.isStructDeclaration(node) && this.checkIfStructIsComponentV2(node); -function reportConflictingDecorators(context: UISyntaxRuleContext, localMonitorUsed: arkts.AstNode, - conflictingDecorators: arkts.AnnotationUsage[]): void { - context.report({ - node: localMonitorUsed, - message: rule.messages.monitorUsedAlone, - fix: () => { - const startPositions = conflictingDecorators.map(annotation => - annotation.startPosition); - const endPositions = conflictingDecorators.map(annotation => annotation.endPosition); - const startPosition = startPositions[0]; - const endPosition = endPositions[endPositions.length - 1]; - return { - range: [startPosition, endPosition], - code: '' - }; + monitorUsed = this.checkMultipleDecorators(node); + + // Check for errors related to @Monitor usage + if (monitorUsed && !isObservedV2 && arkts.isClassDeclaration(node)) { + this.reportInvalidUsage( + node, + this.messages.monitorUsedInObservedV2Class, + `@${PresetDecorators.OBSERVED_V2}` + ); } - }); -} + if (monitorUsed && !isComponentV2 && arkts.isStructDeclaration(node)) { + this.reportInvalidUsage( + node, + this.messages.monitorUsedInComponentV2Struct, + `@${PresetDecorators.COMPONENT_V2}\n` + ); + } + this.checkDecorateMethod(node); + } + private checkIfClassIsObservedV2(node: arkts.ClassDeclaration): boolean { + return node.definition?.annotations?.some( + observedV2 => observedV2.expr && arkts.isIdentifier(observedV2.expr) && + observedV2.expr?.name === PresetDecorators.OBSERVED_V2 + ) ?? false; -function checkIfClassIsObservedV2(node: arkts.ClassDeclaration): boolean { - return node.definition?.annotations?.some( - observedV2 => observedV2.expr && arkts.isIdentifier(observedV2.expr) && - observedV2.expr?.name === PresetDecorators.OBSERVED_V2 - ) ?? false; -} + } -function checkIfStructIsComponentV2(node: arkts.StructDeclaration): boolean { - return node.definition.annotations?.some( - componentV2 => componentV2.expr && arkts.isIdentifier(componentV2.expr) && - componentV2.expr?.name === PresetDecorators.COMPONENT_V2 - ) ?? false; -} + private checkIfStructIsComponentV2(node: arkts.StructDeclaration): boolean { + return node.definition.annotations?.some( + componentV2 => componentV2.expr && arkts.isIdentifier(componentV2.expr) && + componentV2.expr?.name === PresetDecorators.COMPONENT_V2 + ) ?? false; + } -function reportInvalidUsage(context: UISyntaxRuleContext, node: arkts.AstNode, message: string, fixCode: string) - : void { - const startPosition = node.startPosition; - context.report({ - node, - message, - fix: () => ({ - range: [startPosition, startPosition], - code: fixCode, - }), - }); -} + private checkMultipleDecorators( + node: arkts.ClassDeclaration | arkts.StructDeclaration + ): boolean { + // Traverse body of the class to check for @Monitor usage + let monitorUsed: boolean = false; + + node.definition?.body.forEach((body) => { + if (!arkts.isMethodDefinition(body)) { + return; + } + + const duplicatedMonitor = this.getMonitor(body); + const localMonitorUsed = this.getLocalMonitorUsed(body); -function checkMultipleDecorators( - node: arkts.ClassDeclaration | arkts.StructDeclaration, - context: UISyntaxRuleContext -): boolean { - // Traverse body of the class to check for @Monitor usage - let monitorUsed: boolean = false; - node.definition?.body.forEach(body => { - if (arkts.isMethodDefinition(body)) { - const duplicatedMonitor = getMonitor(body); - const localMonitorUsed = getLocalMonitorUsed(body); if (duplicatedMonitor.length > MONITOR_COUNT_LIMIT && localMonitorUsed) { - reportDuplicatedMonitor(context, localMonitorUsed); + this.reportDuplicatedMonitor(localMonitorUsed); } + if (localMonitorUsed) { monitorUsed = true; - checkConflictingDecorators(context, body, localMonitorUsed); + this.checkConflictingDecorators(body, localMonitorUsed); return; // Stop further checks for this method } } - }); - return monitorUsed; -} + ); -function reportDuplicatedMonitor(context: UISyntaxRuleContext, localMonitorUsed: arkts.AstNode,): void { - context.report({ - node: localMonitorUsed, - message: rule.messages.duplicatedMonitor, - fix: () => { - const startPosition = localMonitorUsed.startPosition; - const endPosition = localMonitorUsed.endPosition; - return { - range: [startPosition, endPosition], - code: '' - }; - } - }); -} + return monitorUsed; + } -function checkDecorateMethod( - node: arkts.ClassDeclaration | arkts.StructDeclaration, - context: UISyntaxRuleContext -): void { - // Check if @Monitor is used on a property (which is not allowed) - node.definition?.body.forEach(body => { - if (!arkts.isClassProperty(body)) { - return; - } - const monitorDecorator = body.annotations?.find( + private getMonitor(body: arkts.MethodDefinition): arkts.AnnotationUsage[] { + const monitor = body.scriptFunction.annotations?.filter( annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && - annotation.expr.name === PresetDecorators.MONITOR); - if (monitorDecorator === undefined) { - return; + annotation.expr.name === PresetDecorators.MONITOR + ); + return monitor; + } + + private getLocalMonitorUsed(body: arkts.MethodDefinition): arkts.AnnotationUsage | undefined { + const localMonitorUsed = body.scriptFunction.annotations?.find( + annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.MONITOR + ); + return localMonitorUsed; + } + + private checkConflictingDecorators(body: arkts.MethodDefinition, localMonitorUsed: arkts.AnnotationUsage): boolean { + const conflictingDecorators = body.scriptFunction.annotations?.filter( + annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && + annotation.expr.name !== PresetDecorators.MONITOR + ); + if (conflictingDecorators?.length > 0) { + this.reportConflictingDecorators(localMonitorUsed, conflictingDecorators); + return true; } - context.report({ - node: monitorDecorator, - message: rule.messages.monitorDecorateMethod, + return false; + } + + private reportConflictingDecorators(localMonitorUsed: arkts.AstNode, conflictingDecorators: arkts.AnnotationUsage[]): void { + this.report({ + node: localMonitorUsed, + message: this.messages.monitorUsedAlone, fix: () => { - const startPosition = monitorDecorator.startPosition; - const endPosition = monitorDecorator.endPosition; + const startPositions = conflictingDecorators.map(annotation => + annotation.startPosition); + const endPositions = conflictingDecorators.map(annotation => annotation.endPosition); + const startPosition = startPositions[0]; + const endPosition = endPositions[endPositions.length - 1]; return { range: [startPosition, endPosition], - code: '', + code: '' }; - }, + } }); - }); -} + } -// The rule object with its setup method -const rule: UISyntaxRule = { - name: 'monitor-decorator-check', - messages: { - monitorUsedAlone: - `The member property or method can not be decorated by multiple built-in decorators.`, - monitorUsedInObservedV2Class: - `The '@Monitor' can decorate only member method within a 'class' decorated with @ObservedV2.`, - monitorUsedInComponentV2Struct: - `The '@Monitor' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`, - monitorDecorateMethod: - `@Monitor can only decorate method.`, - duplicatedMonitor: `Duplicate decorators for method are not allowed.`, - }, - setup(context) { - return { - parsed: (node: arkts.AstNode): void => { - if (!arkts.isClassDeclaration(node) && !arkts.isStructDeclaration(node)) { - return; - } - let monitorUsed = false; - - const isObservedV2 = arkts.isClassDeclaration(node) && checkIfClassIsObservedV2(node); - const isComponentV2 = arkts.isStructDeclaration(node) && checkIfStructIsComponentV2(node); - - monitorUsed = checkMultipleDecorators(node, context); - - // Check for errors related to @Monitor usage - if (monitorUsed && !isObservedV2 && arkts.isClassDeclaration(node)) { - reportInvalidUsage(context, node, rule.messages.monitorUsedInObservedV2Class, `@${PresetDecorators.OBSERVED_V2}`); - } - if (monitorUsed && !isComponentV2 && arkts.isStructDeclaration(node)) { - reportInvalidUsage(context, node, rule.messages.monitorUsedInComponentV2Struct, `@${PresetDecorators.COMPONENT_V2}\n`); - } - - checkDecorateMethod(node, context); - }, - }; - }, -}; + private reportDuplicatedMonitor(localMonitorUsed: arkts.AstNode): void { + this.report({ + node: localMonitorUsed, + message: this.messages.duplicatedMonitor, + fix: () => { + const startPosition = localMonitorUsed.startPosition; + const endPosition = localMonitorUsed.endPosition; + return { + range: [startPosition, endPosition], + code: '' + }; + } + }); + } + + private reportInvalidUsage(node: arkts.AstNode, message: string, fixCode: string): void { + this.report({ + node, + message, + fix: () => ({ + range: [node.startPosition, node.startPosition], + code: fixCode + }) + }); + } + + private checkDecorateMethod(node: arkts.ClassDeclaration | arkts.StructDeclaration): void { + // Check if @Monitor is used on a property (which is not allowed) + node.definition?.body.forEach((body) => { + if (!arkts.isClassProperty(body)) { + return; + } + + const monitorDecorator = body.annotations?.find( + (annotation) => + annotation.expr && + arkts.isIdentifier(annotation.expr) && + annotation.expr.name === PresetDecorators.MONITOR + ); + + if (monitorDecorator === undefined) { + return; + } + this.report({ + node: monitorDecorator, + message: this.messages.monitorDecorateMethod, + fix: () => { + const startPosition = monitorDecorator.startPosition; + const endPosition = monitorDecorator.endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); + }); + } +} -export default rule; +export default MonitorDecoratorCheckRule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/nested-relationship.ts b/arkui-plugins/ui-syntax-plugins/rules/nested-relationship.ts index 18f8273e0..69027a616 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/nested-relationship.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/nested-relationship.ts @@ -22,166 +22,163 @@ import { listToString, SINGLE_CHILD_COMPONENT } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -function checkValidParentComponent(node: arkts.AstNode, context: UISyntaxRuleContext,): void { - // Check if the node is an identifier and if there are any restrictions on the parent component - if (!arkts.isIdentifier(node)) { - return; +class NestedRelationshipRule extends AbstractUISyntaxRule { + public setup(): Record { + return { + noChildComponent: `No child component is allowed in the '{{componentName}}' component.`, + singleChildComponent: `The '{{componentName}}' component can have only one child component.`, + delegateChildrenComponentChildren: `The '{{childComponentName}}' component cannot be a child component of the '{{parentComponentName}}' component.`, + delegateChildrenComponentParent: `The component '{{parentComponentName}}' can only have the child component {{childComponentList}}.`, + delegateParentComponent: `The '{{componentName}}' component can only be nested in the {{parentComponentList}} parent component.`, + }; } - const componentName: string = getIdentifierName(node); - if (!context.componentsInfo.validParentComponent.has(componentName) || !node.parent || !node.parent.parent) { - return; + public parsed(node: arkts.StructDeclaration): void { + this.checkValidParentComponent(node); + this.checkValidChildComponent(node); + this.checkSingleChildComponent(node); + this.checkNoChildComponent(node); } - let curNode = node.parent.parent; - while (!arkts.isCallExpression(curNode) || !arkts.isIdentifier(curNode.expression) || - !isBuildInComponent(context, curNode.expression.name)) { - if (!curNode.parent) { + + private checkValidParentComponent(node: arkts.AstNode): void { + // Check if the node is an identifier and if there are any restrictions on the parent component + if (!arkts.isIdentifier(node)) { return; } - curNode = curNode.parent; - } - // If the parent component of the current component is not within the valid range, an error is reported - const parentComponentName = curNode.expression.name; - if (!context.componentsInfo.validParentComponent.get(componentName)!.includes(parentComponentName)) { - context.report({ - node: node, - message: rule.messages.delegateChildrenComponentChildren, - data: { - childComponentName: componentName, - parentComponentName: parentComponentName, - }, - }); - const parentComponentListArray: string[] = context.componentsInfo.validParentComponent.get(componentName)!; - const parentComponentList: string = listToString(parentComponentListArray); - context.report({ - node: node, - message: rule.messages.delegateParentComponent, - data: { - componentName: componentName, - parentComponentList: parentComponentList, - }, - }); + const componentName: string = getIdentifierName(node); + if (!this.context.componentsInfo.validParentComponent.has(componentName) || !node.parent || !node.parent.parent) { + return; + } + let curNode = node.parent.parent; + while (!arkts.isCallExpression(curNode) || !arkts.isIdentifier(curNode.expression) || + !isBuildInComponent(this.context, curNode.expression.name)) { + if (!curNode.parent) { + return; + } + curNode = curNode.parent; + } + // If the parent component of the current component is not within the valid range, an error is reported + const parentComponentName = curNode.expression.name; + if (!this.context.componentsInfo.validParentComponent.get(componentName)!.includes(parentComponentName)) { + this.context.report({ + node: node, + message: this.messages.delegateChildrenComponentChildren, + data: { + childComponentName: componentName, + parentComponentName: parentComponentName, + }, + }); + const parentComponentListArray: string[] = this.context.componentsInfo.validParentComponent.get(componentName)!; + const parentComponentList: string = listToString(parentComponentListArray); + this.report({ + node: node, + message: this.messages.delegateParentComponent, + data: { + componentName: componentName, + parentComponentList: parentComponentList, + }, + }); + } } -} -function checkValidChildComponent(node: arkts.AstNode, context: UISyntaxRuleContext): void { - // Check whether the node is an identifier and whether there are restrictions on subcomponents - if (!arkts.isIdentifier(node)) { - return; - } - const parentComponentName: string = getIdentifierName(node); - if (!context.componentsInfo.validChildComponent.has(parentComponentName) || !node.parent) { - return; - } - let parentNode = node.parent; - if (!arkts.isCallExpression(parentNode) || !arkts.isIdentifier(parentNode.expression)) { - return; - } - // If the BlockStatement contains a child component that should not exist under the component, an error will be reported - parentNode.getChildren().forEach(member => { - if (!arkts.isBlockStatement(member)) { + private checkValidChildComponent(node: arkts.AstNode): void { + // Check whether the node is an identifier and whether there are restrictions on subcomponents + if (!arkts.isIdentifier(node)) { + return; + } + const parentComponentName: string = getIdentifierName(node); + if (!this.context.componentsInfo.validChildComponent.has(parentComponentName) || !node.parent) { return; } - member.statements.forEach(statement => { - if (!arkts.isExpressionStatement(statement) || !statement.expression) { + let parentNode = node.parent; + if (!arkts.isCallExpression(parentNode) || !arkts.isIdentifier(parentNode.expression)) { + return; + } + // If the BlockStatement contains a child component that should not exist under the component, an error will be reported + parentNode.getChildren().forEach(member => { + if (!arkts.isBlockStatement(member)) { return; } - if (!arkts.isCallExpression(statement.expression) || !statement.expression.expression || - !arkts.isIdentifier(statement.expression.expression)) { + member.statements.forEach(statement => { + if (!arkts.isExpressionStatement(statement) || !statement.expression) { + return; + } + if (!arkts.isCallExpression(statement.expression) || !statement.expression.expression || + !arkts.isIdentifier(statement.expression.expression)) { + return; + } + const componentName = getIdentifierName(statement.expression.expression); + const childComponentListArray: string[] = this.context.componentsInfo.validChildComponent.get(parentComponentName)!; + if (!childComponentListArray.includes(componentName) && isBuildInComponent(this.context, componentName)) { + const childComponentList: string = listToString(childComponentListArray); + this.report({ + node: parentNode, + message: this.messages.delegateChildrenComponentParent, + data: { + parentComponentName: parentComponentName, + childComponentList: childComponentList, + }, + }); + } + }); + }); + } + private checkSingleChildComponent(node: arkts.AstNode): void { + // Check whether the current node is an identifier and a single subcomponent container + if (!arkts.isIdentifier(node)) { + return; + } + const componentName: string = getIdentifierName(node); + if (!isSingleChildComponent(this.context, componentName) || !node.parent) { + return; + } + const parentNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; + } + // If there is more than one subcomponent in the BlockStatement, an error is reported + parentNode.getChildren().forEach(member => { + if (!arkts.isBlockStatement(member)) { return; } - const componentName = getIdentifierName(statement.expression.expression); - const childComponentListArray: string[] = context.componentsInfo.validChildComponent.get(parentComponentName)!; - if (!childComponentListArray.includes(componentName) && isBuildInComponent(context, componentName)) { - const childComponentList: string = listToString(childComponentListArray); - context.report({ - node: parentNode, - message: rule.messages.delegateChildrenComponentParent, - data: { - parentComponentName: parentComponentName, - childComponentList: childComponentList, - }, + if (member.statements.length > SINGLE_CHILD_COMPONENT) { + this.report({ + node: node, + message: this.messages.singleChildComponent, + data: { componentName: componentName } }); } }); - }); -} -function checkSingleChildComponent(node: arkts.AstNode, context: UISyntaxRuleContext): void { - // Check whether the current node is an identifier and a single subcomponent container - if (!arkts.isIdentifier(node)) { - return; - } - const componentName: string = getIdentifierName(node); - if (!isSingleChildComponent(context, componentName) || !node.parent) { - return; - } - const parentNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; } - // If there is more than one subcomponent in the BlockStatement, an error is reported - parentNode.getChildren().forEach(member => { - if (!arkts.isBlockStatement(member)) { + + private checkNoChildComponent(node: arkts.AstNode): void { + // Check whether the current node is an identifier and an atomic component + if (!arkts.isIdentifier(node)) { return; } - if (member.statements.length > SINGLE_CHILD_COMPONENT) { - context.report({ - node: node, - message: rule.messages.singleChildComponent, - data: { componentName: componentName } - }); - } - }); -} - -function checkNoChildComponent(node: arkts.AstNode, context: UISyntaxRuleContext): void { - // Check whether the current node is an identifier and an atomic component - if (!arkts.isIdentifier(node)) { - return; - } - const componentName: string = getIdentifierName(node); - if (!isAtomicComponent(context, componentName) || !node.parent) { - return; - } - let parentNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { - return; - } - // If there are child components in arguments, an error will be reported - parentNode.getChildren().forEach(member => { - if (!arkts.isBlockStatement(member)) { + const componentName: string = getIdentifierName(node); + if (!isAtomicComponent(this.context, componentName) || !node.parent) { return; } - if (member.statements.length > 0) { - context.report({ - node: node, - message: rule.messages.noChildComponent, - data: { componentName: componentName } - }); + let parentNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; } - }); + // If there are child components in arguments, an error will be reported + parentNode.getChildren().forEach(member => { + if (!arkts.isBlockStatement(member)) { + return; + } + if (member.statements.length > 0) { + this.report({ + node: node, + message: this.messages.noChildComponent, + data: { componentName: componentName } + }); + } + }); + } } -const rule: UISyntaxRule = { - name: 'nested-relationship', - messages: { - noChildComponent: `No child component is allowed in the '{{componentName}}' component.`, - singleChildComponent: `The '{{componentName}}' component can have only one child component.`, - delegateChildrenComponentChildren: `The '{{childComponentName}}' component cannot be a child component of the '{{parentComponentName}}' component.`, - delegateChildrenComponentParent: `The component '{{parentComponentName}}' can only have the child component {{childComponentList}}.`, - delegateParentComponent: `The '{{componentName}}' component can only be nested in the {{parentComponentList}} parent component.`, - }, - setup(context) { - return { - parsed: (node): void => { - checkValidParentComponent(node, context); - checkValidChildComponent(node, context); - checkSingleChildComponent(node, context); - checkNoChildComponent(node, context); - }, - }; - }, -}; - -export default rule; \ No newline at end of file +export default NestedRelationshipRule; \ No newline at end of file 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 a734b040e..7814aec46 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 @@ -15,192 +15,177 @@ import * as arkts from '@koalaui/libarkts'; import { COMPONENT_REPEAT, getIdentifierName, PresetDecorators, TEMPLATE } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -function initStructName(node: arkts.AstNode, reusableV2StructName: string[], reusableStructName: string[]): void { - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; +class NestedReuseComponentCheckRule extends AbstractUISyntaxRule { + private reusableV2StructName: string[] = []; + private reusableStructName: string[] = []; + + public setup(): Record { + return { + noReusableV2InComponentV1: `A custom component decorated with @Component cannot contain child components decorated with @ReusableV2.`, + noReusableV2InReusableV1: `A custom component decorated with @Reusable cannot contain child components decorated with @ReusableV2.`, + noReusableV1InReusableV2: `A custom component decorated with @ReusableV2 cannot contain child components decorated with @Reusable.`, + noReusableV2InRepeatTemplate: `The template attribute of the Repeat component cannot contain any custom component decorated with @ReusableV2.`, + }; } - //Go through all the children of Program - for (const childNode of node.getChildren()) { - // Check whether the type is struct - if (!arkts.isStructDeclaration(childNode)) { + public parsed(node: arkts.StructDeclaration): void { + this.initStructName(node); + this.checkNestedReuseComponent(node); + this.checkNoReusableV1InReusableV2(node); + } + + private initStructName(node: arkts.AstNode): void { + if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { return; } - // Get a list of annotations - const annotationsList = childNode.definition.annotations; - // Check that the current component has @Reusable and @ReusableV2 decorators - if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V2)) { - const struceName = childNode.definition?.ident?.name || ''; - reusableV2StructName.push(struceName); - } else if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V1)) { - const struceName = childNode.definition?.ident?.name || ''; - reusableStructName.push(struceName); + //Go through all the children of Program + for (const childNode of node.getChildren()) { + // Check whether the type is struct + if (!arkts.isStructDeclaration(childNode)) { + return; + } + // Get a list of annotations + const annotationsList = childNode.definition.annotations; + // Check that the current component has @Reusable and @ReusableV2 decorators + if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V2)) { + const struceName = childNode.definition?.ident?.name || ''; + this.reusableV2StructName.push(struceName); + } else if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V1)) { + const struceName = childNode.definition?.ident?.name || ''; + this.reusableStructName.push(struceName); + } } } -} -function reportNoReusableV2InRepeatTemplate(errorNode: arkts.AstNode, context: UISyntaxRuleContext): void { - context.report({ - node: errorNode, - message: rule.messages.noReusableV2InRepeatTemplate, - fix: (errorNode) => { - return { - range: [errorNode.startPosition, errorNode.endPosition], - code: '', - }; - } - }); -} + private reportNoReusableV2InRepeatTemplate(errorNode: arkts.AstNode): void { + this.report({ + node: errorNode, + message: this.messages.noReusableV2InRepeatTemplate, + fix: (errorNode) => { + return { + range: [errorNode.startPosition, errorNode.endPosition], + code: '', + }; + } + }); + } -function checkHasRepeatOrTemplate(node: arkts.CallExpression): { hasRepeat: boolean, hasTemplate: boolean } { - let hasRepeat: boolean = false; - let hasTemplate: boolean = false; - while (arkts.isCallExpression(node) && - node.expression && arkts.isMemberExpression(node.expression) && - node.expression.object && arkts.isCallExpression(node.expression.object)) { - if (arkts.isIdentifier(node.expression.property) && getIdentifierName(node.expression.property) === TEMPLATE) { - hasTemplate = true; + private checkHasRepeatOrTemplate(node: arkts.CallExpression): { hasRepeat: boolean, hasTemplate: boolean } { + let hasRepeat: boolean = false; + let hasTemplate: boolean = false; + while (arkts.isCallExpression(node) && + node.expression && arkts.isMemberExpression(node.expression) && + node.expression.object && arkts.isCallExpression(node.expression.object)) { + if (arkts.isIdentifier(node.expression.property) && getIdentifierName(node.expression.property) === TEMPLATE) { + hasTemplate = true; + } + node = node.expression.object; } - node = node.expression.object; - } - if (arkts.isCallExpression(node) && arkts.isIdentifier(node.expression)) { - hasRepeat = getIdentifierName(node.expression) === COMPONENT_REPEAT; + if (arkts.isCallExpression(node) && arkts.isIdentifier(node.expression)) { + hasRepeat = getIdentifierName(node.expression) === COMPONENT_REPEAT; + } + return { hasRepeat, hasTemplate }; } - return { hasRepeat, hasTemplate }; -} -function checkNoReusableV2InRepeatTemplate( - node: arkts.AstNode, - errorNode: arkts.AstNode, - context: UISyntaxRuleContext -): boolean { - if (!arkts.isCallExpression(node)) { - return false; + private checkNoReusableV2InRepeatTemplate(node: arkts.AstNode, errorNode: arkts.AstNode): boolean { + if (!arkts.isCallExpression(node)) { + return false; + } + const flag = this.checkHasRepeatOrTemplate(node); + if (!flag.hasRepeat || !flag.hasTemplate) { + return false; + } + this.reportNoReusableV2InRepeatTemplate(errorNode); + return true; } - const flag = checkHasRepeatOrTemplate(node); - if (!flag.hasRepeat || !flag.hasTemplate) { - return false; + + private reportNoReusableV1InReusableV2(node: arkts.AstNode): void { + this.report({ + node: node, + message: this.messages.noReusableV1InReusableV2, + fix: (node) => { + return { + range: [node.startPosition, node.endPosition], + code: '', + }; + } + }); } - reportNoReusableV2InRepeatTemplate(errorNode, context); - return true; -} -function reportNoReusableV1InReusableV2(node: arkts.AstNode, context: UISyntaxRuleContext): void { - context.report({ - node: node, - message: rule.messages.noReusableV1InReusableV2, - fix: (node) => { - return { - range: [node.startPosition, node.endPosition], - code: '', - }; + private checkNoReusableV1InReusableV2(node: arkts.AstNode): void { + if (!arkts.isCallExpression(node) || !arkts.isIdentifier(node.expression)) { + return; } - }); -} - -function checkNoReusableV1InReusableV2( - node: arkts.AstNode, - context: UISyntaxRuleContext, - reusableStructName: string[] -): void { - if (!arkts.isCallExpression(node) || !arkts.isIdentifier(node.expression)) { - return; - } - if (reusableStructName.includes(node.expression.name)) { - // Traverse upwards to find the custom component. - let struceNode: arkts.AstNode = node; - while (!arkts.isStructDeclaration(struceNode)) { - if (!struceNode.parent) { - return; + if (this.reusableStructName.includes(node.expression.name)) { + // 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; + // Check that the current component is decorated by the @ComponentV2 decorator + if (annotationsList.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V2)) { + this.reportNoReusableV1InReusableV2(node); } - struceNode = struceNode.parent; - } - const annotationsList = struceNode.definition.annotations; - // Check that the current component is decorated by the @ComponentV2 decorator - if (annotationsList.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V2)) { - reportNoReusableV1InReusableV2(node, context); } } -} - -function reportNoReusableV2InReusableV1(node: arkts.AstNode, context: UISyntaxRuleContext): void { - context.report({ - node: node, - message: rule.messages.noReusableV2InReusableV1, - fix: (node) => { - return { - range: [node.startPosition, node.endPosition], - code: '', - }; - } - }); -} -function reportNoReusableV2InComponentV1(node: arkts.AstNode, context: UISyntaxRuleContext): void { - context.report({ - node: node, - message: rule.messages.noReusableV2InComponentV1, - fix: (node) => { - return { - range: [node.startPosition, node.endPosition], - code: '', - }; - } - }); -} + private reportNoReusableV2InReusableV1(node: arkts.AstNode): void { + this.report({ + node: node, + message: this.messages.noReusableV2InReusableV1, + fix: (node) => { + return { + range: [node.startPosition, node.endPosition], + code: '', + }; + } + }); + } -function checkNestedReuseComponent( - node: arkts.AstNode, - context: UISyntaxRuleContext, - reusableV2StructName: string[], -): void { - if (!arkts.isCallExpression(node) || !arkts.isIdentifier(node.expression)) { - return; + private reportNoReusableV2InComponentV1(node: arkts.AstNode): void { + this.report({ + node: node, + message: this.messages.noReusableV2InComponentV1, + fix: (node) => { + return { + range: [node.startPosition, node.endPosition], + code: '', + }; + } + }); } - if (reusableV2StructName.includes(node.expression.name)) { - // Traverse upwards to find the custom component. - let struceNode: arkts.AstNode = node; - let hasReportedError = false; - while (!arkts.isStructDeclaration(struceNode)) { - if (!struceNode.parent) { - return; + + private checkNestedReuseComponent(node: arkts.AstNode): void { + if (!arkts.isCallExpression(node) || !arkts.isIdentifier(node.expression)) { + return; + } + if (this.reusableV2StructName.includes(node.expression.name)) { + // Traverse upwards to find the custom component. + let struceNode: arkts.AstNode = node; + let hasReportedError = false; + while (!arkts.isStructDeclaration(struceNode)) { + if (!struceNode.parent) { + return; + } + struceNode = struceNode.parent; + if (!hasReportedError) { + hasReportedError = this.checkNoReusableV2InRepeatTemplate(struceNode, node); + } } - struceNode = struceNode.parent; - if (!hasReportedError) { - hasReportedError = checkNoReusableV2InRepeatTemplate(struceNode, node, context); + // Gets a list of decorators for the current Struct + const annotationsList = struceNode.definition.annotations; + if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V1)) { + this.reportNoReusableV2InReusableV1(node); + } else if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.COMPONENT_V1)) { + this.reportNoReusableV2InComponentV1(node); } } - // Gets a list of decorators for the current Struct - const annotationsList = struceNode.definition.annotations; - if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.REUSABLE_V1)) { - reportNoReusableV2InReusableV1(node, context); - } else if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.COMPONENT_V1)) { - reportNoReusableV2InComponentV1(node, context); - } } } -const rule: UISyntaxRule = { - name: 'nested-reuse-component-check', - messages: { - noReusableV2InComponentV1: `A custom component decorated with @Component cannot contain child components decorated with @ReusableV2.`, - noReusableV2InReusableV1: `A custom component decorated with @Reusable cannot contain child components decorated with @ReusableV2.`, - noReusableV1InReusableV2: `A custom component decorated with @ReusableV2 cannot contain child components decorated with @Reusable.`, - noReusableV2InRepeatTemplate: `The template attribute of the Repeat component cannot contain any custom component decorated with @ReusableV2.`, - }, - setup(context) { - // Create arrays to store the components decorated with @ReusableV2 and @Reusable - let reusableV2StructName: string[] = []; - let reusableStructName: string[] = []; - return { - parsed: (node): void => { - initStructName(node, reusableV2StructName, reusableStructName); - checkNestedReuseComponent(node, context, reusableV2StructName); - checkNoReusableV1InReusableV2(node, context, reusableStructName); - }, - }; - }, -}; - -export default rule; +export default NestedReuseComponentCheckRule; \ No newline at end of file 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 9dab0b46e..8673700ac 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 @@ -14,85 +14,85 @@ */ import * as arkts from '@koalaui/libarkts'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; import { getIdentifierName } from '../utils'; -function isInsideStructAndBuild(node: arkts.AstNode): boolean { - if (!node.parent) { - return false; + +class NoChildInButtonRule extends AbstractUISyntaxRule { + public setup(): Record { + return { + noChildInButton: `The Button component with a label parameter can not have any child.`, + }; } - let parentNode = node.parent; - let isInStruct = false; - let isInBuild = false; - while (arkts.nodeType(parentNode) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - if (arkts.isStructDeclaration(parentNode)) { - isInStruct = true; + + public parsed(node: arkts.AstNode): void { + // Check if the current node is an identifier + if (!arkts.isIdentifier(node)) { + return; } - if (arkts.isScriptFunction(parentNode) && parentNode.id?.name === 'build') { - isInBuild = true; + const componentName = getIdentifierName(node); + // If the current node is 'Button' + if (componentName !== 'Button') { + return; } - if (!parentNode.parent) { - return false; + if (!this.isInsideStructAndBuild(node)) { + return; } - parentNode = parentNode.parent; + if (!node.parent) { + return; + } + // Obtain the information of the parent node of the current node + let parentNode = node.parent; + if (!arkts.isCallExpression(parentNode)) { + return; + }; + // Gets and traverses all the children of the parent node + this.reportNoChildInButtonError(parentNode); } - return isInStruct && isInBuild; -} -function reportNoChildInButtonError(parentNode: arkts.AstNode, context: UISyntaxRuleContext): void { - const siblings = parentNode.getChildren(); - if (!Array.isArray(siblings) || siblings.length < 3) { - return; + private isInsideStructAndBuild(node: arkts.AstNode): boolean { + if (!node.parent) { + return false; + } + let parentNode = node.parent; + let isInStruct = false; + let isInBuild = false; + while (arkts.nodeType(parentNode) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + if (arkts.isStructDeclaration(parentNode)) { + isInStruct = true; + } + if (arkts.isScriptFunction(parentNode) && parentNode.id?.name === 'build') { + isInBuild = true; + } + if (!parentNode.parent) { + return false; + } + parentNode = parentNode.parent; + } + return isInStruct && isInBuild; } - if (arkts.isStringLiteral(siblings[1]) && arkts.isBlockStatement(siblings[2])) { - context.report({ - node: parentNode, - message: rule.messages.noChildInButton, - fix: (parentNode) => { - const startPosition = siblings[2].startPosition; - const endPosition = siblings[2].endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); + + private reportNoChildInButtonError(parentNode: arkts.AstNode): void { + const siblings = parentNode.getChildren(); + if (!Array.isArray(siblings) || siblings.length < 3) { + return; + } + if (arkts.isStringLiteral(siblings[1]) && arkts.isBlockStatement(siblings[2])) { + this.report({ + node: parentNode, + message: this.messages.noChildInButton, + fix: () => { + const startPosition = siblings[2].startPosition; + const endPosition = siblings[2].endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); + } } -} -const rule: UISyntaxRule = { - name: 'no-child-in-button', - messages: { - noChildInButton: `The Button component with a label parameter can not have any child.`, - }, - setup(context: UISyntaxRuleContext) { - return { - parsed: (node): void => { - // Check if the current node is an identifier - if (!arkts.isIdentifier(node)) { - return; - } - const componentName = getIdentifierName(node); - // If the current node is 'Button' - if (componentName !== 'Button') { - return; - } - 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)) { - return; - }; - // Gets and traverses all the children of the parent node - reportNoChildInButtonError(parentNode, context); - } - }; - }, }; -export default rule; \ No newline at end of file +export default NoChildInButtonRule; \ No newline at end of file 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 8cdf3d2ce..90cecb674 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-entry.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-entry.ts @@ -15,69 +15,58 @@ import * as arkts from '@koalaui/libarkts'; import { getAnnotationUsage, MAX_ENTRY_DECORATOR_COUNT, PresetDecorators } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -function checkDuplicateEntry( - node: arkts.AstNode, - context: UISyntaxRuleContext, - entryDecoratorUsages: arkts.AnnotationUsage[], - entryData: { count: number } -): void { - if (!arkts.isStructDeclaration(node)) { - return; - } - const entryDecoratorUsage = getAnnotationUsage( - node, - PresetDecorators.ENTRY, - ); - if (entryDecoratorUsage) { - entryDecoratorUsages.push(entryDecoratorUsage); - } - // If more than one entry decorator is recorded, an error is reported - if (entryDecoratorUsages.length <= MAX_ENTRY_DECORATOR_COUNT) { - return; +class NoDuplicateEntryRule extends AbstractUISyntaxRule { + private entryDecoratorUsages: arkts.AnnotationUsage[] = []; + private entryDecoratorUsageIndex = 1; + + public setup(): Record { + return { + duplicateEntry: `A page can't contain more then one '@Entry' decorator.`, + }; } - if (entryData.count === MAX_ENTRY_DECORATOR_COUNT) { - const entryDocoratorUsage = entryDecoratorUsages.at(0)!; - context.report({ - node: entryDocoratorUsage, - message: rule.messages.duplicateEntry, + public parsed(node: arkts.StructDeclaration): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + let entryDecoratorUsage = getAnnotationUsage( + node, + PresetDecorators.ENTRY, + ); + if (entryDecoratorUsage) { + this.entryDecoratorUsages.push(entryDecoratorUsage); + } + // If more than one entry decorator is recorded, an error is reported + if (this.entryDecoratorUsages.length <= MAX_ENTRY_DECORATOR_COUNT) { + return; + } + if (this.entryDecoratorUsageIndex === MAX_ENTRY_DECORATOR_COUNT) { + const entryDecoratorUsage = this.entryDecoratorUsages.at(0)!; + this.report({ + node: entryDecoratorUsage, + message: this.messages.duplicateEntry, + fix: () => { + return { + range: [entryDecoratorUsage.startPosition, entryDecoratorUsage.endPosition], + code: '', + }; + } + }); + } + entryDecoratorUsage = this.entryDecoratorUsages.at(this.entryDecoratorUsageIndex)!; + this.report({ + node: entryDecoratorUsage, + message: this.messages.duplicateEntry, fix: () => { return { - range: [entryDocoratorUsage.startPosition, entryDocoratorUsage.endPosition], + range: [entryDecoratorUsage.startPosition, entryDecoratorUsage.endPosition], code: '', }; } }); + this.entryDecoratorUsageIndex++; } - const entryDocoratorUsage = entryDecoratorUsages.at(entryData.count)!; - context.report({ - node: entryDocoratorUsage, - message: rule.messages.duplicateEntry, - fix: () => { - return { - range: [entryDocoratorUsage.startPosition, entryDocoratorUsage.endPosition], - code: '', - }; - } - }); - entryData.count++; } -const rule: UISyntaxRule = { - name: 'no-duplicate-entry', - messages: { - duplicateEntry: `A page can't contain more then one '@Entry' decorator.`, - }, - setup(context) { - let entryDecoratorUsages: arkts.AnnotationUsage[] = []; - let entryData = { count: 1 }; - return { - parsed: (node): void => { - checkDuplicateEntry(node, context, entryDecoratorUsages, entryData); - }, - }; - }, -}; - -export default rule; \ No newline at end of file +export default NoDuplicateEntryRule; \ No newline at end of file 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 ed5149173..d4b689652 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-preview.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-preview.ts @@ -15,65 +15,55 @@ import * as arkts from '@koalaui/libarkts'; import { getAnnotationUsage, MAX_PREVIEW_DECORATOR_COUNT, PresetDecorators } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -function reportError(context: UISyntaxRuleContext, errorNode: arkts.AnnotationUsage): void { - context.report({ - node: errorNode, - message: rule.messages.duplicateEntry, - fix: () => { - return { - range: [errorNode.startPosition, errorNode.endPosition], - code: '', - }; - } - }); -} +class NoDuplicatePreviewRule extends AbstractUISyntaxRule { + private previewDecoratorUsages: arkts.AnnotationUsage[] = []; + private previewDecoratorUsageIndex = 10; -function checkDuplicatePreview( - node: arkts.AstNode, - context: UISyntaxRuleContext, - previewDecoratorUsages: arkts.AnnotationUsage[], - previewData: { count: number }, -): void { - if (!arkts.isStructDeclaration(node)) { - return; - } - const previewDecoratorUsage = getAnnotationUsage( - node, - PresetDecorators.PREVIEW, - ); - if (previewDecoratorUsage) { - previewDecoratorUsages.push(previewDecoratorUsage); + public setup(): Record { + return { + duplicateEntry: `A page can contain at most 10 '@Preview' decorators.`, + }; } - // If the number of preview decorators is less than 10, no error is reported - if (previewDecoratorUsages.length <= MAX_PREVIEW_DECORATOR_COUNT) { - return; + + public parsed(node: arkts.StructDeclaration): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + const previewDecoratorUsage = getAnnotationUsage( + node, + PresetDecorators.PREVIEW, + ); + if (previewDecoratorUsage) { + this.previewDecoratorUsages.push(previewDecoratorUsage); + } + // If the number of preview decorators is less than 10, no error is reported + if (this.previewDecoratorUsages.length <= MAX_PREVIEW_DECORATOR_COUNT) { + return; + } + if (this.previewDecoratorUsageIndex === MAX_PREVIEW_DECORATOR_COUNT) { + this.previewDecoratorUsages.forEach((previewDecoratorUsage) => { + this.reportError(previewDecoratorUsage); + }); + } else { + this.reportError(this.previewDecoratorUsages.at(this.previewDecoratorUsageIndex)!); + } + this.previewDecoratorUsageIndex++; } - if (previewData.count === MAX_PREVIEW_DECORATOR_COUNT) { - previewDecoratorUsages.forEach((previewDecoratorUsage) => { - reportError(context, previewDecoratorUsage); + + private reportError(errorNode: arkts.AnnotationUsage): void { + this.report({ + node: errorNode, + message: this.messages.duplicateEntry, + fix: () => { + return { + range: [errorNode.startPosition, errorNode.endPosition], + code: '', + }; + } }); - } else { - reportError(context, previewDecoratorUsages.at(previewData.count)!); } - previewData.count++; } -const rule: UISyntaxRule = { - name: 'no-duplicate-preview', - messages: { - duplicateEntry: `A page can contain at most 10 '@Preview' decorators.`, - }, - setup(context) { - let previewDecoratorUsages: arkts.AnnotationUsage[] = []; - let previewData = { count: 10 }; - return { - parsed: (node): void => { - checkDuplicatePreview(node, context, previewDecoratorUsages, previewData); - }, - }; - }, -}; - -export default rule; \ No newline at end of file +export default NoDuplicatePreviewRule; \ No newline at end of file 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 f32635b3d..e41aebcfc 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 @@ -15,9 +15,9 @@ import * as arkts from '@koalaui/libarkts'; import { getClassPropertyAnnotationNames, getIdentifierName, PresetDecorators, getAnnotationUsage } from '../utils'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -export const stateManagementDecorator = { +const stateManagementDecorator = { STATE: PresetDecorators.STATE, PROP: PresetDecorators.PROP, LINK: PresetDecorators.LINK, @@ -26,58 +26,56 @@ export const stateManagementDecorator = { }; const CLASS_PROPERTY_ANNOTATION_ONE: number = 1; -function duplicateState( - node: arkts.StructDeclaration, - context: UISyntaxRuleContext -): void { - node.definition.body.forEach(body => { - if (arkts.isClassProperty(body)) { - // Get the list of decorators + +class NoDuplicateStateManagerRule extends AbstractUISyntaxRule { + public setup(): Record { + return { + duplicateState: `The property '{{attributeName}}' cannot have multiple state management decorators.`, + }; + } + + public parsed(node: arkts.AstNode): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + + // If it's a struct for @ComponentV2, the check is skipped + const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); + if (componentV2Decorator) { + return; + } + + this.checkForDuplicateStateDecorators(node); + } + + // Check that the properties in the struct are not reused with state management-related decorators + private checkForDuplicateStateDecorators(node: arkts.StructDeclaration): void { + node.definition.body.forEach((body) => { + if (!arkts.isClassProperty(body)) { + return; + } + const propertyDecorators = getClassPropertyAnnotationNames(body); // Filter the decorators to get those related to state management const stateDecorators = propertyDecorators.filter(decorator => Object.values(stateManagementDecorator).includes(decorator) ); - // Check if Require is included const propertyNameNode = body.key; - let attributeName: string = ''; - if (propertyNameNode && arkts.isIdentifier(propertyNameNode)) { - attributeName = getIdentifierName(propertyNameNode); - } - if (!propertyNameNode || !attributeName) { + if (!propertyNameNode || !arkts.isIdentifier(propertyNameNode)) { return; } + + const attributeName = getIdentifierName(propertyNameNode); if (stateDecorators.length > CLASS_PROPERTY_ANNOTATION_ONE) { - context.report({ + this.report({ node: propertyNameNode, - message: rule.messages.duplicateState, + message: this.messages.duplicateState, data: { attributeName }, }); } - } - }); + }); + } } -const rule: UISyntaxRule = { - name: 'no-duplicate-state-manager', - messages: { - duplicateState: `The property '{{attributeName}}' cannot have multiple state management decorators.`, - }, - setup(context) { - return { - parsed: (node): void => { - if (!arkts.isStructDeclaration(node)) { - return; - } - const componentV2Decorator = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); - if (componentV2Decorator) { - return; - } - duplicateState(node, context); - }, - }; - }, -}; - -export default rule; +export default NoDuplicateStateManagerRule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-same-as-built-in-attribute.ts b/arkui-plugins/ui-syntax-plugins/rules/no-same-as-built-in-attribute.ts index 7afe1f354..5940191f6 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-same-as-built-in-attribute.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-same-as-built-in-attribute.ts @@ -14,40 +14,38 @@ */ import * as arkts from '@koalaui/libarkts'; -import { UISyntaxRule } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; import { isBuiltInAttribute } from '../utils'; -const rule: UISyntaxRule = { - name: 'no-same-as-built-in-attribute', - messages: { - duplicateName: `The struct '{{structName}}' cannot have the same name as the built-in attribute '{{builtInName}}'.`, - }, - setup(context) { +class NoSameAsBuiltInAttributeRule extends AbstractUISyntaxRule { + public setup(): Record { return { - parsed: (node): void => { - if (!arkts.isStructDeclaration(node)) { - return; - } - if (!node.definition) { - return; - } - if (!arkts.isClassDefinition(node.definition)) { - return; - } - const structIdent = node.definition.ident; - const structName = node.definition.ident?.name ?? ' '; - // If the struct name matches any built-in attribute, report an error - if (structIdent && isBuiltInAttribute(context, structName)) { - const builtInName = structName; - context.report({ - node: structIdent, - message: rule.messages.duplicateName, - data: { structName, builtInName } - }); - } - }, + duplicateName: `The struct '{{structName}}' cannot have the same name as the built-in attribute '{{builtInName}}'.`, }; - }, + } + + public parsed(node: arkts.AstNode): void { + if (!arkts.isStructDeclaration(node)) { + return; + } + if (!node.definition) { + return; + } + if (!arkts.isClassDefinition(node.definition)) { + return; + } + const structIdent = node.definition.ident; + const structName = node.definition.ident?.name ?? ' '; + // If the struct name matches any built-in attribute, report an error + if (structIdent && isBuiltInAttribute(this.context, structName)) { + const builtInName = structName; + this.report({ + node: structIdent, + message: this.messages.duplicateName, + data: { structName, builtInName } + }); + } + } }; -export default rule; \ No newline at end of file +export default NoSameAsBuiltInAttributeRule; diff --git a/arkui-plugins/ui-syntax-plugins/rules/observed-heritage-compatible-check.ts b/arkui-plugins/ui-syntax-plugins/rules/observed-heritage-compatible-check.ts index b32a86f73..f56e9b7ba 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/observed-heritage-compatible-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/observed-heritage-compatible-check.ts @@ -14,108 +14,108 @@ */ import * as arkts from '@koalaui/libarkts'; -import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; import { getIdentifierName, PresetDecorators } from '../utils/index'; -function getObservedDecorator(node: arkts.AstNode, observedClasses: string[], observedV2Classes: string[]): void { - for (const child of node.getChildren()) { - // Check if it is of the ClassDeclaration type - if (arkts.isClassDeclaration(child)) { - // Get a list of annotations - const annotations = child.definition?.annotations ?? []; - // Check for @Observed decorators - const hasObservedDecorator = annotations.find((annotation: any) => - annotation.expr.name === PresetDecorators.OBSERVED_V1); - // Check if there is a @ObservedV2 decorator - const hasObservedV2Decorator = annotations.find((annotation: any) => - annotation.expr.name === PresetDecorators.OBSERVED_V2); - // If there is a @Observed decorator, record the class name - if (hasObservedDecorator) { - const className = child.definition?.ident?.name ?? ''; - observedClasses.push(className); - } - // If there is a @ObservedV2 decorator, record the class name - if (hasObservedV2Decorator) { - const className = child.definition?.ident?.name ?? ''; - observedV2Classes.push(className); - } - } +class ObservedHeritageCompatibleCheckRule extends AbstractUISyntaxRule { + public setup(): Record { + return { + incompatibleHeritageObservedToObservedV2: `A class decorated by '@Observed' cannot inherit from a class decorated by '@ObservedV2'.`, + incompatibleHeritageObservedV2ToObserved: `A class decorated by '@ObservedV2' cannot inherit from a class decorated by '@Observed'.`, + }; } -} -function checkInheritanceCompatibility(context: UISyntaxRuleContext, node: arkts.ClassDeclaration, - observedClasses: string[], observedV2Classes: string[]): void { - const observedV1Decorator = node.definition?.annotations?.find(annotations => - annotations.expr && - arkts.isIdentifier(annotations.expr) && - annotations.expr.name === PresetDecorators.OBSERVED_V1 - ); - const observedV2Decorator = node.definition?.annotations?.find(annotations => - annotations.expr && - arkts.isIdentifier(annotations.expr) && - annotations.expr.name === PresetDecorators.OBSERVED_V2 - ); + // Record the class name decorated with @Observed and @ObservedV2 + private observedClasses: string[] = []; + private observedV2Classes: string[] = []; - //Get the name of the superClass - if (!node.definition?.super) { - return; - } - if (arkts.isETSTypeReference(node.definition?.super)) { - if (!node.definition.super.part) { - return; + public parsed(node: arkts.AstNode): void { + // Check if it's of type "Program". + if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + this.getObservedDecorator(node, this.observedClasses, this.observedV2Classes); } - if (!arkts.isETSTypeReferencePart(node.definition?.super.part)) { + // Check if the current node is a class declaration + if (!arkts.isClassDeclaration(node)) { return; } - if (!node.definition?.super.part.name) { - return; + this.checkInheritanceCompatibility(node, this.observedClasses, this.observedV2Classes); + } + + private getObservedDecorator(node: arkts.AstNode, observedClasses: string[], observedV2Classes: string[]): void { + for (const child of node.getChildren()) { + // Check if it is of the ClassDeclaration type + if (arkts.isClassDeclaration(child)) { + // Get a list of annotations + const annotations = child.definition?.annotations ?? []; + // Check for @Observed decorators + const hasObservedDecorator = annotations.find((annotation: any) => + annotation.expr.name === PresetDecorators.OBSERVED_V1); + // Check if there is a @ObservedV2 decorator + const hasObservedV2Decorator = annotations.find((annotation: any) => + annotation.expr.name === PresetDecorators.OBSERVED_V2); + // If there is a @Observed decorator, record the class name + if (hasObservedDecorator) { + const className = child.definition?.ident?.name ?? ''; + observedClasses.push(className); + } + // If there is a @ObservedV2 decorator, record the class name + if (hasObservedV2Decorator) { + const className = child.definition?.ident?.name ?? ''; + observedV2Classes.push(className); + } + } } - const superClassName = getIdentifierName(node.definition?.super.part.name); + } + + private checkInheritanceCompatibility( + node: arkts.ClassDeclaration, + observedClasses: string[], + observedV2Classes: string[]): void { + const observedV1Decorator = node.definition?.annotations?.find(annotations => + annotations.expr && + arkts.isIdentifier(annotations.expr) && + annotations.expr.name === PresetDecorators.OBSERVED_V1 + ); + const observedV2Decorator = node.definition?.annotations?.find(annotations => + annotations.expr && + arkts.isIdentifier(annotations.expr) && + annotations.expr.name === PresetDecorators.OBSERVED_V2 + ); - if (!superClassName) { + //Get the name of the superClass + if (!node.definition?.super) { return; } - // Verify that the inheritance relationship is compatible - if (observedV1Decorator && observedV2Classes.includes(superClassName)) { - context.report({ - node: observedV1Decorator, - message: rule.messages.incompatibleHeritageObservedToObservedV2, - }); - } - if (observedV2Decorator && observedClasses.includes(superClassName)) { - context.report({ - node: observedV2Decorator, - message: rule.messages.incompatibleHeritageObservedV2ToObserved, - }); + if (arkts.isETSTypeReference(node.definition?.super)) { + if (!node.definition.super.part) { + return; + } + if (!arkts.isETSTypeReferencePart(node.definition?.super.part)) { + return; + } + if (!node.definition?.super.part.name) { + return; + } + const superClassName = getIdentifierName(node.definition?.super.part.name); + + if (!superClassName) { + return; + } + // Verify that the inheritance relationship is compatible + if (observedV1Decorator && observedV2Classes.includes(superClassName)) { + this.report({ + node: observedV1Decorator, + message: this.messages.incompatibleHeritageObservedToObservedV2, + }); + } + if (observedV2Decorator && observedClasses.includes(superClassName)) { + this.report({ + node: observedV2Decorator, + message: this.messages.incompatibleHeritageObservedV2ToObserved, + }); + } } } -} - -const rule: UISyntaxRule = { - name: 'observed-heritage-compatible-check', - messages: { - incompatibleHeritageObservedToObservedV2: `A class decorated by '@Observed' cannot inherit from a class decorated by '@ObservedV2'.`, - incompatibleHeritageObservedV2ToObserved: `A class decorated by '@ObservedV2' cannot inherit from a class decorated by '@Observed'.`, - }, - setup(context) { - // Record the class name decorated with @Observed and @ObservedV2 - const observedClasses: string[] = []; - const observedV2Classes: string[] = []; - return { - parsed: (node): void => { - // Check if it's of type "Program". - if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - getObservedDecorator(node, observedClasses, observedV2Classes); - } - - // Check if the current node is a class declaration - if (!arkts.isClassDeclaration(node)) { - return; - } - checkInheritanceCompatibility(context, node, observedClasses, observedV2Classes); - }, - }; - }, }; -export default rule; \ No newline at end of file +export default ObservedHeritageCompatibleCheckRule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/observed-observedV2-check.ts b/arkui-plugins/ui-syntax-plugins/rules/observed-observedV2-check.ts index 808438ed0..79d42a4d6 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/observed-observedV2-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/observed-observedV2-check.ts @@ -15,35 +15,33 @@ import * as arkts from '@koalaui/libarkts'; import { PresetDecorators } from '../utils'; -import { UISyntaxRule } from './ui-syntax-rule'; +import { AbstractUISyntaxRule } from './ui-syntax-rule'; -const rule: UISyntaxRule = { - name: 'observed-observedV2-check', - messages: { - conflictingDecorators: `A class cannot be decorated by both '@Observed' and '@ObservedV2' at the same time.`, - }, - setup(context) { +class ObservedObservedV2Rule extends AbstractUISyntaxRule { + public setup(): Record { return { - parsed: (node): void => { - if (!arkts.isClassDeclaration(node)) { - return; - } - const hasObservedDecorator = node.definition?.annotations?.find(annotations => annotations.expr && - arkts.isIdentifier(annotations.expr) && - annotations.expr.name === PresetDecorators.OBSERVED_V1); - const hasObservedV2Decorator = node.definition?.annotations?.find(annotations => annotations.expr && - arkts.isIdentifier(annotations.expr) && - annotations.expr.name === PresetDecorators.OBSERVED_V2); - // If the current class is decorated by @Observed and @ObservedV2, an error is reported - if (hasObservedDecorator && hasObservedV2Decorator) { - context.report({ - node: hasObservedDecorator, - message: rule.messages.conflictingDecorators, - }); - } - }, + conflictingDecorators: `A class cannot be decorated by both '@Observed' and '@ObservedV2' at the same time.`, }; - }, + } + + public parsed(node: arkts.AstNode): void { + if (!arkts.isClassDeclaration(node)) { + return; + } + const hasObservedDecorator = node.definition?.annotations?.find(annotations => annotations.expr && + arkts.isIdentifier(annotations.expr) && + annotations.expr.name === PresetDecorators.OBSERVED_V1); + const hasObservedV2Decorator = node.definition?.annotations?.find(annotations => annotations.expr && + arkts.isIdentifier(annotations.expr) && + annotations.expr.name === PresetDecorators.OBSERVED_V2); + // If the current class is decorated by @Observed and @ObservedV2, an error is reported + if (hasObservedDecorator && hasObservedV2Decorator) { + this.report({ + node: hasObservedDecorator, + message: this.messages.conflictingDecorators, + }); + } + } }; -export default rule; \ No newline at end of file +export default ObservedObservedV2Rule; -- Gitee