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 86ac32a63a9a5f6e23085b19589db8666148fd74..a914891f9a65984a9b399a34ac34dbb40f4000cf 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/build-root-node.ts @@ -18,6 +18,7 @@ import { getIdentifierName, getAnnotationUsage, PresetDecorators, BUILD_NAME } f import { AbstractUISyntaxRule } from './ui-syntax-rule'; const STATEMENT_LENGTH: number = 1; +const BUILD_COUNT_LIMIT: number = 1; class BuildRootNodeRule extends AbstractUISyntaxRule { public setup(): Record { @@ -37,45 +38,68 @@ class BuildRootNodeRule extends AbstractUISyntaxRule { return; } const blockStatement = member.scriptFunction.body; - if (!blockStatement || !arkts.isBlockStatement(blockStatement)) { - return; - } const buildNode = member.scriptFunction.id; - if (!buildNode) { + if (!blockStatement || !arkts.isBlockStatement(blockStatement) || !buildNode) { return; } const statements = blockStatement.statements; + let buildCount = 0; + // rule1: The 'build' method cannot have more than one root node. 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 (!this.isBuildOneRoot(statements, buildCount)) { + this.report({ + node: buildNode, + message: entryDecoratorUsage ? this.messages.invalidEntryBuildRoot : this.messages.invalidBuildRoot, + }); + } } + // rule2: its 'build' function can have only one root node, which must be a container component. if (!statements.length || !entryDecoratorUsage) { return; } - const expressionStatement = statements[0]; - if (!arkts.isExpressionStatement(expressionStatement)) { - return; - } - const callExpression = expressionStatement.expression; - if (!arkts.isCallExpression(callExpression)) { + this.validateContainerInBuild(statements, buildNode); + }); + } + + private isBuildOneRoot( + statements: readonly arkts.Statement[], + buildCount: number + ): boolean { + statements.forEach(statement => { + if (!arkts.isExpressionStatement(statement)) { return; } - let componentName: string | undefined = this.getComponentName(callExpression); - if (!componentName) { + if (!statement.expression) { 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, - }); + const componentName = this.getComponentName(statement.expression); + if (componentName && componentName !== 'hilog') { + buildCount++; } }); + return buildCount <= BUILD_COUNT_LIMIT; + } + + private validateContainerInBuild(statements: readonly arkts.Statement[], buildNode: arkts.Identifier): void { + const expressionStatement = statements[0]; + if (!arkts.isExpressionStatement(expressionStatement)) { + return; + } + const callExpression = expressionStatement.expression; + if (!arkts.isCallExpression(callExpression)) { + return; + } + let componentName = this.getComponentName(callExpression); + if (!componentName) { + return; + } + let isContainer = this.isContainerComponent(componentName); + if (!isContainer) { + this.report({ + node: buildNode, + message: this.messages.invalidEntryBuildRoot, + }); + } } private isContainerComponent(componentName: string): boolean { 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 cbe92f65081245e468335175d815ca4978c0f0f7..b94a235d980177e4ec16dda03497843bacd6bcb6 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 @@ -56,7 +56,7 @@ class ComponentV2StateUsageValidationRule extends AbstractUISyntaxRule { propertyDecorators: string[]): void { const appliedBuiltInDecorators = propertyDecorators.filter(d => builtInDecorators.includes(d)); if (appliedBuiltInDecorators.length > 1) { - member.annotations?.forEach(annotation => { + member.annotations.forEach(annotation => { if (annotation.expr && arkts.isIdentifier(annotation.expr)) { const annotationsName = annotation.expr.name; this.reportMultipleBuiltInDecoratorsError(annotation, annotationsName, builtInDecorators); @@ -96,7 +96,7 @@ class ComponentV2StateUsageValidationRule extends AbstractUISyntaxRule { private checkRequireOnlyWithParam(member: arkts.ClassProperty, propertyDecorators: string[], isComponentV2: boolean): void { - const requireDecorator = member.annotations?.find(annotation => + const requireDecorator = member.annotations.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.REQUIRE ); if (isComponentV2 && @@ -119,7 +119,7 @@ class ComponentV2StateUsageValidationRule extends AbstractUISyntaxRule { }; private checkuseStateDecoratorsWithProperty(method: arkts.MethodDefinition): void { - method.scriptFunction.annotations?.forEach(annotation => { + 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); @@ -146,7 +146,6 @@ class ComponentV2StateUsageValidationRule extends AbstractUISyntaxRule { 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; @@ -203,11 +202,11 @@ class ComponentV2StateUsageValidationRule extends AbstractUISyntaxRule { } node.getChildren().forEach((member) => { if (!arkts.isStructDeclaration(member) || !member.definition.ident || - !this.checkDecorator(member?.definition.annotations, PresetDecorators.COMPONENT_V2)) { + !this.checkDecorator(member.definition.annotations, PresetDecorators.COMPONENT_V2)) { return; } - let structName: string = member.definition.ident?.name ?? ''; - member.definition?.body?.forEach((item) => { + let structName: string = member.definition.ident.name; + member.definition.body.forEach((item) => { this.processClassPropertyAnnotations(item, structName, componentV2PropertyMap); }); }); 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 9c86d806ec856082b070c33fa694e08f8af9e540..b4b986383c9d97969527f0d652c4ee2e66a342c3 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 @@ -18,29 +18,29 @@ import { getIdentifierName, MultiMap, PresetDecorators, getAnnotationName } from import { AbstractUISyntaxRule } from './ui-syntax-rule'; class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { - private componentv2WithConsumer: MultiMap = new MultiMap(); - private componentv2WithProvider: MultiMap = new MultiMap(); + private componentV2WithConsumer: MultiMap = new MultiMap(); + private componentV2WithProvider: MultiMap = new MultiMap(); public setup(): Record { return { - consumerOnlyOnMember: `'@{{decorator}}' can only decorate member property.`, + providerAndConsumerOnlyOnProperty: `'@{{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'.`, + providerAndConsumerOnlyInStruct: `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).`, }; } public parsed(node: arkts.AstNode): void { this.collectStructsWithConsumerAndProvider(node); - this.validateStructDecoratorsAndMembers(node); - this.validateInClass(node); + this.validateDecoratorsOnMember(node); + this.validateOnlyInStruct(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 @@ -59,7 +59,7 @@ class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { private rememberStructName(node: arkts.AstNode): void { if (arkts.isStructDeclaration(node)) { - node?.definition?.annotations.forEach((anno) => { + node.definition.annotations.forEach((anno) => { if (!anno.expr) { return; } @@ -77,35 +77,38 @@ class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { 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 => + const consumerDecorator = member.annotations.some(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.CONSUMER ); - const providerDecorator = member?.annotations.some(annotation => + const providerDecorator = member.annotations.some(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === PresetDecorators.PROVIDER ); - if (!member?.key) { + if (!member.key) { return; } - const memberName = getIdentifierName(member?.key); - if (comsumerDecorator && structName && memberName) { - this.componentv2WithConsumer.add(structName, memberName); + const memberName = getIdentifierName(member.key); + if (consumerDecorator && structName && memberName) { + this.componentV2WithConsumer.add(structName, memberName); } if (providerDecorator && structName && memberName) { - this.componentv2WithProvider.add(structName, memberName); + this.componentV2WithProvider.add(structName, memberName); } } }); } - private validateStructDecoratorsAndMembers(node: arkts.AstNode): void { + private validateDecoratorsOnMember(node: arkts.AstNode): void { + if (arkts.isScriptFunction(node) || arkts.isVariableDeclaration(node) || arkts.isTSInterfaceDeclaration(node) || + arkts.isTSTypeAliasDeclaration(node)) { + this.validateDecorator(node, this.messages.providerAndConsumerOnlyOnProperty, PresetDecorators.CONSUMER); + this.validateDecorator(node, this.messages.providerAndConsumerOnlyOnProperty, PresetDecorators.PROVIDER); + } + if (arkts.isStructDeclaration(node)) { node.definition.body.forEach(member => { - if (arkts.isMethodDefinition(member)) { - this.validateDecoratorOnMethod(member); - } if (arkts.isClassProperty(member)) { this.validateMemberDecorators(member); } @@ -146,80 +149,74 @@ class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { }); } - private findDecorator(member: arkts.ClassProperty, decoratorName: string): arkts.AnnotationUsage | undefined { - return member.annotations?.find(annotation => + private findDecorator( + member: arkts.ClassProperty | arkts.VariableDeclaration | arkts.FunctionDeclaration | + arkts.ScriptFunction | arkts.TSInterfaceDeclaration | arkts.TSTypeAliasDeclaration, + decoratorName: string + ): arkts.AnnotationUsage | undefined { + return member.annotations.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name === decoratorName ); } private findOtherDecorator(member: arkts.ClassProperty, decoratorName: string): arkts.AnnotationUsage | undefined { - return member.annotations?.find(annotation => + return member.annotations.find(annotation => annotation.expr && arkts.isIdentifier(annotation.expr) && annotation.expr.name !== decoratorName ); } - 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 - ); - } - - private validateDecoratorOnMethod(member: arkts.MethodDefinition): void { - this.validateDecorator(member, PresetDecorators.CONSUMER); - this.validateDecorator(member, PresetDecorators.PROVIDER); - } - - private validateDecorator(member: arkts.MethodDefinition, decoratorName: string): void { - const decorator = this.findDecoratorInMethod(member, decoratorName); - if (!decorator) { - return; - } - - this.report({ - node: decorator, - message: this.messages.consumerOnlyOnMember, - data: { - decorator: getAnnotationName(decorator), - }, - fix: (decorator) => { - const startPosition = decorator.startPosition; - const endPosition = decorator.endPosition; - return { - range: [startPosition, endPosition], - code: '', - }; - } - }); - } + private validateOnlyInStruct(node: arkts.AstNode): void { - 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); + this.validateDecorator(member, this.messages.providerAndConsumerOnlyInStruct, PresetDecorators.CONSUMER); + this.validateDecorator(member, this.messages.providerAndConsumerOnlyInStruct, PresetDecorators.PROVIDER); + } + if (arkts.isMethodDefinition(member)) { + this.validateDecorator( + member.scriptFunction, this.messages.providerAndConsumerOnlyInStruct, PresetDecorators.CONSUMER); + this.validateDecorator( + member.scriptFunction, this.messages.providerAndConsumerOnlyInStruct, PresetDecorators.PROVIDER); } }); + return; + } + + // function/ variable/ interface/ type alias declaration + if (arkts.isFunctionDeclaration(node) || + arkts.isVariableDeclaration(node) || + arkts.isTSInterfaceDeclaration(node) || + arkts.isTSTypeAliasDeclaration(node) + ) { + this.validateDecorator(node, this.messages.providerAndConsumerOnlyInStruct, PresetDecorators.CONSUMER); + this.validateDecorator(node, this.messages.providerAndConsumerOnlyInStruct, PresetDecorators.PROVIDER); + return; } } - private validateDecoratorInClass(member: arkts.ClassProperty, decoratorName: string): void { - const decorator = this.findDecorator(member, decoratorName); + private validateDecorator( + node: arkts.ClassProperty | arkts.VariableDeclaration | arkts.FunctionDeclaration | + arkts.ScriptFunction | arkts.TSInterfaceDeclaration | arkts.TSTypeAliasDeclaration, + message: string, + decoratorName: string + ): void { + const decorator = this.findDecorator(node, decoratorName); if (!decorator) { return; } + this.report({ node: decorator, - message: this.messages.providerOnlyInStruct, + message: message, data: { decorator: getAnnotationName(decorator), }, - fix: (providerDecorator) => { - const startPosition = providerDecorator.startPosition; - const endPosition = providerDecorator.endPosition; + fix: (decorator) => { + const startPosition = decorator.startPosition; + const endPosition = decorator.endPosition; return { range: [startPosition, endPosition], code: '', @@ -233,7 +230,7 @@ class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { return; } const callExpName: string = node.expression.name; - if (this.componentv2WithConsumer.has(callExpName)) { + if (this.componentV2WithConsumer.has(callExpName)) { const queue: Array = [node]; while (queue.length > 0) { const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; @@ -253,7 +250,7 @@ class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { return; } const callExpName: string = node.expression.name; - if (this.componentv2WithProvider.has(callExpName)) { + if (this.componentV2WithProvider.has(callExpName)) { const queue: Array = [node]; while (queue.length > 0) { const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode; @@ -270,7 +267,7 @@ class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { private checkInvalidConsumerUsage(currentNode: arkts.Identifier, callExpName: string): void { const parent = currentNode.parent; - if (parent && this.componentv2WithConsumer.get(callExpName).includes(getIdentifierName(currentNode))) { + if (parent && this.componentV2WithConsumer.get(callExpName).includes(getIdentifierName(currentNode))) { this.report({ node: parent, message: this.messages.forbiddenInitialization, @@ -293,7 +290,7 @@ class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { private checkInvalidProviderUsage(currentNode: arkts.Identifier, callExpName: string): void { const parent = currentNode.parent; - if (parent && this.componentv2WithProvider.get(callExpName)?.includes(getIdentifierName(currentNode))) { + if (parent && this.componentV2WithProvider.get(callExpName)?.includes(getIdentifierName(currentNode))) { this.report({ node: parent, message: this.messages.forbiddenInitialization, @@ -315,4 +312,5 @@ class ConsumerProviderDecoratorCheckRule extends AbstractUISyntaxRule { } } -export default ConsumerProviderDecoratorCheckRule; \ No newline at end of file +export default ConsumerProviderDecoratorCheckRule; + diff --git a/arkui-plugins/ui-syntax-plugins/rules/index.ts b/arkui-plugins/ui-syntax-plugins/rules/index.ts index 6c622d46e763a157c1f20bc95988c7b9b912a5c4..f72189a2325017933eee23eae71a7e1c9fc8b755 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/index.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/index.ts @@ -68,58 +68,27 @@ import SpecificComponentChildren from './specific-component-children'; import StructNoExtends from './struct-no-extends'; const rules: Array = [ - AttributeNoInvoke, - BuilderparamDecoratorCheck, [BuildRootNodeRule, 'error'], - CheckConstructPrivateParameter, [CheckDecoratedPropertyTypeRule, 'error'], [ComponentComponentV2MixUseCheckRule, 'error'], [ComponentV2MixCheckRule, 'error'], - ConstructParameterLiteral, [ConstructParameterRule, 'error'], [ConsumerProviderDecoratorCheckRule, 'error'], [ComponentV2StateUsageValidationRule, 'error'], [CustomDialogMissingControllerRule, 'error'], - EntryLoacalStorageCheck, - EntryStructNoExport, [MainPagesEntryCheckRule, 'error'], [MonitorDecoratorCheckRule, 'error'], [NestedRelationshipRule, 'error'], [NestedReuseComponentCheckRule, 'error'], [NoChildInButtonRule, 'error'], - NoDuplicateDecorators, [NoDuplicateEntryRule, 'error'], - NoDuplicateId, [NoDuplicatePreviewRule, 'error'], [NoDuplicateStateManagerRule, 'error'], - NoPropLinkObjectlinkInEntry, [NoSameAsBuiltInAttributeRule, 'error'], - ReuseAttributeCheck, - StructMissingDecorator, - StructPropertyDecorator, - StructPropertyOptional, - StructVariableInitialization, - TrackDecoratorCheck, - TypeDecoratorCheck, - ValidateBuildInStruct, - ValidateDecoratorTarget, - WatchDecoratorFunction, - WatchDecoratorRegular, - WrapBuilderCheck, [ObservedHeritageCompatibleCheckRule, 'error'], [ObservedObservedV2Rule, 'error'], - ObservedV2TraceUsageValidation, - OnceDecoratorCheck, - OneDecoratorOnFunctionMethod, - OldNewDecoratorMixUseCheck, [ComputedDecoratorCheckRule, 'error'], - ReusableV2DecoratorCheck, - RequireDecoratorRegular, - ReusableComponentInV2Check, - VariableInitializationViaComponentConstructor, [ComponentComponentV2InitCheckRule, 'error'], - SpecificComponentChildren, - StructNoExtends, ]; export default rules; diff --git a/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts b/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts index d614ba7769ec2ef520180f221cf6561cc3dffc47..1624ddb3a6642df26b70b32c92879f62ca6a5b09 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/reuse-attribute-check.ts @@ -40,12 +40,13 @@ function validateReuseOrReuseIdUsage(context: UISyntaxRuleContext, node: arkts.M // Gets the reuse or reuseId attribute const decoratedNode = node.property; if (arkts.isCallExpression(structNode)) { - const Node = structNode.expression; - if (arkts.isIdentifier(Node) && arkts.isIdentifier(decoratedNode)) { - if (decoratedNode.name === ReuseConstants.REUSE && !reusableV2ComponentV2Struct.includes(Node.name)) { + const nodeExpression = structNode.expression; + if (arkts.isIdentifier(nodeExpression) && arkts.isIdentifier(decoratedNode)) { + if (decoratedNode.name === ReuseConstants.REUSE && !reusableV2ComponentV2Struct.includes(nodeExpression.name)) { reportInvalidReuseUsage(context, node, decoratedNode, rule); } - else if (decoratedNode.name === ReuseConstants.REUSE_ID && reusableV2ComponentV2Struct.includes(Node.name)) { + else if (decoratedNode.name === ReuseConstants.REUSE_ID && + reusableV2ComponentV2Struct.includes(nodeExpression.name)) { reportInvalidReuseIdUsage(context, node, decoratedNode, rule); } } diff --git a/arkui-plugins/ui-syntax-plugins/rules/struct-variable-initialization.ts b/arkui-plugins/ui-syntax-plugins/rules/struct-variable-initialization.ts index 67617caf5b163dec057ad3232c83bd196b15a86e..ea5fe4ebacb38d9eab6a8f3e67cb8b12f7c403ad 100644 --- a/arkui-plugins/ui-syntax-plugins/rules/struct-variable-initialization.ts +++ b/arkui-plugins/ui-syntax-plugins/rules/struct-variable-initialization.ts @@ -29,7 +29,6 @@ const mustInitializeDecorators = [ // Disables a list of decorators that are initialized locally const prohibitInitializeDecorators = [ PresetDecorators.LINK, - PresetDecorators.CONSUME, PresetDecorators.OBJECT_LINK, ];