1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import * as arkts from '@koalaui/libarkts'; 17import { getClassPropertyAnnotationNames, PresetDecorators, getAnnotationUsage } from '../utils'; 18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 19 20// Helper functions for rules 21const hasisComponentV2 = (node: arkts.StructDeclaration): boolean => !!getAnnotationUsage(node, 22 PresetDecorators.COMPONENT_V2); 23 24const hasComponent = (node: arkts.StructDeclaration): boolean => !!getAnnotationUsage(node, 25 PresetDecorators.COMPONENT_V1); 26function checkMultipleBuiltInDecorators(context: UISyntaxRuleContext, member: arkts.ClassProperty, 27 propertyDecorators: string[]): void { 28 const builtInDecorators = [PresetDecorators.LOCAL, PresetDecorators.PARAM, PresetDecorators.EVENT]; 29 const appliedBuiltInDecorators = propertyDecorators.filter(d => builtInDecorators.includes(d)); 30 if (appliedBuiltInDecorators.length > 1) { 31 member.annotations?.forEach(annotation => { 32 const annotationsName = annotation.expr?.dumpSrc(); 33 reportMultipleBuiltInDecoratorsError(context, annotation, annotationsName, builtInDecorators); 34 }); 35 } 36}; 37 38function reportMultipleBuiltInDecoratorsError(context: UISyntaxRuleContext, annotation: arkts.AstNode, 39 annotationsName: string | undefined, builtInDecorators: string[]): void { 40 if (annotationsName && builtInDecorators.includes(annotationsName)) { 41 context.report({ 42 node: annotation, 43 message: rule.messages.multipleBuiltInDecorators, 44 fix: (annotation) => { 45 const startPosition = arkts.getStartPosition(annotation); 46 const endPosition = arkts.getEndPosition(annotation); 47 return { 48 range: [startPosition, endPosition], 49 code: '', 50 }; 51 }, 52 }); 53 } 54} 55 56function checkDecoratorOnlyInisComponentV2(context: UISyntaxRuleContext, member: arkts.ClassProperty, 57 node: arkts.StructDeclaration, hasisComponentV2: boolean, hasComponent: boolean): void { 58 const builtInDecorators = [PresetDecorators.LOCAL, PresetDecorators.PARAM, PresetDecorators.EVENT]; 59 member.annotations?.forEach(annotation => { 60 const annotationsName = annotation.expr?.dumpSrc(); 61 if (annotationsName && builtInDecorators.includes(annotationsName) && !hasisComponentV2 && !hasComponent) { 62 reportDecoratorOnlyInisComponentV2Error(context, annotation, annotationsName, node); 63 } 64 }); 65}; 66 67function reportDecoratorOnlyInisComponentV2Error(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage, 68 annotationsName: string, node: arkts.StructDeclaration): void { 69 context.report({ 70 node: annotation, 71 message: rule.messages.decoratorOnlyInisComponentV2, 72 data: { annotationsName }, 73 fix: (annotation) => { 74 const startPosition = arkts.getStartPosition(node); 75 return { 76 range: [startPosition, startPosition], 77 code: `@${PresetDecorators.COMPONENT_V2}\n`, 78 }; 79 }, 80 }); 81} 82 83function checkParamRequiresRequire(context: UISyntaxRuleContext, member: arkts.ClassProperty, 84 propertyDecorators: string[]): void { 85 if (propertyDecorators.includes(PresetDecorators.PARAM) && !member.value && 86 !propertyDecorators.includes(PresetDecorators.REQUIRE) && member.key) { 87 const memberKey = member.key; 88 context.report({ 89 node: memberKey, 90 message: rule.messages.paramRequiresRequire, 91 fix: (memberKey) => { 92 const startPosition = arkts.getStartPosition(memberKey); 93 return { 94 range: [startPosition, startPosition], 95 code: `@${PresetDecorators.REQUIRE} `, 96 }; 97 }, 98 }); 99 } 100}; 101 102function checkRequireOnlyWithParam(context: UISyntaxRuleContext, member: arkts.ClassProperty, 103 propertyDecorators: string[]): void { 104 const requireDecorator = member.annotations?.find(annotation => 105 annotation.expr && annotation.expr.dumpSrc() === PresetDecorators.REQUIRE 106 ); 107 if (requireDecorator && !propertyDecorators.includes(PresetDecorators.PARAM)) { 108 context.report({ 109 node: requireDecorator, 110 message: rule.messages.requireOnlyWithParam, 111 fix: (requireDecorator) => { 112 const startPosition = arkts.getStartPosition(requireDecorator); 113 const endPosition = arkts.getEndPosition(requireDecorator); 114 return { 115 range: [startPosition, endPosition], 116 code: '', 117 }; 118 }, 119 }); 120 } 121}; 122 123function validateClassPropertyDecorators(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void { 124 const isComponentV2 = hasisComponentV2(node); 125 const isComponent = hasComponent(node); 126 node.definition.body.forEach(member => { 127 if (!arkts.isClassProperty(member)) { 128 return; 129 } 130 const propertyDecorators = getClassPropertyAnnotationNames(member); 131 132 // Rule 1: Multiple built-in decorators 133 checkMultipleBuiltInDecorators(context, member, propertyDecorators); 134 135 // Rule 2: Built-in decorators only allowed in @isComponentV2 136 checkDecoratorOnlyInisComponentV2(context, member, node, isComponentV2, isComponent); 137 138 // Rule 3: @Param without default value must be combined with @Require 139 checkParamRequiresRequire(context, member, propertyDecorators); 140 141 // Rule 4: @Require must be used together with @Param 142 checkRequireOnlyWithParam(context, member, propertyDecorators); 143 }); 144} 145 146const rule: UISyntaxRule = { 147 name: 'iscomponentV2-state-usage-validation', 148 messages: { 149 multipleBuiltInDecorators: `The member property or method cannot be decorated by multiple built-in decorators.`, 150 decoratorOnlyInisComponentV2: `The '@{{annotationsName}}' decorator can only be used in a 'struct' decorated with '@isComponentV2'.`, 151 paramRequiresRequire: `When a variable decorated with @Param is not assigned a default value, it must also be decorated with @Require.`, 152 requireOnlyWithParam: `In a struct decorated with @isComponentV2, @Require can only be used with @Param. ` 153 }, 154 155 setup(context) { 156 return { 157 parsed: (node): void => { 158 159 if (!arkts.isStructDeclaration(node)) { 160 return; 161 } 162 validateClassPropertyDecorators(context, node); 163 }, 164 }; 165 }, 166}; 167 168export default rule;