From 92dcbabaa1605fa3bc2da90bf967ba20dbec19b0 Mon Sep 17 00:00:00 2001 From: wangweiyuan Date: Thu, 24 Jul 2025 20:15:14 +0800 Subject: [PATCH] ui-syntax-plugins 0724 Signed-off-by: wangweiyuan --- .../rules/attribute-no-invoke.ts | 14 +- .../rules/builderparam-decorator-check.ts | 119 ++--- .../rules/component-componentV2-init-check.ts | 125 +++--- .../component-componentV2-mix-use-check.ts | 239 ++++------ .../rules/computed-decorator-check.ts | 415 +++++++++--------- .../rules/entry-localstorage-check.ts | 28 +- .../rules/nested-relationship.ts | 161 ++++--- .../rules/no-child-in-button.ts | 112 ++--- .../rules/no-duplicate-id.ts | 2 +- .../rules/no-same-as-built-in-attribute.ts | 54 ++- .../rules/reusable-component-in-V2-check.ts | 75 ++-- .../rules/specific-component-children.ts | 24 +- .../rules/wrap-builder-check.ts | 73 ++- .../ui-syntax-plugins/utils/index.ts | 123 +++++- 14 files changed, 790 insertions(+), 774 deletions(-) diff --git a/arkui-plugins/ui-syntax-plugins/rules/attribute-no-invoke.ts b/arkui-plugins/ui-syntax-plugins/rules/attribute-no-invoke.ts index 47f7b4ae4..2c4aa76b5 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/attribute-no-invoke.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/attribute-no-invoke.ts @@ -14,7 +14,7 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getCallee, isBuildInComponent } from '../utils'; +import { getCallee } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; @@ -46,7 +46,7 @@ class AttributeNoInvokeRule extends AbstractUISyntaxRule { const callee = getCallee(childNode[0].object); // Determine whether it is a built-in component - if (callee && isBuildInComponent(this.context, callee.name)) { + if (callee && this.isBuiltInComponent(callee.name)) { this.report({ node, message: this.messages.cannotInitializePrivateVariables, @@ -56,6 +56,16 @@ class AttributeNoInvokeRule extends AbstractUISyntaxRule { }); } } + + private isBuiltInComponent(componentName: string): boolean { + if (!this.context.componentsInfo) { + return false; + } + return ( + this.context.componentsInfo.containerComponents.includes(componentName) || + this.context.componentsInfo.atomicComponents.includes(componentName) + ); + } }; export default AttributeNoInvokeRule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/builderparam-decorator-check.ts b/arkui-plugins/ui-syntax-plugins/rules/builderparam-decorator-check.ts index ac532a3e9..8ed3bda50 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/builderparam-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/builderparam-decorator-check.ts @@ -14,119 +14,82 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getIdentifierName, PresetDecorators, BUILD_NAME, findDecorator } from '../utils'; +import { PresetDecorators, getAnnotationUsageByName, isStructClassDeclaration, isBuildInDecorator } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; class BuilderParamDecoratorCheckRule extends AbstractUISyntaxRule { - private structNameWithMultiplyBuilderParam: string[] = []; - public setup(): Record { return { onlyOneBuilderParamProperty: `In the trailing lambda case, '{{structName}}' must have one and only one property decorated with @BuilderParam, and its @BuilderParam expects no parameter.`, }; } - public beforeTransform(): void { - this.structNameWithMultiplyBuilderParam = []; - } - - public parsed(node: arkts.AstNode): void { - this.getStructNameWithMultiplyBuilderParam(node); - this.checkComponentInitialize(node); + public checked(node: arkts.AstNode): void { + if (arkts.isCallExpression(node)) { + this.checkComponentInitialize(node); + } } - private getStructNameWithMultiplyBuilderParam( - node: arkts.AstNode, - ): void { - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; + private isStructWithMultiplyBuilderParam( + node: arkts.ClassDeclaration, + ): boolean { + let count: number = 0; + if (!node.definition) { + return false; } - node.getChildren().forEach((member) => { - if (!arkts.isStructDeclaration(member) || !member.definition.ident) { + node.definition.body.forEach((item) => { + if (!arkts.isClassProperty(item) || !item.key) { return; } - let count: number = 0; - let structName: string = member.definition.ident.name ?? ''; - member.definition.body.forEach((item) => { - if (!arkts.isClassProperty(item) || !item.key) { - return; - } - const builderParam = findDecorator(item, PresetDecorators.BUILDER_PARAM); - - if (builderParam) { - count++; - } - }); - if (count > 1) { - this.structNameWithMultiplyBuilderParam.push(structName); + const builderParam = getAnnotationUsageByName(item.annotations, PresetDecorators.BUILDER_PARAM); + if (builderParam && isBuildInDecorator(builderParam)) { + count++; } }); - } - - private isInBuild(node: arkts.AstNode): boolean { - if (!node.parent) { - return false; - } - let structNode = node.parent; - while (!arkts.isMethodDefinition(structNode) || getIdentifierName(structNode.name) !== BUILD_NAME) { - if (!structNode.parent) { - return false; - } - structNode = structNode.parent; + if (count > 1) { + return true; } - return arkts.isMethodDefinition(structNode) && getIdentifierName(structNode.name) === BUILD_NAME; + return false; } - private hasBlockStatement(node: arkts.AstNode): boolean { - if (!node.parent) { - return false; - } - let parentNode = node.parent; - const siblings = parentNode.getChildren(); - if (!Array.isArray(siblings) || siblings.length < 2) { - return false; - } - if (arkts.isStringLiteral(siblings[1]) && arkts.isBlockStatement(siblings[2])) { - return true; - } - if (arkts.isBlockStatement(siblings[1])) { + private isTrailingLambda(node: arkts.CallExpression): boolean { + if (node.arguments[0] && node.arguments[1] && + arkts.isArrowFunctionExpression(node.arguments[0]) && + arkts.isArrowFunctionExpression(node.arguments[1])) { return true; } return false; } private checkComponentInitialize( - node: arkts.AstNode, + node: arkts.CallExpression, ): void { - if (!node.parent) { + let children = node.getChildren(); + let firstMemberExpression = children[0]; + if (!arkts.isMemberExpression(firstMemberExpression)) { return; } - let parentNode: arkts.AstNode = node.parent; - if (!arkts.isCallExpression(parentNode)) { + if (!this.isTrailingLambda(node)) { return; } - if (!arkts.isIdentifier(node) || !this.structNameWithMultiplyBuilderParam.includes(getIdentifierName(node))) { + if (!arkts.isIdentifier(firstMemberExpression.object)) { return; } - if (!this.hasBlockStatement(node)) { + const componentNameNode = firstMemberExpression.object; + const structName = firstMemberExpression.object.name; + const structDefinition = arkts.getDecl(firstMemberExpression.object); + if (!structDefinition || !structDefinition.parent) { return; } - let structName: string = getIdentifierName(node); - let structNode = node.parent; - while (!arkts.isStructDeclaration(structNode)) { - if (!structNode.parent) { - return; - } - structNode = structNode.parent; - } - if (!this.isInBuild(node)) { - return; + const structDeclaration = structDefinition.parent; + if (isStructClassDeclaration(structDeclaration) && + this.isStructWithMultiplyBuilderParam(structDeclaration)) { + this.report({ + node: componentNameNode, + message: this.messages.onlyOneBuilderParamProperty, + data: { structName }, + }); } - this.report({ - node: node, - message: this.messages.onlyOneBuilderParamProperty, - data: { structName }, - }); } } 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 42c497eb7..c962c44b1 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 @@ -14,80 +14,83 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getAnnotationUsage, getIdentifierName, hasAnnotation, PresetDecorators } from '../utils'; +import { getAnnotationUsageByName, getIdentifierName, isStructClassDeclaration, PresetDecorators } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; 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.`, - }; - } + public setup(): Record { + return { + componentInitLinkCheck: `A V2 component cannot be used with any member property decorated by '@Link' in a V1 component.`, + }; + } - public beforeTransform(): void { - this.componentV1WithLinkList = []; - } + public checked(node: arkts.AstNode): void { + if (!arkts.isCallExpression(node) || !node.expression || !arkts.isMemberExpression(node.expression) || + !node.expression.object || !arkts.isIdentifier(node.expression.object)) { + return; + } - public parsed(node: arkts.StructDeclaration): void { - this.initComponentV1WithLinkList(node); - this.checkComponentInitLink(node); - } + let structNode = arkts.getDecl(node.expression.object); + // Determine whether it is a custom component + const isCustomComponent = structNode && arkts.isClassDefinition(structNode) && structNode.parent && + isStructClassDeclaration(structNode.parent); + if (!isCustomComponent || !structNode.parent || !isStructClassDeclaration(structNode.parent)) { + return; + } - private initComponentV1WithLinkList(node: arkts.AstNode): void { - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { - return; - } - node.getChildren().forEach((member) => { - if (!arkts.isStructDeclaration(member) || !member.definition.ident || - !hasAnnotation(member?.definition.annotations, PresetDecorators.COMPONENT_V1)) { - return; - } - let structName: string = member.definition.ident?.name ?? ''; - member.definition?.body?.forEach((item) => { - if (!arkts.isClassProperty(item) || !item.key) { - return; + //if the current component has a @ComponentV2 annotation and the custom component that is called has a property with a @link annotation, report error + const hasLinkProperty = this.isStructHasLinkProperty(structNode.parent); + if (!hasLinkProperty) { + return; + } + const structAnnotations = this.getStructAnnotations(node); + if (!structAnnotations) { + return; } - if (item.annotations.some(annotation => annotation.expr && - getIdentifierName(annotation.expr) === PresetDecorators.LINK)) { - this.componentV1WithLinkList.push(structName); + const isComponentV2 = getAnnotationUsageByName(structAnnotations, PresetDecorators.COMPONENT_V2); + if (!isComponentV2) { + return; } - }); - }); - } - private checkComponentInitLink(node: arkts.AstNode): void { - if (!arkts.isIdentifier(node) || !this.componentV1WithLinkList.includes(getIdentifierName(node))) { - return; + this.report({ + node: node, + message: this.messages.componentInitLinkCheck, + fix: () => { + return { + range: [node.startPosition, node.endPosition], + code: '', + }; + } + }); } - if (!node.parent) { - return; - } - let structNode = node.parent; - while (!arkts.isStructDeclaration(structNode)) { - if (!structNode.parent) { - return; - } - structNode = structNode.parent; + + private isStructHasLinkProperty(structNode: arkts.ClassDeclaration): boolean { + let hasLinkProperty = false; + structNode.definition?.body?.forEach((item) => { + if (hasLinkProperty || !arkts.isClassProperty(item) || !item.key) { + return; + } + if (item.annotations.some(annotation => annotation.expr && + getIdentifierName(annotation.expr) === PresetDecorators.LINK)) { + hasLinkProperty = true; + } + }); + return hasLinkProperty; } - 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: '', - }; + + private getStructAnnotations(node: arkts.AstNode): readonly arkts.AnnotationUsage[] | undefined { + while (!isStructClassDeclaration(node)) { + if (!node.parent) { + return undefined; + } + node = node.parent; } - }); + if (!node.definition) { + return undefined; + } + return node.definition.annotations; } - } + } 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 70714c3b3..54ceff8ed 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 @@ -14,176 +14,119 @@ */ import * as arkts from '@koalaui/libarkts'; -import { PresetDecorators, getIdentifierName } from '../utils'; +import { PresetDecorators, isClassDeclaration, getAnnotationUsageByName, getIdentifierName, isStructClassDeclaration, getAnnotationName } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; const v1ComponentDecorators: string[] = [ - PresetDecorators.STATE, - PresetDecorators.PROP_REF, - PresetDecorators.LINK, - PresetDecorators.PROVIDE, - PresetDecorators.CONSUME, - PresetDecorators.STORAGE_LINK, - PresetDecorators.STORAGE_PROP_REF, - PresetDecorators.LOCAL_STORAGE_LINK, + PresetDecorators.STATE, + PresetDecorators.PROP_REF, + PresetDecorators.LINK, + PresetDecorators.PROVIDE, + PresetDecorators.CONSUME, + PresetDecorators.STORAGE_LINK, + PresetDecorators.STORAGE_PROP_REF, + PresetDecorators.LOCAL_STORAGE_LINK, ]; class ComponentComponentV2MixUseCheckRule extends AbstractUISyntaxRule { - private observedV2Names: Set = new Set(); - public setup(): Record { - return { - observedv2_v1: `The type of the @{{annotation}} property cannot be a class decorated with '@ObservedV2'.` - }; - } - - public beforeTransform(): void { - this.observedV2Names = new Set(); - } - - 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); + public setup(): Record { + return { + observedv2_v1: `The type of the @{{annotation}} property cannot be a class decorated with '@ObservedV2'.` + }; } - } - private findAllObserved(node: arkts.AstNode): void { - if (arkts.isClassDeclaration(node)) { - node.definition?.annotations.forEach((anno) => { - if (!anno.expr) { - return; + public checked(node: arkts.AstNode): void { + const structAnnotations = this.getStructAnnotations(node); + if (!structAnnotations) { + return; } - - const annotationName = getIdentifierName(anno.expr); - - if (annotationName === PresetDecorators.OBSERVED_V2) { - const componentV2Name = node.definition?.ident?.name; - componentV2Name ? this.observedV2Names.add(componentV2Name) : null; + const componentV1Annotation = getAnnotationUsageByName(structAnnotations, PresetDecorators.COMPONENT_V1); + if (!componentV1Annotation) { + return; } - }); - } - - 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 annotation = this.getClassPropertyAnnotation(node); + if (annotation && v1ComponentDecorators.includes(getAnnotationName(annotation))) { + const typeNode = this.getTypeNode(node); + if (!typeNode) { + return; + } + + const annotationNode = this.getClassAnnotations(typeNode); + if (!annotationNode) { + return; + } + const observedV2Annotation = getAnnotationUsageByName(annotationNode, PresetDecorators.OBSERVED_V2); + if (observedV2Annotation) { + this.report({ + node: annotation, + message: this.messages.observedv2_v1, + data: { + annotation: getAnnotationName(annotation), + }, + }); + } } - } - } - - for (const child of node.getChildren()) { - this.findAllTSTypeAliasDeclaration(child); } - } - - private findAllObservedType( - node: arkts.AstNode, - typeName: string - ): void { - if (arkts.isIdentifier(node)) { - const name = getIdentifierName(node); - - if (this.observedV2Names.has(name)) { - this.observedV2Names.add(typeName); - } + private getStructAnnotations(node: arkts.AstNode): readonly arkts.AnnotationUsage[] | undefined { + while (!isStructClassDeclaration(node)) { + if (!node.parent) { + return undefined; + } + node = node.parent; + } + if (!node.definition) { + return undefined; + } + return node.definition.annotations; } - for (const child of node.getChildren()) { - this.findAllObservedType(child, typeName); + private getClassAnnotations(node: arkts.AstNode): readonly arkts.AnnotationUsage[] | undefined { + while (!isClassDeclaration(node)) { + if (!node.parent) { + return undefined; + } + node = node.parent; + } + if (!node.definition) { + return undefined; + } + return node.definition.annotations; } - } - private processComponentAnnotations( - node: arkts.StructDeclaration - ): void { - node.definition.annotations.forEach((anno) => { - if (!anno.expr) { - return; - } - const annotationName = getIdentifierName(anno.expr); - if ( - annotationName === PresetDecorators.COMPONENT_V2 || - annotationName === PresetDecorators.COMPONENT_V1 - ) { - this.traverseTree(node, annotationName); - } - }); - } - - private traverseTree( - node: arkts.AstNode, - annotationName: string - ): void { - if (arkts.isClassProperty(node)) { - this.processNode(node, annotationName); - } + private getClassPropertyAnnotation(node: arkts.AstNode): arkts.AnnotationUsage | undefined { + if (!arkts.isClassProperty(node)) { + return undefined; + } + const annotation = node.annotations.find(annotation => { + return annotation.expr && getIdentifierName(annotation.expr); + }); - for (const child of node.getChildren()) { - this.traverseTree(child, annotationName); + return annotation; } - } - 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_V1 && - this.observedV2Names.has(name) - ) { - this.checkObservedConflict(node, v1ComponentDecorators); - break; + private getTypeNode(node: arkts.AstNode): arkts.AstNode | undefined { + if (!arkts.isClassProperty(node)) { + return undefined; + } + if (!node.typeAnnotation) { + return undefined; } - } - const children = currentNode.getChildren(); - for (const child of children) { - queue.push(child); - } + if (!arkts.isETSTypeReference(node.typeAnnotation)) { + return undefined; + } + if (!node.typeAnnotation.part) { + return undefined; + } + if (!arkts.isETSTypeReferencePart(node.typeAnnotation.part)) { + return undefined; + } + if (!node.typeAnnotation.part.name) { + return undefined; + } + const typeNode = arkts.getDecl(node.typeAnnotation.part.name); + return typeNode; } - } - - private checkObservedConflict( - node: arkts.ClassProperty, - componentDecorators: string[] - ): void { - node.annotations.forEach((anno) => { - if (!anno.expr) { - return; - } - - const annotationName = getIdentifierName(anno.expr); - if (annotationName && componentDecorators.includes(annotationName)) { - this.report({ - node: anno, - message: this.messages.observedv2_v1, - data: { - annotation: annotationName, - }, - }); - } - }); - } } export default ComponentComponentV2MixUseCheckRule; \ 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 2a1d477ab..96d04365b 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/computed-decorator-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/computed-decorator-check.ts @@ -14,13 +14,10 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getClassAnnotationUsage, getIdentifierName, PresetDecorators, getAnnotationUsage, BUILD_NAME, findDecorator } from '../utils'; +import { isClassDeclaration, isStructClassDeclaration, getAnnotationUsageByName, getIdentifierName, PresetDecorators, getAnnotationName, getAnnotationUsage, BUILD_NAME, findDecorator } from '../utils'; 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'.`, @@ -30,58 +27,61 @@ class ComputedDecoratorCheckRule extends AbstractUISyntaxRule { computedMethodDefineSet: `A property decorated by '@Computed' cannot define a set method.` }; } + public checked(node: arkts.AstNode): void { + if (arkts.isObjectExpression(node)) { + node.properties.forEach((member) => { + if (!arkts.isProperty(member)) { + return; + } - public beforeTransform(): void { - this.computedGetters = new Map(); - this.computedSetters = new Map(); - } - - public parsed(node: arkts.AstNode): void { - if (arkts.isStructDeclaration(node)) { - this.validateComponentV2InStruct(node); - this.validateStructBody(node); + if (!member.key || !arkts.isIdentifier(member.key)) { + return; + } + const objectNode = arkts.getDecl(member.key); + if (!objectNode) { + return; + } + this.checkNoTwoWayBinding(objectNode, member); + }); + } + const propertyAnnotation = this.getClassPropertyAnnotation(node); + if (propertyAnnotation) { + this.validateAnnotation(propertyAnnotation); } + const annotation = this.getMethodAnnotation(node); + if (!annotation) { + return; + } + if (arkts.isMethodDefinition(node) && node.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET) { + this.checkObservedV2InClass(annotation, node); + this.checkComponentV2InStruct(annotation, node); - if (arkts.isClassDeclaration(node)) { - this.validateClassBody(node); + this.checkComputedWithSet(annotation, node); + } + else { + this.validateAnnotation(annotation); } } - 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 (arkts.isMethodDefinition(member)) { - const methodName = getIdentifierName(member.name); - computedDecorator = findDecorator(member.scriptFunction, 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); - } - } + private getClassPropertyAnnotation(node: arkts.AstNode): arkts.AnnotationUsage | undefined { + if (!arkts.isClassProperty(node)) { + return undefined; + } + const annotation = node.annotations.find(annotation => { + return annotation.expr && getIdentifierName(annotation.expr); }); - this.validateGetterSetterConflict(); + return annotation; } - private validateComputedOnClassProperty(member: arkts.ClassProperty): void { - const computedDecorator = findDecorator(member, PresetDecorators.COMPUTED); - if (computedDecorator) { + private validateAnnotation(annotation: arkts.AnnotationUsage): void { + if (getAnnotationName(annotation) === PresetDecorators.COMPUTED) { this.report({ - node: computedDecorator, + node: annotation, message: this.messages.onlyOnGetter, - fix: (computedDecorator) => { - const startPosition = computedDecorator.startPosition; - const endPosition = computedDecorator.endPosition; + fix: (annotation) => { + let startPosition = annotation.startPosition; + const endPosition = annotation.endPosition; return { range: [startPosition, endPosition], code: '', @@ -91,217 +91,238 @@ class ComputedDecoratorCheckRule extends AbstractUISyntaxRule { } } - 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); + private getStructAnnotations(node: arkts.AstNode): readonly arkts.AnnotationUsage[] | undefined { + while (!isStructClassDeclaration(node)) { + if (!node.parent) { + return undefined; } + node = node.parent; } - } - - private reportValidateComputedMethodKind(computedDecorator: arkts.AnnotationUsage | undefined): void { - if (!computedDecorator) { - return; + if (!node.definition) { + return undefined; } - this.report({ - node: computedDecorator, - message: this.messages.onlyOnGetter, - fix: (computedDecorator) => { - const startPosition = computedDecorator.startPosition; - const endPosition = computedDecorator.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); + return node.definition.annotations; } - private validateBuildMethod(member: arkts.MethodDefinition): void { - member.scriptFunction.body?.getChildren().forEach((childNode) => { - if (!arkts.isExpressionStatement(childNode)) { - 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); - } + private getClassAnnotations(node: arkts.AstNode): readonly arkts.AnnotationUsage[] | undefined { + + while (!isClassDeclaration(node)) { + if (!node.parent) { + return undefined; } + node = node.parent; + } + if (!node.definition) { + return undefined; + } + return node.definition.annotations; + } + private getMethodAnnotation(node: arkts.AstNode): arkts.AnnotationUsage | undefined { + if (!arkts.isMethodDefinition(node)) { + return undefined; + } + const annotation = node.scriptFunction.annotations.find(annotation => { + return annotation.expr && getIdentifierName(annotation.expr); }); + return annotation; } - private validateCallExpression(currentNode: arkts.CallExpression): void { - if (!arkts.isIdentifier(currentNode.expression) || getIdentifierName(currentNode.expression) !== '$$') { - return; + private checkComponentV2InStruct( + annotation: arkts.AnnotationUsage, + node: arkts.AstNode + ): void { + const structAnnotations = this.getStructAnnotations(node); + if (getAnnotationName(annotation) === PresetDecorators.COMPUTED && structAnnotations && node) { + const componentV1Decorator = getAnnotationUsageByName(structAnnotations, PresetDecorators.COMPONENT_V1); + const componentV2Decorator = getAnnotationUsageByName(structAnnotations, PresetDecorators.COMPONENT_V2); + this.validateComponentV2InStruct(annotation, componentV1Decorator, componentV2Decorator); } - - currentNode.arguments.forEach((argument) => { - if (arkts.isMemberExpression(argument)) { - const getterName = getIdentifierName(argument.property); - this.reportValidateCallExpression(currentNode, argument, getterName); - } - }); } - private reportValidateCallExpression( - currentNode: arkts.CallExpression, - argument: arkts.MemberExpression, - getterName: string + private validateComponentV2InStruct( + annotation: arkts.AnnotationUsage, + componentV1Decorator: arkts.AnnotationUsage | undefined, + componentV2Decorator: arkts.AnnotationUsage | undefined, ): void { - if (this.computedGetters.has(getterName)) { + if (!componentV2Decorator && componentV1Decorator) { this.report({ - node: currentNode, - message: this.messages.noTwoWayBinding, - fix: (currentNode) => { - const startPosition = currentNode.startPosition; - const endPosition = currentNode.endPosition; + node: annotation, + message: this.messages.componentV2InStruct, + fix: () => { + let startPosition = componentV1Decorator.startPosition; + const endPosition = componentV1Decorator.endPosition; return { range: [startPosition, endPosition], - code: argument.dumpSrc(), + code: `@${PresetDecorators.COMPONENT_V2}`, }; }, }); } } - private validateGetterSetterConflict(): void { - for (const [name] of this.computedGetters) { - if (this.computedSetters.has(name)) { - this.reportValidateGetterSetterConflict(name); - } + private checkObservedV2InClass( + annotation: arkts.AnnotationUsage, + node: arkts.AstNode + ): void { + const classAnnotations = this.getClassAnnotations(node); + if (getAnnotationName(annotation) === PresetDecorators.COMPUTED && classAnnotations && node) { + const observedV1Decorator = getAnnotationUsageByName(classAnnotations, PresetDecorators.OBSERVED_V1); + const observedV2Decorator = getAnnotationUsageByName(classAnnotations, PresetDecorators.OBSERVED_V2); + this.validateObservedV2InClass(node, annotation, observedV1Decorator, observedV2Decorator); } } - 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.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - }, - }); - } - - private validateClassBody(node: arkts.ClassDeclaration): void { - const observedV2Decorator = getClassAnnotationUsage(node, PresetDecorators.OBSERVED_V2); - const observedDecorator = getClassAnnotationUsage(node, PresetDecorators.OBSERVED_V1); - - node.definition?.body.forEach((member) => { - if (arkts.isMethodDefinition(member)) { - - this.validateComputedInClass(node, member, observedV2Decorator, observedDecorator); - const computedDecorator = findDecorator(member.scriptFunction, PresetDecorators.COMPUTED); - if (!arkts.isIdentifier(member.name)) { - return; - } - const methodName = getIdentifierName(member.name); - - this.validateComputedMethodKind(member, computedDecorator, methodName); - } - }); - } - - 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 + private validateObservedV2InClass( + node: arkts.AstNode, + annotation: arkts.AnnotationUsage, + observedV1Decorator: arkts.AnnotationUsage | undefined, + observedV2Decorator: arkts.AnnotationUsage | undefined, ): void { - const computedDecorator = findDecorator(member.scriptFunction, PresetDecorators.COMPUTED); - if (computedDecorator && !componentV2Decorator && !componentDecorator) { + if (!observedV1Decorator && !observedV2Decorator) { this.report({ - node: computedDecorator, - message: this.messages.componentV2InStruct, + node: annotation, + message: this.messages.onlyInObservedV2, fix: () => { const startPosition = node.startPosition; const endPosition = node.startPosition; return { range: [startPosition, endPosition], - code: `@${PresetDecorators.COMPONENT_V2}\n`, + code: `@${PresetDecorators.OBSERVED_V2}\n`, }; }, }); } - if (computedDecorator && !componentV2Decorator && componentDecorator) { + if (!observedV2Decorator && observedV1Decorator) { this.report({ - node: computedDecorator, - message: this.messages.componentV2InStruct, + node: annotation, + range: [annotation.startPosition, annotation.endPosition], + message: this.messages.onlyInObservedV2, fix: () => { - const startPosition = componentDecorator.startPosition; - const endPosition = componentDecorator.endPosition; + let startPosition = observedV1Decorator.startPosition; + const endPosition = observedV1Decorator.endPosition; return { range: [startPosition, endPosition], - code: `${PresetDecorators.COMPONENT_V2}`, + code: `@${PresetDecorators.OBSERVED_V2}`, + title: 'Change @Observed to @ObservedV2', }; }, }); } } - private validateComputedInClass( + private checkNoTwoWayBinding( node: arkts.AstNode, - member: arkts.MethodDefinition, - observedV2Decorator: arkts.AnnotationUsage | undefined, - observedDecorator: arkts.AnnotationUsage | undefined + member: arkts.Property + ): void { + if (!arkts.isMethodDefinition(node)) { + return; + } + if (!node.scriptFunction) { + return; + } + const typeNode = node.scriptFunction.returnTypeAnnotation; + if (!typeNode) { + return; + } + if (!arkts.isETSUnionType(typeNode)) { + return; + } + typeNode.types.forEach(type => { + if (!arkts.isETSTypeReference(type)) { + return; + } + if (!type.part) { + return; + } + if (!arkts.isETSTypeReferencePart(type.part)) { + return; + } + if (!type.part.name) { + return; + } + if (!arkts.isIdentifier(type.part.name)) { + return; + } + const typeName = type.part.name.name; + if (typeName !== 'Bindable') { + return; + } + const value = member.value; + if (!value || !arkts.isCallExpression(value)) { + return; + } + this.validateTwoWayBinding(value, member); + }); + } + + private validateTwoWayBinding( + value: arkts.CallExpression, + member: arkts.Property ): void { - const computedDecorator = findDecorator(member.scriptFunction, PresetDecorators.COMPUTED); - if (computedDecorator && !observedV2Decorator && !observedDecorator && - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET === member.kind) { + value.arguments.forEach((argument) => { + if (!arkts.isMemberExpression(argument)) { + return; + } + const propertyNode = arkts.getDecl(argument.property); + if (!propertyNode) { + return; + } + const propertyAnnotation = this.getMethodAnnotation(propertyNode); + if (!propertyAnnotation) { + return; + } + if (getAnnotationName(propertyAnnotation) !== PresetDecorators.COMPUTED) { + return; + } this.report({ - node: computedDecorator, - message: this.messages.onlyInObservedV2, - fix: () => { - const startPosition = node.startPosition; + node: member, + message: this.messages.noTwoWayBinding, + fix: (value) => { + const startPosition = value.startPosition; + const endPosition = value.endPosition; return { - range: [startPosition, startPosition], - code: `@${PresetDecorators.OBSERVED_V2}\n`, + range: [startPosition, endPosition], + code: argument.dumpSrc(), }; }, }); + }); + } + + private checkComputedWithSet( + annotation: arkts.AnnotationUsage, + node: arkts.MethodDefinition + ): void { + if (getAnnotationName(annotation) === PresetDecorators.COMPUTED && node) { + node.overloads.forEach((member) => { + if (!arkts.isMethodDefinition(member)) { + return; + } + this.validateComputedWithSet(member, node); + }); } - if (computedDecorator && !observedV2Decorator && observedDecorator && - arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_GET === member.kind) { + } + + private validateComputedWithSet( + member: arkts.MethodDefinition, + node: arkts.MethodDefinition + ): void { + if (member.kind === arkts.Es2pandaMethodDefinitionKind.METHOD_DEFINITION_KIND_SET) { + const setMethodName = getIdentifierName(member.name); + const getMethodName = getIdentifierName(node.name); + if (setMethodName !== getMethodName) { + return; + } this.report({ - node: computedDecorator, - message: this.messages.onlyInObservedV2, - fix: () => { - const startPosition = observedDecorator.startPosition; - const endPosition = observedDecorator.endPosition; + node: member, + message: this.messages.computedMethodDefineSet, + fix: (member) => { + const startPosition = member.startPosition; + const endPosition = member.endPosition; return { range: [startPosition, endPosition], - code: `${PresetDecorators.OBSERVED_V2}`, + code: '', }; }, }); diff --git a/arkui-plugins/ui-syntax-plugins/rules/entry-localstorage-check.ts b/arkui-plugins/ui-syntax-plugins/rules/entry-localstorage-check.ts index 7fb289253..32a0a121c 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/entry-localstorage-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/entry-localstorage-check.ts @@ -14,7 +14,9 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getAnnotationUsage, getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; +import { + getAnnotationUsageByName, getClassPropertyAnnotationNames, PresetDecorators, isStructClassDeclaration +} from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; class EntryLocalStorageCheckRule extends AbstractUISyntaxRule { @@ -24,17 +26,29 @@ class EntryLocalStorageCheckRule extends AbstractUISyntaxRule { }; } - public parsed(node: arkts.AstNode): void { - if (!arkts.isStructDeclaration(node)) { + public checked(node: arkts.AstNode): void { + if (!isStructClassDeclaration(node)) { return; } this.checkLocalStorageLink(node); } - private checkLocalStorageLink(node: arkts.StructDeclaration): void { + private checkLocalStorageLink(node: arkts.ClassDeclaration): void { + if (!node.definition) { + return; + } // Check if @Entry decorator exists with parameter - const entryDecorator = getAnnotationUsage(node, PresetDecorators.ENTRY); - const isStorageUsed = entryDecorator && entryDecorator.properties[0]; + const entryDecorator = getAnnotationUsageByName(node.definition.annotations, PresetDecorators.ENTRY); + if (!entryDecorator || + !arkts.isClassProperty(entryDecorator.properties[0]) || + !entryDecorator.properties[0].value) { + return; + } + // Judge whether the entry decorator comes with parameters + if (arkts.nodeType(entryDecorator.properties[0].value) !== + arkts.Es2pandaAstNodeType.AST_NODE_TYPE_BOOLEAN_LITERAL) { + return; + } // Check if @LocalStorageLink exists let localStorageLinkUsed = false; node.definition.body.forEach(body => { @@ -48,7 +62,7 @@ class EntryLocalStorageCheckRule extends AbstractUISyntaxRule { }); // If @LocalStorageLink is used but @Entry(storage) is missing, report error - if (entryDecorator && localStorageLinkUsed && !isStorageUsed) { + if (entryDecorator && localStorageLinkUsed) { this.report({ node: entryDecorator, message: this.messages.entryLocalStorageCheck diff --git a/arkui-plugins/ui-syntax-plugins/rules/nested-relationship.ts b/arkui-plugins/ui-syntax-plugins/rules/nested-relationship.ts index 2f4ee91e0..49a052496 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/nested-relationship.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/nested-relationship.ts @@ -17,8 +17,9 @@ import * as arkts from '@koalaui/libarkts'; import { getIdentifierName, isAtomicComponent, - isBuildInComponent, + isBuiltInComponent, isSingleChildComponent, + isStructClassDeclaration, listToString, SINGLE_CHILD_COMPONENT } from '../utils'; @@ -34,25 +35,51 @@ class NestedRelationshipRule extends AbstractUISyntaxRule { delegateParentComponent: `The '{{componentName}}' component can only be nested in the '{{parentComponentList}}' parent component.`, }; } - public parsed(node: arkts.StructDeclaration): void { - this.checkValidParentComponent(node); - this.checkValidChildComponent(node); - this.checkSingleChildComponent(node); - this.checkNoChildComponent(node); + public checked(node: arkts.StructDeclaration): void { + if (!arkts.isCallExpression(node) || !node.expression || !arkts.isIdentifier(node.expression)) { + return; + } + const componentName: string = getIdentifierName(node.expression); + if (componentName === '') { + return; + } + const isInStruct: boolean = this.isInStructClassDeclaration(node); + if (!isInStruct) { + return; + } + const buildInComponent: boolean = isBuiltInComponent(this.context, node.expression); + if (!buildInComponent) { + return; + } + + this.checkValidParentComponent(node.expression, componentName); + this.checkValidChildComponent(node.expression, componentName); + this.checkSingleChildComponent(node.expression, componentName); + this.checkNoChildComponent(node.expression, componentName); + } + + private isInStructClassDeclaration(node: arkts.AstNode): boolean { + while (!isStructClassDeclaration(node)) { + if (!node.parent) { + return false; + } + node = node.parent; + } + return true; } - 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) || !this.context.componentsInfo) { + private checkValidParentComponent(componentIdentifier: arkts.Identifier, componentName: string): void { + if (!this.context.componentsInfo) { return; } - const componentName: string = getIdentifierName(node); - if (!this.context.componentsInfo.validParentComponent.has(componentName) || !node.parent || !node.parent.parent) { + // Check if there are any restrictions on the node parent component + if (!this.context.componentsInfo.validParentComponent.has(componentName) || !componentIdentifier.parent || + !componentIdentifier.parent.parent) { return; } - let curNode = node.parent.parent; + let curNode = componentIdentifier.parent.parent; while (!arkts.isCallExpression(curNode) || !arkts.isIdentifier(curNode.expression) || - !isBuildInComponent(this.context, curNode.expression.name)) { + !isBuiltInComponent(this.context, curNode.expression)) { if (!curNode.parent) { return; } @@ -60,41 +87,43 @@ class NestedRelationshipRule extends AbstractUISyntaxRule { } // 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)) { - const parentComponentListArray: string[] = this.context.componentsInfo.validParentComponent.get(componentName)!; - this.report({ - node: node, - message: this.messages.delegateParentComponent, - data: { - componentName: componentName, - parentComponentList: listToString(parentComponentListArray), - }, - }); + const parentComponentListArray = this.context.componentsInfo.validParentComponent.get(componentName); + if (!parentComponentListArray || parentComponentListArray.includes(parentComponentName)) { + return; } + this.report({ + node: componentIdentifier, + message: this.messages.delegateParentComponent, + data: { + componentName: componentName, + parentComponentList: listToString(parentComponentListArray), + }, + }); } - private checkValidChildComponent(node: arkts.AstNode): void { - // Check whether the node is an identifier and whether there are restrictions on subcomponents - if (!arkts.isIdentifier(node) || !this.context.componentsInfo) { + private checkValidChildComponent(componentIdentifier: arkts.Identifier, componentName: string): void { + // Check if the node's sub components have limitations + if (!this.context.componentsInfo || !this.context.componentsInfo.validChildComponent.has(componentName) || + !componentIdentifier.parent || !componentIdentifier.parent.parent) { return; } - const componentName: string = getIdentifierName(node); - if (!this.context.componentsInfo.validChildComponent.has(componentName) || !node.parent || - !arkts.isCallExpression(node.parent) || !arkts.isIdentifier(node.parent.expression)) { + let parentNode = componentIdentifier.parent; + + if (!arkts.isCallExpression(parentNode) || !arkts.isIdentifier(parentNode.expression)) { return; } - let parentNode = node.parent; const childComponentListArray = this.context.componentsInfo.validChildComponent.get(componentName); if (!childComponentListArray) { return; } let reportFlag: boolean = false; // 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)) { + parentNode.arguments.forEach(argument => { + if (!arkts.isArrowFunctionExpression(argument) || !argument.scriptFunction.body || + !arkts.isBlockStatement(argument.scriptFunction.body)) { return; } - member.statements.forEach(statement => { + argument.scriptFunction.body.statements.forEach(statement => { if (!arkts.isExpressionStatement(statement) || !statement.expression || !arkts.isCallExpression(statement.expression) || !statement.expression.expression || !arkts.isIdentifier(statement.expression.expression)) { @@ -102,8 +131,8 @@ class NestedRelationshipRule extends AbstractUISyntaxRule { } const childComponentNode = statement.expression.expression; const childComponentName = getIdentifierName(childComponentNode); - if (childComponentListArray.includes(childComponentName) || - !isBuildInComponent(this.context, childComponentName)) { + if (childComponentName === '' || childComponentListArray.includes(childComponentName) || + !isBuiltInComponent(this.context, childComponentNode)) { return; } reportFlag = true; @@ -114,7 +143,7 @@ class NestedRelationshipRule extends AbstractUISyntaxRule { return; } this.report({ - node: node, + node: componentIdentifier, message: this.messages.delegateChildrenComponentParent, data: { parentComponentName: componentName, @@ -124,12 +153,12 @@ class NestedRelationshipRule extends AbstractUISyntaxRule { } private reportDelegateChildrenComponentChildren( - parentNode: arkts.Identifier, + childComponentNode: arkts.Identifier, childComponentName: string, componentName: string ): void { this.report({ - node: parentNode, + node: childComponentNode, message: this.messages.delegateChildrenComponentChildren, data: { childComponentName: childComponentName, @@ -138,59 +167,55 @@ class NestedRelationshipRule extends AbstractUISyntaxRule { }); } - private checkSingleChildComponent(node: arkts.AstNode): void { + private checkSingleChildComponent(componentIdentifier: arkts.Identifier, componentName: string): 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) { + if (!isSingleChildComponent(this.context, componentIdentifier) || !componentIdentifier.parent) { return; } - const parentNode = node.parent; + const parentNode = componentIdentifier.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)) { + parentNode.arguments.forEach(argument => { + if (!arkts.isArrowFunctionExpression(argument) || !argument.scriptFunction.body || + !arkts.isBlockStatement(argument.scriptFunction.body)) { return; } - if (member.statements.length > SINGLE_CHILD_COMPONENT) { - this.report({ - node: node, - message: this.messages.singleChildComponent, - data: { componentName: componentName } - }); + if (argument.scriptFunction.body.statements.length <= SINGLE_CHILD_COMPONENT) { + return; } + this.report({ + node: componentIdentifier, + message: this.messages.singleChildComponent, + data: { componentName: componentName } + }); }); } - private checkNoChildComponent(node: arkts.AstNode): void { + private checkNoChildComponent(componentIdentifier: arkts.Identifier, componentName: string): 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(this.context, componentName) || !node.parent) { + if (!isAtomicComponent(this.context, componentIdentifier) || !componentIdentifier.parent) { return; } - let parentNode = node.parent; + let parentNode = componentIdentifier.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)) { + parentNode.arguments.forEach(argument => { + if (!arkts.isArrowFunctionExpression(argument) || !argument.scriptFunction.body || + !arkts.isBlockStatement(argument.scriptFunction.body)) { return; } - if (member.statements.length > 0) { - this.report({ - node: node, - message: this.messages.noChildComponent, - data: { componentName: componentName } - }); + if (argument.scriptFunction.body.statements.length === 0) { + return; } + this.report({ + node: componentIdentifier, + message: this.messages.noChildComponent, + data: { componentName: componentName } + }); }); } } 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 8673700ac..f130ffb96 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,55 @@ */ import * as arkts from '@koalaui/libarkts'; +import { isBuiltInDeclaration } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; -import { getIdentifierName } from '../utils'; - class NoChildInButtonRule extends AbstractUISyntaxRule { - public setup(): Record { - return { - noChildInButton: `The Button component with a label parameter can not have any child.`, - }; - } - - public parsed(node: arkts.AstNode): 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 (!this.isInsideStructAndBuild(node)) { - return; + public setup(): Record { + return { + noChildInButton: `The Button component with a label parameter can not have any child.`, + }; } - 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); - } - 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; + public checked(node: arkts.AstNode): void { + if (!arkts.isCallExpression(node)) { + return; + }; + const children = node.getChildren(); + if (!arkts.isIdentifier(children[0])) { + return; + } + const componentName = children[0].name; + // If the current component is 'Button' + if (componentName !== 'Button') { + return; + } + if (!isBuiltInDeclaration(children[0])) { + return; + } + // Gets and traverses all the children of the parent node + this.reportNoChildInButtonError(node); } - return isInStruct && isInBuild; - } - private reportNoChildInButtonError(parentNode: arkts.AstNode): void { - const siblings = parentNode.getChildren(); - if (!Array.isArray(siblings) || siblings.length < 3) { - return; + private reportNoChildInButtonError(node: arkts.CallExpression): void { + if (node.arguments.length < 2) { + return; + } + if (arkts.isStringLiteral(node.arguments[0]) && arkts.isArrowFunctionExpression(node.arguments[1])) { + this.report({ + node: node, + message: this.messages.noChildInButton, + fix: () => { + const startPosition = node.arguments[1].startPosition; + const endPosition = node.arguments[1].endPosition; + return { + range: [startPosition, endPosition], + code: '', + }; + }, + }); + } } - 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: '', - }; - }, - }); - } - } - }; export default NoChildInButtonRule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-id.ts b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-id.ts index 32f2b369d..c0a54d03f 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-id.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/no-duplicate-id.ts @@ -31,7 +31,7 @@ class NoDuplicateIdRule extends AbstractUISyntaxRule { }; } - public parsed(node: arkts.AstNode): void { + public checked(node: arkts.AstNode): void { const usedIds = new Map(); if (arkts.isBlockStatement(node)) { this.findAndValidateIds(node, usedIds); 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 5940191f6..030d3ed3b 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 @@ -15,37 +15,35 @@ import * as arkts from '@koalaui/libarkts'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; -import { isBuiltInAttribute } from '../utils'; +import { isBuiltInAttribute, isStructClassDeclaration } from '../utils'; class NoSameAsBuiltInAttributeRule extends AbstractUISyntaxRule { - public setup(): Record { - return { - 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; + public setup(): Record { + return { + duplicateName: `The struct '{{structName}}' cannot have the same name as the built-in attribute '{{builtInName}}'.`, + }; } - 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 } - }); + + public checked(node: arkts.AstNode): void { + if (!isStructClassDeclaration(node)) { + return; + } + if (!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, + range: [structIdent.startPosition, structIdent.endPosition], + message: this.messages.duplicateName, + data: { structName, builtInName } + }); + } } - } }; -export default NoSameAsBuiltInAttributeRule; +export default NoSameAsBuiltInAttributeRule; \ No newline at end of file diff --git a/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts b/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts index 57901a88d..2a1beb356 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/reusable-component-in-V2-check.ts @@ -15,65 +15,54 @@ import * as arkts from '@koalaui/libarkts'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; -import { getAnnotationUsage, PresetDecorators } from '../utils'; +import { getAnnotationUsageByName, isStructClassDeclaration, PresetDecorators } from '../utils'; class ReusableComponentInV2CheckRule extends AbstractUISyntaxRule { - private reusableStructName: string[] = []; - public setup(): Record { return { noReusableV1InComponentV2: `When a custom component is decorated with @ComponentV2 and contains a child component decorated with @Reusable, the child component will not create.`, }; } - public beforeTransform(): void { - this.reusableStructName = []; - } - public parsed(node: arkts.StructDeclaration): void { - this.initStructName(node); - this.checkNoReusableV1InComponentV2(node); - } - - private initStructName(node: arkts.AstNode): void { - if (arkts.nodeType(node) !== arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) { + public checked(node: arkts.AstNode): void { + if (!arkts.isCallExpression(node) || !node.expression || !arkts.isMemberExpression(node.expression) || + !node.expression.object || !arkts.isIdentifier(node.expression.object)) { return; } - //Go through all the children of Program - for (const childNode of node.getChildren()) { - // Check whether the type is struct - if (!arkts.isStructDeclaration(childNode)) { - continue; - } - const reusableV1Decorator = getAnnotationUsage(childNode, PresetDecorators.REUSABLE_V1); - const structName = childNode.definition?.ident?.name; - if (reusableV1Decorator && structName) { - this.reusableStructName.push(structName); - } + + const structNode = arkts.getDecl(node.expression.object); + if (!structNode || !arkts.isClassDefinition(structNode) || !structNode.parent || + !isStructClassDeclaration(structNode.parent)) { + return; } - } - private checkNoReusableV1InComponentV2(node: arkts.AstNode,): void { - if (!arkts.isCallExpression(node) || !arkts.isIdentifier(node.expression)) { + const reusableV1Annotation = getAnnotationUsageByName(structNode.annotations, PresetDecorators.REUSABLE_V1); + if (!reusableV1Annotation) { return; } - if (this.reusableStructName.includes(node.expression.name)) { - // Traverse upwards to find the custom component. - let structNode: arkts.AstNode = node; - while (!arkts.isStructDeclaration(structNode)) { - if (!structNode.parent) { - return; - } - structNode = structNode.parent; - } - const annotationsList = structNode.definition.annotations; - // Check that the current component is decorated by the @ComponentV2 decorator - if (annotationsList?.some((annotation: any) => annotation.expr.name === PresetDecorators.COMPONENT_V2)) { - this.report({ - node: node, - message: this.messages.noReusableV1InComponentV2, - }); + + let curStructNode: arkts.AstNode = node; + while (!isStructClassDeclaration(curStructNode)) { + if (!curStructNode.parent) { + return; } + curStructNode = curStructNode.parent; + } + if (!curStructNode.definition) { + return; + } + + const componentV2Annotation = getAnnotationUsageByName( + curStructNode.definition.annotations, + PresetDecorators.COMPONENT_V2 + ); + if (!componentV2Annotation) { + return; } + this.report({ + node: node, + message: this.messages.noReusableV1InComponentV2, + }); } } diff --git a/arkui-plugins/ui-syntax-plugins/rules/specific-component-children.ts b/arkui-plugins/ui-syntax-plugins/rules/specific-component-children.ts index aace72ff3..26c7b7501 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/specific-component-children.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/specific-component-children.ts @@ -16,8 +16,8 @@ import * as arkts from '@koalaui/libarkts'; import { getIdentifierName, - PresetDecorators, SINGLE_CHILD_COMPONENT, + TOGGLE, TOGGLE_TYPE, ToggleType, TYPE @@ -32,29 +32,26 @@ class SpecificComponentChildrenRule extends AbstractUISyntaxRule { }; } - public parsed(node: arkts.StructDeclaration): void { - if (!arkts.isCallExpression(node) || !node.expression) { - return; - } - // Check whether the current node is an identifier and toggle component - if (!arkts.isIdentifier(node.expression)) { + public checked(node: arkts.AstNode): void { + // Check whether the current node is an CallExpression and toggle component + if (!arkts.isCallExpression(node) || !node.expression || !arkts.isIdentifier(node.expression)) { return; } const componentName: string = getIdentifierName(node.expression); - if (componentName !== PresetDecorators.TOGGLE) { + if (componentName !== TOGGLE) { return; } const toggleTypeValue = this.getToggleType(node); if (toggleTypeValue === '') { return; } - // If there is more than one subComponent in the BlockStatement, an error is reported - node.getChildren().forEach(member => { - if (!arkts.isBlockStatement(member)) { + node.arguments.forEach(arg => { + if (!arkts.isArrowFunctionExpression(arg) || !arg.scriptFunction.body || + !arkts.isBlockStatement(arg.scriptFunction.body)) { return; } // If the toggle component type is checkbox and has child components, an error is reported - if (toggleTypeValue === ToggleType.CHECKBOX && member.statements.length > 0) { + if (toggleTypeValue === ToggleType.CHECKBOX && arg.scriptFunction.body.statements.length > 0) { this.report({ node: node, message: this.messages.toggleTypeCheckboxWithNoChild, @@ -66,7 +63,8 @@ class SpecificComponentChildrenRule extends AbstractUISyntaxRule { }); } // If the toggle component type is button and there is more than one child component, an error is reported - if (toggleTypeValue === ToggleType.BUTTON && member.statements.length > SINGLE_CHILD_COMPONENT) { + if (toggleTypeValue === ToggleType.BUTTON && + arg.scriptFunction.body.statements.length > SINGLE_CHILD_COMPONENT) { this.report({ node: node, message: this.messages.toggleTypeButtonWithSingleChild, diff --git a/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts b/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts index a8f2da56f..e9922a3f3 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/wrap-builder-check.ts @@ -14,72 +14,49 @@ */ import * as arkts from '@koalaui/libarkts'; -import { getIdentifierName, PresetDecorators, WRAP_BUILDER, getFunctionAnnotationUsage } from '../utils'; +import { getIdentifierName, PresetDecorators, WRAP_BUILDER, getAnnotationUsageByName } from '../utils'; import { AbstractUISyntaxRule } from './ui-syntax-rule'; class StructNoExtendsRule extends AbstractUISyntaxRule { - private builderFunctionNames: string[] = []; - public setup(): Record { return { invalidWrapBuilderCheck: 'The wrapBuilder\'s parameter should be @Builder function.', }; } - public beforeTransform(): void { - this.builderFunctionNames = []; - } - - public parsed(node: arkts.StructDeclaration): void { - this.collectBuilderFunctions(node); - this.validateWrapBuilderInIdentifier(node); - } - - // Collect all the function names that are decorated with @Builder - private collectBuilderFunctions(node: arkts.AstNode): void { - if (!arkts.isEtsScript(node)) { + public checked(node: arkts.StructDeclaration): void { + // Check that the current node is a CallExpression node named wrapBuilder + if (!arkts.isCallExpression(node) || !node.expression || !arkts.isIdentifier(node.expression)) { return; } - node.statements.forEach((statement) => { - if (!arkts.isFunctionDeclaration(statement)) { - return; - } - const buildDecoratorUsage = getFunctionAnnotationUsage(statement, PresetDecorators.BUILDER); - if (!buildDecoratorUsage) { - return; - } - const functionName = statement.scriptFunction.id?.name; - if (!functionName || functionName === '' || this.builderFunctionNames.includes(functionName)) { - return; - } - this.builderFunctionNames.push(functionName); - }); - } - - private validateWrapBuilderInIdentifier(node: arkts.AstNode): void { - if (!arkts.isCallExpression(node) || !node.expression) { + const componentName: string = getIdentifierName(node.expression); + if (componentName !== WRAP_BUILDER) { return; } - // If the current node is not a wrap builder, return - if (!arkts.isIdentifier(node.expression) || getIdentifierName(node.expression) !== WRAP_BUILDER) { + + // If the parameter is not 1, other errors will be reported + if (node.arguments.length !== 1) { return; } - let functionName: string = ''; - // Get the parameters of the wrap builder - node.arguments.forEach(argument => { - if (!arkts.isIdentifier(argument)) { + const funcIdentifier = node.arguments[0]; + if (arkts.isIdentifier(funcIdentifier)) { + const functionNode = arkts.getDecl(node.arguments[0]); + if (!functionNode || !arkts.isMethodDefinition(functionNode) || + !arkts.isFunctionExpression(functionNode.funcExpr) || + !arkts.isScriptFunction(functionNode.funcExpr.scriptFunction)) { + return; + } + // If the parameter is a Builder decorated method, no error will be reported + const builderDecorator = getAnnotationUsageByName(functionNode.funcExpr.scriptFunction.annotations, + PresetDecorators.BUILDER); + if (builderDecorator) { return; } - functionName = argument.name; - }); - // If the function name is not empty and not decorated by the @builder, an error is reported - if (functionName === '' || !this.builderFunctionNames.includes(functionName)) { - const errorNode = node.arguments[0]; - this.report({ - node: errorNode, - message: this.messages.invalidWrapBuilderCheck, - }); } + this.report({ + node: funcIdentifier, + message: this.messages.invalidWrapBuilderCheck, + }); } } diff --git a/arkui-plugins/ui-syntax-plugins/utils/index.ts b/arkui-plugins/ui-syntax-plugins/utils/index.ts index 0e3f34341..6ebe19a03 100644 --- a/arkui-plugins/ui-syntax-plugins/utils/index.ts +++ b/arkui-plugins/ui-syntax-plugins/utils/index.ts @@ -404,35 +404,48 @@ export function isBuiltInAttribute(context: UISyntaxRuleContext, attributeName: } return context.componentsInfo.builtInAttributes.includes(attributeName); } -export function isBuildInComponent(context: UISyntaxRuleContext, componentName: string): boolean { + +export function isBuiltInComponent(context: UISyntaxRuleContext, componentIdentifier: arkts.Identifier): boolean { if (!context.componentsInfo) { return false; } + if (!isBuiltInDeclaration(componentIdentifier)) { + return false; + } return ( - context.componentsInfo.containerComponents.includes(componentName) || - context.componentsInfo.atomicComponents.includes(componentName) + context.componentsInfo.containerComponents.includes(componentIdentifier.name) || + context.componentsInfo.atomicComponents.includes(componentIdentifier.name) ); } -export function isAtomicComponent(context: UISyntaxRuleContext, componentName: string): boolean { +export function isAtomicComponent(context: UISyntaxRuleContext, componentIdentifier: arkts.Identifier): boolean { if (!context.componentsInfo) { return false; } - return context.componentsInfo.atomicComponents.includes(componentName); + if (!isBuiltInDeclaration(componentIdentifier)) { + return false; + } + return context.componentsInfo.atomicComponents.includes(componentIdentifier.name); } -export function isContainerComponent(context: UISyntaxRuleContext, componentName: string): boolean { +export function isContainerComponent(context: UISyntaxRuleContext, componentIdentifier: arkts.Identifier): boolean { if (!context.componentsInfo) { return false; } - return context.componentsInfo.containerComponents.includes(componentName); + if (!isBuiltInDeclaration(componentIdentifier)) { + return false; + } + return context.componentsInfo.containerComponents.includes(componentIdentifier.name); } -export function isSingleChildComponent(context: UISyntaxRuleContext, componentName: string): boolean { +export function isSingleChildComponent(context: UISyntaxRuleContext, componentIdentifier: arkts.Identifier): boolean { if (!context.componentsInfo) { return false; } - return context.componentsInfo.singleChildComponents.includes(componentName); + if (!isBuiltInDeclaration(componentIdentifier)) { + return false; + } + return context.componentsInfo.singleChildComponents.includes(componentIdentifier.name); } export function readJSON(path: string): T | null { @@ -554,4 +567,96 @@ export const TypeFlags = { export function getCurrentFilePath(node: arkts.AstNode): string | undefined { const program = arkts.getProgramFromAstNode(node); return program.absName; +} + +export function isClassDeclaration(node: arkts.AstNode): node is arkts.ClassDeclaration { + return ( + arkts.isClassDeclaration(node) && !!node.definition && !arkts.classDefinitionIsFromStructConst(node.definition) + ); +} + +export function isBuiltInDeclaration(node: arkts.Identifier): boolean { + const declaration = arkts.getDecl(node); + if (!declaration) { + return false; + } + const program = arkts.getProgramFromAstNode(declaration); + if (!isFromPresetModules(program.moduleName)) { + return false; + } + return true; +} + +export function getAnnotationNames( + annotations: readonly arkts.AnnotationUsage[] +): string[] { + const validNames: string[] = []; + + for (const annotation of annotations) { + if (!annotation.expr || + !arkts.isIdentifier(annotation.expr) || + !annotation.expr.name) { + continue; + } + const annotationDeclaration = arkts.getDecl(annotation.expr); + if (!annotationDeclaration) { + continue; + } + + const program = arkts.getProgramFromAstNode(annotationDeclaration); + if (!isFromPresetModules(program.moduleName)) { + continue; + } + + validNames.push(annotation.expr.name); + } + + return validNames; +} + +export function isBuildInDecorator(annotation: arkts.AnnotationUsage): boolean { + if (annotation.expr && arkts.isIdentifier(annotation.expr) && isBuiltInDeclaration(annotation.expr)) { + return true; + } + return false; +} + +export const GLOBAL_CLASS_NAME = "ETSGLOBAL"; + +export const INIT_METHOD_NAME = "_$init$_"; +export const MAIN_METHOD_NAME = "main"; + +export function isInEtsGlobalClassDeclaration(node: arkts.AstNode): boolean { + while (node) { + if (isClassDeclaration(node)) { + if (node.definition?.ident?.name === GLOBAL_CLASS_NAME) { + return true; + } else { + return false; + } + } + if (!node.parent) { + return false; + } + node = node.parent; + } + return false; +} + +export function isFunctionDeclaration(node: arkts.AstNode): node is arkts.MethodDefinition { + if (!arkts.isMethodDefinition(node) || !node.isStatic || !node.scriptFunction.id) { + return false; + } + const methodName = node.scriptFunction.id.name; + return ( + methodName !== INIT_METHOD_NAME && + methodName !== MAIN_METHOD_NAME && + isInEtsGlobalClassDeclaration(node) + ); +} + +export function isVariableDeclaration(node: arkts.AstNode): node is arkts.ClassProperty { + return ( + arkts.isClassProperty(node) && isInEtsGlobalClassDeclaration(node) + ); } \ No newline at end of file -- Gitee