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 } from '../utils'; 18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 19 20// Function declarations moved to the top with explicit return types 21function getLocalMonitorUsed(body: arkts.MethodDefinition): arkts.AnnotationUsage | undefined { 22 const localMonitorUsed = body.scriptFunction.annotations?.find( 23 annotation => annotation.expr && 24 annotation.expr.dumpSrc() === PresetDecorators.MONITOR 25 ); 26 return localMonitorUsed; 27} 28 29function checkConflictingDecorators(context: UISyntaxRuleContext, body: arkts.MethodDefinition, 30 localMonitorUsed: arkts.AnnotationUsage): boolean { 31 const conflictingDecorators = body.scriptFunction.annotations?.filter( 32 annotation => annotation.expr && 33 annotation.expr.dumpSrc() !== PresetDecorators.MONITOR 34 ); 35 if (conflictingDecorators?.length > 0) { 36 reportConflictingDecorators(context, localMonitorUsed, conflictingDecorators); 37 return true; 38 } 39 return false; 40} 41 42function reportConflictingDecorators(context: UISyntaxRuleContext, localMonitorUsed: arkts.AstNode, 43 conflictingDecorators: arkts.AnnotationUsage[]): void { 44 context.report({ 45 node: localMonitorUsed, 46 message: rule.messages.invalidUsage1, 47 fix: () => { 48 const startPositions = conflictingDecorators.map(annotation => 49 arkts.getStartPosition(annotation)); 50 const endPositions = conflictingDecorators.map(annotation => arkts.getEndPosition(annotation)); 51 const startPosition = startPositions[0]; 52 const endPosition = endPositions[endPositions.length - 1]; 53 return { 54 range: [startPosition, endPosition], 55 code: '' 56 }; 57 } 58 }); 59} 60 61function checkIfClassIsObservedV2(node: arkts.ClassDeclaration): boolean { 62 return node.definition?.annotations?.some( 63 observedV2 => observedV2.expr?.dumpSrc() === PresetDecorators.OBSERVED_V2 64 ) ?? false; 65} 66 67function checkIfStructIsComponentV2(node: arkts.StructDeclaration): boolean { 68 return node.definition?.annotations?.some( 69 componentV2 => componentV2.expr?.dumpSrc() === PresetDecorators.COMPONENT_V2 70 ) ?? false; 71} 72 73function reportInvalidUsage(context: UISyntaxRuleContext, node: arkts.AstNode, message: string, fixCode: string) 74 : void { 75 const startPosition = arkts.getStartPosition(node); 76 context.report({ 77 node, 78 message, 79 fix: () => ({ 80 range: [startPosition, startPosition], 81 code: fixCode, 82 }), 83 }); 84} 85 86function checkMultipleDecorators( 87 node: arkts.ClassDeclaration | arkts.StructDeclaration, 88 context: UISyntaxRuleContext 89): boolean { 90 // Traverse body of the class to check for @Monitor usage 91 let monitorUsed: boolean = false; 92 node.definition?.body.forEach(body => { 93 if (arkts.isMethodDefinition(body)) { 94 const localMonitorUsed = getLocalMonitorUsed(body); 95 if (localMonitorUsed) { 96 monitorUsed = true; 97 checkConflictingDecorators(context, body, localMonitorUsed); 98 return; // Stop further checks for this method 99 } 100 } 101 }); 102 return monitorUsed; 103} 104 105function checkDecorateMethod( 106 node: arkts.ClassDeclaration | arkts.StructDeclaration, 107 context: UISyntaxRuleContext 108): void { 109 // Check if @Monitor is used on a property (which is not allowed) 110 node.definition?.body.forEach(body => { 111 if (!arkts.isClassProperty(body)) { 112 return; 113 } 114 const monitorDecorator = body.annotations?.find( 115 annotation => annotation.expr?.dumpSrc() === PresetDecorators.MONITOR); 116 if (monitorDecorator === undefined) { 117 return; 118 } 119 context.report({ 120 node: monitorDecorator, 121 message: rule.messages.invalidUsage4, 122 fix: () => { 123 const startPosition = arkts.getStartPosition(monitorDecorator); 124 const endPosition = arkts.getEndPosition(monitorDecorator); 125 return { 126 range: [startPosition, endPosition], 127 code: '', 128 }; 129 }, 130 }); 131 }); 132} 133 134// The rule object with its setup method 135const rule: UISyntaxRule = { 136 name: 'monitor-decorator-check', 137 messages: { 138 invalidUsage1: 139 `The member property or method can not be decorated by multiple built-in decorators.`, 140 invalidUsage2: 141 `The '@Monitor' can decorate only member method within a 'class' decorated with @ObservedV2.`, 142 invalidUsage3: 143 `The '@Monitor' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`, 144 invalidUsage4: 145 `@Monitor can only decorate method`, 146 }, 147 setup(context) { 148 return { 149 parsed: (node: arkts.AstNode): void => { 150 if (!arkts.isClassDeclaration(node) && !arkts.isStructDeclaration(node)) { 151 return; 152 } 153 let monitorUsed = false; 154 155 const isObservedV2 = arkts.isClassDeclaration(node) && checkIfClassIsObservedV2(node); 156 const isComponentV2 = arkts.isStructDeclaration(node) && checkIfStructIsComponentV2(node); 157 158 monitorUsed = checkMultipleDecorators(node, context); 159 160 // Check for errors related to @Monitor usage 161 if (monitorUsed && !isObservedV2 && arkts.isClassDeclaration(node)) { 162 reportInvalidUsage(context, node, rule.messages.invalidUsage2, `@${PresetDecorators.OBSERVED_V2}\n`); 163 } 164 if (monitorUsed && !isComponentV2 && arkts.isStructDeclaration(node)) { 165 reportInvalidUsage(context, node, rule.messages.invalidUsage3, `@${PresetDecorators.COMPONENT_V2}\n`); 166 } 167 168 checkDecorateMethod(node, context); 169 }, 170 }; 171 }, 172}; 173 174export default rule; 175