• 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 { getClassPropertyAnnotationNames, PresetDecorators, getAnnotationUsage } from '../utils';
18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule';
19
20// Helper functions for rules
21const hasisComponentV2 = (node: arkts.StructDeclaration): boolean => !!getAnnotationUsage(node,
22  PresetDecorators.COMPONENT_V2);
23
24const hasComponent = (node: arkts.StructDeclaration): boolean => !!getAnnotationUsage(node,
25  PresetDecorators.COMPONENT_V1);
26function checkMultipleBuiltInDecorators(context: UISyntaxRuleContext, member: arkts.ClassProperty,
27  propertyDecorators: string[]): void {
28  const builtInDecorators = [PresetDecorators.LOCAL, PresetDecorators.PARAM, PresetDecorators.EVENT];
29  const appliedBuiltInDecorators = propertyDecorators.filter(d => builtInDecorators.includes(d));
30  if (appliedBuiltInDecorators.length > 1) {
31    member.annotations?.forEach(annotation => {
32      const annotationsName = annotation.expr?.dumpSrc();
33      reportMultipleBuiltInDecoratorsError(context, annotation, annotationsName, builtInDecorators);
34    });
35  }
36};
37
38function reportMultipleBuiltInDecoratorsError(context: UISyntaxRuleContext, annotation: arkts.AstNode,
39  annotationsName: string | undefined, builtInDecorators: string[]): void {
40  if (annotationsName && builtInDecorators.includes(annotationsName)) {
41    context.report({
42      node: annotation,
43      message: rule.messages.multipleBuiltInDecorators,
44      fix: (annotation) => {
45        const startPosition = arkts.getStartPosition(annotation);
46        const endPosition = arkts.getEndPosition(annotation);
47        return {
48          range: [startPosition, endPosition],
49          code: '',
50        };
51      },
52    });
53  }
54}
55
56function checkDecoratorOnlyInisComponentV2(context: UISyntaxRuleContext, member: arkts.ClassProperty,
57  node: arkts.StructDeclaration, hasisComponentV2: boolean, hasComponent: boolean): void {
58  const builtInDecorators = [PresetDecorators.LOCAL, PresetDecorators.PARAM, PresetDecorators.EVENT];
59  member.annotations?.forEach(annotation => {
60    const annotationsName = annotation.expr?.dumpSrc();
61    if (annotationsName && builtInDecorators.includes(annotationsName) && !hasisComponentV2 && !hasComponent) {
62      reportDecoratorOnlyInisComponentV2Error(context, annotation, annotationsName, node);
63    }
64  });
65};
66
67function reportDecoratorOnlyInisComponentV2Error(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage,
68  annotationsName: string, node: arkts.StructDeclaration): void {
69  context.report({
70    node: annotation,
71    message: rule.messages.decoratorOnlyInisComponentV2,
72    data: { annotationsName },
73    fix: (annotation) => {
74      const startPosition = arkts.getStartPosition(node);
75      return {
76        range: [startPosition, startPosition],
77        code: `@${PresetDecorators.COMPONENT_V2}\n`,
78      };
79    },
80  });
81}
82
83function checkParamRequiresRequire(context: UISyntaxRuleContext, member: arkts.ClassProperty,
84  propertyDecorators: string[]): void {
85  if (propertyDecorators.includes(PresetDecorators.PARAM) && !member.value &&
86    !propertyDecorators.includes(PresetDecorators.REQUIRE) && member.key) {
87    const memberKey = member.key;
88    context.report({
89      node: memberKey,
90      message: rule.messages.paramRequiresRequire,
91      fix: (memberKey) => {
92        const startPosition = arkts.getStartPosition(memberKey);
93        return {
94          range: [startPosition, startPosition],
95          code: `@${PresetDecorators.REQUIRE} `,
96        };
97      },
98    });
99  }
100};
101
102function checkRequireOnlyWithParam(context: UISyntaxRuleContext, member: arkts.ClassProperty,
103  propertyDecorators: string[]): void {
104  const requireDecorator = member.annotations?.find(annotation =>
105    annotation.expr && annotation.expr.dumpSrc() === PresetDecorators.REQUIRE
106  );
107  if (requireDecorator && !propertyDecorators.includes(PresetDecorators.PARAM)) {
108    context.report({
109      node: requireDecorator,
110      message: rule.messages.requireOnlyWithParam,
111      fix: (requireDecorator) => {
112        const startPosition = arkts.getStartPosition(requireDecorator);
113        const endPosition = arkts.getEndPosition(requireDecorator);
114        return {
115          range: [startPosition, endPosition],
116          code: '',
117        };
118      },
119    });
120  }
121};
122
123function validateClassPropertyDecorators(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void {
124  const isComponentV2 = hasisComponentV2(node);
125  const isComponent = hasComponent(node);
126  node.definition.body.forEach(member => {
127    if (!arkts.isClassProperty(member)) {
128      return;
129    }
130    const propertyDecorators = getClassPropertyAnnotationNames(member);
131
132    // Rule 1: Multiple built-in decorators
133    checkMultipleBuiltInDecorators(context, member, propertyDecorators);
134
135    // Rule 2: Built-in decorators only allowed in @isComponentV2
136    checkDecoratorOnlyInisComponentV2(context, member, node, isComponentV2, isComponent);
137
138    // Rule 3: @Param without default value must be combined with @Require
139    checkParamRequiresRequire(context, member, propertyDecorators);
140
141    // Rule 4: @Require must be used together with @Param
142    checkRequireOnlyWithParam(context, member, propertyDecorators);
143  });
144}
145
146const rule: UISyntaxRule = {
147  name: 'iscomponentV2-state-usage-validation',
148  messages: {
149    multipleBuiltInDecorators: `The member property or method cannot be decorated by multiple built-in decorators.`,
150    decoratorOnlyInisComponentV2: `The '@{{annotationsName}}' decorator can only be used in a 'struct' decorated with '@isComponentV2'.`,
151    paramRequiresRequire: `When a variable decorated with @Param is not assigned a default value, it must also be decorated with @Require.`,
152    requireOnlyWithParam: `In a struct decorated with @isComponentV2, @Require can only be used with @Param. `
153  },
154
155  setup(context) {
156    return {
157      parsed: (node): void => {
158
159        if (!arkts.isStructDeclaration(node)) {
160          return;
161        }
162        validateClassPropertyDecorators(context, node);
163      },
164    };
165  },
166};
167
168export default rule;