• 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 { 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