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 { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 18import { PresetDecorators } from '../utils/index'; 19 20const rule: UISyntaxRule = { 21 name: 'track-decorator-check', 22 messages: { 23 invalidTarget: `The '@Track' decorator can only be used on class member variables.`, 24 invalidClass: `The '@Track' decorator can only be used within a 'class' decorated with '@Observed'.` 25 }, 26 setup(context) { 27 return { 28 parsed: (node): void => { 29 if (arkts.isStructDeclaration(node)) { 30 checkInvalidTrackAnnotations(context, node); 31 } 32 // Check if the current node is a class declaration 33 if (arkts.isClassDeclaration(node)) { 34 checkTrackOnlyUsedWithObserved(context, node); 35 } 36 } 37 }; 38 } 39}; 40 41function checkInvalidTrackAnnotations(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void { 42 // Traverse all members of the struct body 43 node.definition.body.forEach((member) => { 44 // Check whether it is a member variable 45 if (arkts.isClassProperty(member)) { 46 const hasTrackDecorator = findClassPropertyAnnotation(member, PresetDecorators.TRACK); 47 // If a member variable is decorated with @Track, an error is reported immediately 48 if (hasTrackDecorator) { 49 reportInvalidTarget(context, hasTrackDecorator); 50 } 51 } 52 // Check whether this is the method 53 if (arkts.isMethodDefinition(member)) { 54 const hasTrackDecorator = getMethodAnnotation(member, PresetDecorators.TRACK); 55 // If the method is decorated with @Track, an error is reported immediately 56 if (hasTrackDecorator) { 57 reportInvalidTarget(context, hasTrackDecorator); 58 } 59 } 60 },); 61} 62 63function checkTrackOnlyUsedWithObserved(context: UISyntaxRuleContext, node: arkts.ClassDeclaration): void { 64 // Check if the class is decorated with @Observed 65 const hasObservedDecorator = node.definition?.annotations?.find( 66 annotations => 67 annotations.expr && 68 arkts.isIdentifier(annotations.expr) && 69 annotations.expr.name === PresetDecorators.OBSERVED_V1 70 ); 71 // Traverse all members of the body class 72 node.definition?.body.forEach((member) => { 73 // Check whether it is a class attribute 74 if (arkts.isClassProperty(member)) { 75 const hasTrackDecorator = findClassPropertyAnnotation(member, PresetDecorators.TRACK); 76 // If the class is not decorated with @Observed and has decorators, an error is reported 77 if (!hasObservedDecorator && hasTrackDecorator) { 78 reportInvalidClass(context, hasTrackDecorator); 79 } 80 } 81 // Check whether this is the method 82 if (arkts.isMethodDefinition(member)) { 83 const hasTrackDecorator = getMethodAnnotation(member, PresetDecorators.TRACK); 84 // If the method is decorated with @Track, an error is reported immediately 85 if (hasTrackDecorator) { 86 reportInvalidTarget(context, hasTrackDecorator); 87 } 88 } 89 }); 90} 91 92function reportInvalidClass(context: UISyntaxRuleContext, hasTrackDecorator: arkts.AnnotationUsage): void { 93 context.report({ 94 node: hasTrackDecorator, 95 message: rule.messages.invalidClass, 96 fix: (hasTrackDecorator) => { 97 const startPosition = arkts.getStartPosition(hasTrackDecorator); 98 const endPosition = arkts.getEndPosition(hasTrackDecorator); 99 return { 100 range: [startPosition, endPosition], 101 code: '', 102 }; 103 }, 104 }); 105} 106 107function getMethodAnnotation( 108 node: arkts.MethodDefinition, 109 annotationName: string) 110 : arkts.AnnotationUsage | undefined { 111 return node.scriptFunction.annotations?.find( 112 annotation => 113 annotation.expr && 114 annotation.expr.dumpSrc() === annotationName 115 ); 116} 117 118function findClassPropertyAnnotation( 119 node: arkts.ClassProperty, 120 annotationName: string) 121 : arkts.AnnotationUsage | undefined { 122 return node.annotations?.find(annotation => 123 annotation.expr && 124 annotation.expr.dumpSrc() === annotationName 125 ); 126} 127 128function reportInvalidTarget( 129 context: UISyntaxRuleContext, 130 node: arkts.AnnotationUsage) 131 : void { 132 context.report({ 133 node: node, 134 message: rule.messages.invalidTarget, 135 fix: (node) => { 136 const startPosition = arkts.getStartPosition(node); 137 const endPosition = arkts.getEndPosition(node); 138 return { 139 range: [startPosition, endPosition], 140 code: '', 141 }; 142 }, 143 }); 144} 145 146export default rule;