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 { PresetDecorators, getAnnotationUsage } from '../utils'; 18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 19 20const rule: UISyntaxRule = { 21 name: 'observedV2-trace-usage-validation', 22 messages: { 23 observedV2DecoratorError: `The '@ObservedV2' decorator can only be used in 'class'.`, 24 traceDecoratorError: `The '@Trace' decorator can only be used in 'class'.`, 25 traceInObservedV2Error: `The '@Trace' decorator can only be used in a 'class' decorated with '@ObservedV2'.`, 26 traceMemberVariableError: `The '@Trace' decorator can only decorate member variables within a 'class' decorated with '@ObservedV2'.`, 27 }, 28 setup(context) { 29 return { 30 parsed: (node): void => { 31 validateTraceDecoratorUsage(node, context); 32 }, 33 }; 34 }, 35}; 36 37function reportObservedV2DecoratorError(context: UISyntaxRuleContext, hasObservedV2Decorator: arkts.AnnotationUsage) 38 : void { 39 context.report({ 40 node: hasObservedV2Decorator, 41 message: rule.messages.observedV2DecoratorError, 42 fix: (hasObservedV2Decorator) => { 43 const startPosition = arkts.getStartPosition(hasObservedV2Decorator); 44 const endPosition = arkts.getEndPosition(hasObservedV2Decorator); 45 return { 46 range: [startPosition, endPosition], 47 code: '', 48 }; 49 }, 50 }); 51} 52 53function reportTraceMemberVariableError(context: UISyntaxRuleContext, hasTraceDecorator: arkts.AnnotationUsage) 54 : void { 55 context.report({ 56 node: hasTraceDecorator, 57 message: rule.messages.traceMemberVariableError, 58 fix: (hasTraceDecorator) => { 59 const startPosition = arkts.getStartPosition(hasTraceDecorator); 60 const endPosition = arkts.getEndPosition(hasTraceDecorator); 61 return { 62 range: [startPosition, endPosition], 63 code: '', 64 }; 65 }, 66 }); 67} 68 69function tracePerportyRule( 70 context: UISyntaxRuleContext, 71 currentNode: arkts.AstNode, 72 hasTraceDecorator: arkts.AnnotationUsage): void { 73 if (arkts.isStructDeclaration(currentNode)) { 74 reportTraceDecoratorError(context, hasTraceDecorator); 75 } else if (arkts.isClassDeclaration(currentNode)) { 76 // The '@Trace' decorator can only be used in a 'class' decorated with '@ObservedV2' 77 if (!currentNode.definition?.annotations?.some((annotation: any) => annotation.expr.name === 78 PresetDecorators.OBSERVED_V2)) { 79 reportTraceInObservedV2Error(context, hasTraceDecorator, currentNode); 80 } 81 } 82} 83 84function reportTraceDecoratorError(context: UISyntaxRuleContext, hasTraceDecorator: arkts.AnnotationUsage) 85 : void { 86 context.report({ 87 node: hasTraceDecorator, 88 message: rule.messages.traceDecoratorError, 89 fix: (hasTraceDecorator) => { 90 const startPosition = arkts.getStartPosition(hasTraceDecorator); 91 const endPosition = arkts.getEndPosition(hasTraceDecorator); 92 return { 93 range: [startPosition, endPosition], 94 code: '', 95 }; 96 }, 97 }); 98} 99 100function reportTraceInObservedV2Error(context: UISyntaxRuleContext, hasTraceDecorator: arkts.AnnotationUsage, 101 currentNode: arkts.ClassDeclaration): void { 102 context.report({ 103 node: hasTraceDecorator, 104 message: rule.messages.traceInObservedV2Error, 105 fix: () => { 106 const startPosition = arkts.getStartPosition(currentNode); 107 return { 108 range: [startPosition, startPosition], 109 code: `@${PresetDecorators.OBSERVED_V2}\n`, 110 }; 111 }, 112 }); 113} 114 115function validateTraceDecoratorUsage(node: arkts.AstNode, context: UISyntaxRuleContext): void { 116 let currentNode = node; 117 if (arkts.isStructDeclaration(node)) { 118 // Check whether the current custom component is decorated by the @ObservedV2 decorator 119 const hasObservedV2Decorator = getAnnotationUsage(node, PresetDecorators.OBSERVED_V2); 120 if (hasObservedV2Decorator) { 121 reportObservedV2DecoratorError(context, hasObservedV2Decorator); 122 } 123 } 124 if (arkts.isClassProperty(node)) { 125 const hasTraceDecorator = node.annotations?.find(annotation => 126 annotation.expr && annotation.expr.dumpSrc() === PresetDecorators.TRACE); 127 if (hasTraceDecorator) { 128 // Iterate up the parent node to check whether it is a class or a custom component 129 while (!arkts.isStructDeclaration(currentNode) && !arkts.isClassDeclaration(currentNode)) { 130 currentNode = currentNode.parent; 131 } 132 // The '@Trace' decorator can only be used in 'class' 133 tracePerportyRule(context, currentNode, hasTraceDecorator); 134 } 135 } 136 if (arkts.isMethodDefinition(node)) { 137 // Check that @Trace is in the correct location 138 const hasTraceDecorator = node.scriptFunction.annotations?.find(annotation => 139 annotation.expr && annotation.expr.dumpSrc() === PresetDecorators.TRACE); 140 if (hasTraceDecorator) { 141 reportTraceMemberVariableError(context, hasTraceDecorator); 142 } 143 } 144} 145 146export default rule; 147