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