• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;