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 { getAnnotationUsage, getClassPropertyAnnotationNames, PresetDecorators } from '../utils'; 18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 19 20function findDecorator(member: arkts.ClassProperty, decorator: string): arkts.AnnotationUsage | undefined { 21 return member.annotations?.find(annotation => 22 annotation.expr && 23 annotation.expr.dumpSrc() === decorator 24 ); 25} 26 27// Check that the property decorator complies with the rules 28function validatePropertyAnnotations( 29 body: arkts.ClassProperty, 30 context: UISyntaxRuleContext, 31 hasOnceDecorator: arkts.AnnotationUsage | undefined 32): void { 33 const propertyAnnotations = getClassPropertyAnnotationNames(body); 34 hasOnceDecorator = findDecorator(body, PresetDecorators.ONCE); 35 if (hasOnceDecorator) { 36 const isParamUsed = propertyAnnotations.includes(PresetDecorators.PARAM); 37 // If @Once is found, check if @Param is also used 38 if (!isParamUsed) { 39 reportMissingParamWithOnce(hasOnceDecorator, context); 40 } else { 41 // If both @Once and @Param are used, check for other 42 // incompatible decorators 43 const otherDecorators = body.annotations?.find(annotation => 44 annotation.expr && 45 annotation.expr.dumpSrc() !== PresetDecorators.ONCE && 46 annotation.expr.dumpSrc() !== PresetDecorators.PARAM 47 ); 48 reportInvalidDecoratorsWithOnceAndParam(otherDecorators, context); 49 } 50 } 51} 52 53function reportMissingParamWithOnce( 54 hasOnceDecorator: arkts.AnnotationUsage | undefined, 55 context: UISyntaxRuleContext 56): void { 57 if (!hasOnceDecorator) { 58 return; 59 } 60 context.report({ 61 node: hasOnceDecorator, 62 message: rule.messages.invalidDecorator, 63 fix: (hasOnceDecorator) => { 64 const startPosition = arkts.getEndPosition(hasOnceDecorator); 65 const endPosition = arkts.getEndPosition(hasOnceDecorator); 66 return { 67 range: [startPosition, endPosition], 68 code: `@${PresetDecorators.PARAM}` 69 }; 70 } 71 }); 72} 73 74function reportInvalidDecoratorsWithOnceAndParam( 75 otherDecorators: arkts.AnnotationUsage | undefined, 76 context: UISyntaxRuleContext 77): void { 78 if (!otherDecorators) { 79 return; 80 } 81 context.report({ 82 node: otherDecorators, 83 message: rule.messages.invalidDecorator, 84 fix: (otherDecorators) => { 85 const startPosition = arkts.getStartPosition(otherDecorators); 86 const endPosition = arkts.getEndPosition(otherDecorators); 87 return { 88 range: [startPosition, endPosition], 89 code: '' 90 }; 91 } 92 }); 93} 94 95// Check if the method is @Once decorated (not allowed) 96function validateMethodAnnotations(body: arkts.MethodDefinition, context: UISyntaxRuleContext): void { 97 const methodAnnotations = body.scriptFunction.annotations?.find(annotation => 98 annotation.expr && 99 annotation.expr.dumpSrc() === PresetDecorators.ONCE 100 ); 101 if (methodAnnotations) { 102 context.report({ 103 node: methodAnnotations, 104 message: rule.messages.invalidMemberDecorate, 105 fix: (methodAnnotations) => { 106 const startPosition = arkts.getStartPosition(methodAnnotations); 107 const endPosition = arkts.getEndPosition(methodAnnotations); 108 return { 109 range: [startPosition, endPosition], 110 code: '' 111 }; 112 } 113 }); 114 } 115} 116 117function invalidComponentUsage( 118 body: arkts.ClassProperty, 119 hasOnceDecorator: arkts.AnnotationUsage | undefined, 120 componentV2DocoratorUsage: arkts.AnnotationUsage | undefined, 121 componentDocoratorUsage: arkts.AnnotationUsage | undefined, 122 context: UISyntaxRuleContext 123): void { 124 hasOnceDecorator = findDecorator(body, PresetDecorators.ONCE); 125 if (hasOnceDecorator && !componentV2DocoratorUsage && componentDocoratorUsage) { 126 context.report({ 127 node: hasOnceDecorator, 128 message: rule.messages.invalidUsage, 129 fix: (hasOnceDecorator) => { 130 const startPosition = arkts.getStartPosition(componentDocoratorUsage); 131 const endPosition = arkts.getEndPosition(componentDocoratorUsage); 132 return { 133 range: [startPosition, endPosition], 134 code: `@${PresetDecorators.COMPONENT_V2}` 135 }; 136 } 137 }); 138 } 139} 140 141function validateDecorater( 142 node: arkts.StructDeclaration, 143 hasOnceDecorator: arkts.AnnotationUsage | undefined, 144 componentV2DocoratorUsage: arkts.AnnotationUsage | undefined, 145 componentDocoratorUsage: arkts.AnnotationUsage | undefined, 146 context: UISyntaxRuleContext, 147): void { 148 node.definition?.body.forEach(body => { 149 // Check if @Once is used on a property and if @Param is used with 150 if (arkts.isClassProperty(body)) { 151 validatePropertyAnnotations(body, context, hasOnceDecorator); 152 // If @Once is used but not in a @ComponentV2 struct, report an error 153 invalidComponentUsage(body, hasOnceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context); 154 } 155 if (!arkts.isMethodDefinition(body)) { 156 return; 157 } 158 // Check if @Once is used on a method (which is not allowed) 159 validateMethodAnnotations(body, context); 160 }); 161} 162 163const rule: UISyntaxRule = { 164 name: 'once-decorator-check', 165 messages: { 166 invalidUsage: `@Once can only decorate member properties in a @ComponentV2 struct.`, 167 invalidMemberDecorate: `@Once can only decorate member properties.`, 168 invalidDecorator: `@Once must be only used with @Param. ` 169 }, 170 setup(context) { 171 return { 172 parsed: (node): void => { 173 // Check if the node is a struct declaration 174 if (!arkts.isStructDeclaration(node)) { 175 return; 176 } 177 let hasOnceDecorator: arkts.AnnotationUsage | undefined; 178 // Check if the struct is decorated with @ComponentV2 179 const componentV2DocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2); 180 const componentDocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1); 181 validateDecorater(node, hasOnceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context); 182 }, 183 }; 184 }, 185}; 186 187export default rule; 188