• 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 { getAnnotationUsage, getClassPropertyAnnotationNames, PresetDecorators } from '../utils';
18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule';
19
20function findDecorator(member: arkts.ClassProperty, decorator: string): arkts.AnnotationUsage | undefined {
21  return member.annotations?.find(annotation =>
22    annotation.expr &&
23    annotation.expr.dumpSrc() === decorator
24  );
25}
26
27// Check that the property decorator complies with the rules
28function validatePropertyAnnotations(
29  body: arkts.ClassProperty,
30  context: UISyntaxRuleContext,
31  hasOnceDecorator: arkts.AnnotationUsage | undefined
32): void {
33  const propertyAnnotations = getClassPropertyAnnotationNames(body);
34  hasOnceDecorator = findDecorator(body, PresetDecorators.ONCE);
35  if (hasOnceDecorator) {
36    const isParamUsed = propertyAnnotations.includes(PresetDecorators.PARAM);
37    // If @Once is found, check if @Param is also used
38    if (!isParamUsed) {
39      reportMissingParamWithOnce(hasOnceDecorator, context);
40    } else {
41      // If both @Once and @Param are used, check for other
42      // incompatible decorators
43      const otherDecorators = body.annotations?.find(annotation =>
44        annotation.expr &&
45        annotation.expr.dumpSrc() !== PresetDecorators.ONCE &&
46        annotation.expr.dumpSrc() !== PresetDecorators.PARAM
47      );
48      reportInvalidDecoratorsWithOnceAndParam(otherDecorators, context);
49    }
50  }
51}
52
53function reportMissingParamWithOnce(
54  hasOnceDecorator: arkts.AnnotationUsage | undefined,
55  context: UISyntaxRuleContext
56): void {
57  if (!hasOnceDecorator) {
58    return;
59  }
60  context.report({
61    node: hasOnceDecorator,
62    message: rule.messages.invalidDecorator,
63    fix: (hasOnceDecorator) => {
64      const startPosition = arkts.getEndPosition(hasOnceDecorator);
65      const endPosition = arkts.getEndPosition(hasOnceDecorator);
66      return {
67        range: [startPosition, endPosition],
68        code: `@${PresetDecorators.PARAM}`
69      };
70    }
71  });
72}
73
74function reportInvalidDecoratorsWithOnceAndParam(
75  otherDecorators: arkts.AnnotationUsage | undefined,
76  context: UISyntaxRuleContext
77): void {
78  if (!otherDecorators) {
79    return;
80  }
81  context.report({
82    node: otherDecorators,
83    message: rule.messages.invalidDecorator,
84    fix: (otherDecorators) => {
85      const startPosition = arkts.getStartPosition(otherDecorators);
86      const endPosition = arkts.getEndPosition(otherDecorators);
87      return {
88        range: [startPosition, endPosition],
89        code: ''
90      };
91    }
92  });
93}
94
95// Check if the method is @Once decorated (not allowed)
96function validateMethodAnnotations(body: arkts.MethodDefinition, context: UISyntaxRuleContext): void {
97  const methodAnnotations = body.scriptFunction.annotations?.find(annotation =>
98    annotation.expr &&
99    annotation.expr.dumpSrc() === PresetDecorators.ONCE
100  );
101  if (methodAnnotations) {
102    context.report({
103      node: methodAnnotations,
104      message: rule.messages.invalidMemberDecorate,
105      fix: (methodAnnotations) => {
106        const startPosition = arkts.getStartPosition(methodAnnotations);
107        const endPosition = arkts.getEndPosition(methodAnnotations);
108        return {
109          range: [startPosition, endPosition],
110          code: ''
111        };
112      }
113    });
114  }
115}
116
117function invalidComponentUsage(
118  body: arkts.ClassProperty,
119  hasOnceDecorator: arkts.AnnotationUsage | undefined,
120  componentV2DocoratorUsage: arkts.AnnotationUsage | undefined,
121  componentDocoratorUsage: arkts.AnnotationUsage | undefined,
122  context: UISyntaxRuleContext
123): void {
124  hasOnceDecorator = findDecorator(body, PresetDecorators.ONCE);
125  if (hasOnceDecorator && !componentV2DocoratorUsage && componentDocoratorUsage) {
126    context.report({
127      node: hasOnceDecorator,
128      message: rule.messages.invalidUsage,
129      fix: (hasOnceDecorator) => {
130        const startPosition = arkts.getStartPosition(componentDocoratorUsage);
131        const endPosition = arkts.getEndPosition(componentDocoratorUsage);
132        return {
133          range: [startPosition, endPosition],
134          code: `@${PresetDecorators.COMPONENT_V2}`
135        };
136      }
137    });
138  }
139}
140
141function validateDecorater(
142  node: arkts.StructDeclaration,
143  hasOnceDecorator: arkts.AnnotationUsage | undefined,
144  componentV2DocoratorUsage: arkts.AnnotationUsage | undefined,
145  componentDocoratorUsage: arkts.AnnotationUsage | undefined,
146  context: UISyntaxRuleContext,
147): void {
148  node.definition?.body.forEach(body => {
149    // Check if @Once is used on a property and if @Param is used with
150    if (arkts.isClassProperty(body)) {
151      validatePropertyAnnotations(body, context, hasOnceDecorator);
152      // If @Once is used but not in a @ComponentV2 struct, report an error
153      invalidComponentUsage(body, hasOnceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context);
154    }
155    if (!arkts.isMethodDefinition(body)) {
156      return;
157    }
158    // Check if @Once is used on a method (which is not allowed)
159    validateMethodAnnotations(body, context);
160  });
161}
162
163const rule: UISyntaxRule = {
164  name: 'once-decorator-check',
165  messages: {
166    invalidUsage: `@Once can only decorate member properties in a @ComponentV2 struct.`,
167    invalidMemberDecorate: `@Once can only decorate member properties.`,
168    invalidDecorator: `@Once must be only used with @Param. `
169  },
170  setup(context) {
171    return {
172      parsed: (node): void => {
173        // Check if the node is a struct declaration
174        if (!arkts.isStructDeclaration(node)) {
175          return;
176        }
177        let hasOnceDecorator: arkts.AnnotationUsage | undefined;
178        // Check if the struct is decorated with @ComponentV2
179        const componentV2DocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2);
180        const componentDocoratorUsage = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1);
181        validateDecorater(node, hasOnceDecorator, componentV2DocoratorUsage, componentDocoratorUsage, context);
182      },
183    };
184  },
185};
186
187export default rule;
188