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 { getIdentifierName, PresetDecorators } from '../utils'; 18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 19 20// Gets the names of all methods in the struct 21function getMethodNames(node: arkts.StructDeclaration): string[] { 22 const methodNames: string[] = []; 23 node.definition.body.forEach((member) => { 24 if (arkts.isMethodDefinition(member)) { 25 const methodName = getIdentifierName(member.name); 26 if (methodName) { 27 methodNames.push(methodName); 28 } 29 } 30 }); 31 return methodNames; 32} 33 34// Invalid @Watch decorator bugs are reported 35function reportInvalidWatch( 36 member: arkts.ClassProperty, 37 methodName: string, 38 hasWatchDecorator: arkts.AnnotationUsage, 39 context: UISyntaxRuleContext 40): void { 41 context.report({ 42 node: hasWatchDecorator, 43 message: rule.messages.invalidWatch, 44 data: { methodName }, 45 fix: () => { 46 const startPosition = arkts.getEndPosition(member); 47 const endPosition = arkts.getEndPosition(member); 48 return { 49 range: [startPosition, endPosition], 50 code: `\n${methodName}(){\n}`, 51 }; 52 }, 53 }); 54} 55 56function validateWatchDecorator( 57 member: arkts.ClassProperty, 58 methodNames: string[], 59 hasWatchDecorator: arkts.AnnotationUsage | undefined, 60 context: UISyntaxRuleContext 61): void { 62 member.annotations.forEach((annotation) => { 63 validateWatchProperty(annotation, member, methodNames, hasWatchDecorator, context); 64 }); 65} 66 67function validateWatchProperty( 68 annotation: arkts.AnnotationUsage, 69 member: arkts.ClassProperty, 70 methodNames: string[], 71 hasWatchDecorator: arkts.AnnotationUsage | undefined, 72 context: UISyntaxRuleContext 73): void { 74 if ( 75 annotation.expr && 76 annotation.expr.dumpSrc() === PresetDecorators.WATCH 77 ) { 78 annotation.properties.forEach((element) => { 79 if (!arkts.isClassProperty(element)) { 80 return; 81 } 82 const methodName = element.value?.dumpSrc().slice(1, -1); 83 if (hasWatchDecorator && methodName && !methodNames.includes(methodName)) { 84 reportInvalidWatch(member, methodName, hasWatchDecorator, context); 85 } 86 }); 87 } 88 89} 90 91function validateWatch( 92 node: arkts.StructDeclaration, 93 methodNames: string[], 94 context: UISyntaxRuleContext 95): void { 96 node.definition.body.forEach(member => { 97 if (!arkts.isClassProperty(member)) { 98 return; 99 } 100 const hasWatchDecorator = member.annotations?.find(annotation => 101 annotation.expr && 102 annotation.expr.dumpSrc() === PresetDecorators.WATCH 103 ); 104 // Determine whether it contains @watch decorators 105 validateWatchDecorator(member, methodNames, hasWatchDecorator, context); 106 }); 107} 108 109const rule: UISyntaxRule = { 110 name: 'watch-decorator-function', 111 messages: { 112 invalidWatch: `The '@Watch' decorated parameter must be a callback '{{methodName}}' of a function in a custom component.`, 113 }, 114 setup(context) { 115 return { 116 parsed: (node): void => { 117 if (!arkts.isStructDeclaration(node)) { 118 return; 119 } 120 // Get all method names 121 const methodNames = getMethodNames(node); 122 validateWatch(node, methodNames, context); 123 }, 124 }; 125 }, 126}; 127 128export default rule;