• 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, MultiMap, PresetDecorators } from '../utils';
18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule';
19// Traverse the member variables of the struct, recording the members of the @Consumer modifications
20function processStructMembers(
21  node: arkts.StructDeclaration,
22  structName: string,
23  componentv2WithConsumer: MultiMap<string, string>
24): void {
25  node.definition.body.forEach((member) => {
26    // When a member variable is @consumer modified, it is stored to mark fields that cannot be initialized
27    if (arkts.isClassProperty(member)) {
28      const memberName = member?.key?.dumpSrc();
29      structName && memberName ? componentv2WithConsumer.add(structName, memberName) : null;
30    }
31  });
32}
33function rememberStructName(node: arkts.AstNode, componentv2WithConsumer: MultiMap<string, string>): void {
34  // First it has to be of the struct type
35  if (arkts.isStructDeclaration(node)) {
36    node?.definition?.annotations.forEach((anno) => {
37      // Second, it must be decorated with a @component v2 decorator
38      if (anno.expr?.dumpSrc() === PresetDecorators.COMPONENT_V2) {
39        const structName = node.definition.ident?.name ?? '';
40        processStructMembers(node, structName, componentv2WithConsumer);
41      }
42    });
43  }
44}
45function findDecorator(member: arkts.ClassProperty, decorator: string): arkts.AnnotationUsage | undefined {
46  return member.annotations?.find(annotation =>
47    annotation.expr &&
48    annotation.expr.dumpSrc() === decorator
49  );
50}
51// Verify that the @Consumer decorator is used on the method
52function validateConsumerOnMethod(member: arkts.MethodDefinition, context: UISyntaxRuleContext): void {
53  const annotationNode = member.scriptFunction.annotations?.find(annotation =>
54    annotation.expr && annotation.expr.dumpSrc() === PresetDecorators.CONSUMER
55  );
56  if (annotationNode) {
57    context.report({
58      node: annotationNode,
59      message: rule.messages.consumerOnlyOnMember,
60      fix: (annotationNode) => {
61        const startPosition = arkts.getStartPosition(annotationNode);
62        const endPosition = arkts.getEndPosition(annotationNode);
63        return {
64          range: [startPosition, endPosition],
65          code: '',
66        };
67      }
68    });
69  }
70}
71// @Consumer Bugs that conflict with other decorators
72function reportMultipleBuiltInDecorators(
73  hasConsumeDecorator: arkts.AnnotationUsage,
74  otherDecorators: arkts.AnnotationUsage,
75  context: UISyntaxRuleContext,
76): void {
77  context.report({
78    node: hasConsumeDecorator,
79    message: rule.messages.multipleBuiltInDecorators,
80    fix: (hasConsumeDecorator) => {
81      const startPosition = arkts.getStartPosition(otherDecorators);
82      const endPosition = arkts.getEndPosition(otherDecorators);
83      return {
84        range: [startPosition, endPosition],
85        code: '',
86      };
87    }
88  });
89}
90// Report a bug where @Provider is missing @ComponentV2
91function reportProviderRequiresComponentV2(
92  hasProviderDecorator: arkts.AnnotationUsage,
93  hasComponent: arkts.AnnotationUsage | undefined,
94  node: arkts.AstNode,
95  context: UISyntaxRuleContext,
96): void {
97  if (hasComponent) {
98    context.report({
99      node: hasProviderDecorator,
100      message: rule.messages.providerRequiresComponentV2,
101      fix: (hasProviderDecorator) => {
102        const startPosition = arkts.getStartPosition(hasComponent);
103        const endPosition = arkts.getEndPosition(hasComponent);
104        return {
105          range: [startPosition, endPosition],
106          code: `@${PresetDecorators.COMPONENT_V2}`,
107        };
108      }
109    });
110  } else {
111    context.report({
112      node: hasProviderDecorator,
113      message: rule.messages.providerRequiresComponentV2,
114      fix: (hasProviderDecorator) => {
115        const startPosition = arkts.getStartPosition(node);
116        const endPosition = startPosition;
117        return {
118          range: [startPosition, endPosition],
119          code: `@${PresetDecorators.COMPONENT_V2}\n`,
120        };
121      }
122    });
123  }
124}
125// Verify decorator conflicts on member variables
126function validateMemberDecorators(member: arkts.ClassProperty,
127  hasComponentV2: arkts.AnnotationUsage | undefined,
128  hasComponent: arkts.AnnotationUsage | undefined,
129  node: arkts.AstNode,
130  context: UISyntaxRuleContext
131): void {
132  const hasConsumeDecorator = findDecorator(member, PresetDecorators.CONSUMER);
133  const hasProviderDecorator = findDecorator(member, PresetDecorators.PROVIDER);
134  const otherDecorators = member.annotations?.find(annotation =>
135    annotation.expr &&
136    annotation.expr.dumpSrc() !== PresetDecorators.CONSUMER
137  );
138  if (hasConsumeDecorator && otherDecorators) {
139    reportMultipleBuiltInDecorators(hasConsumeDecorator, otherDecorators, context);
140  }
141  if (hasProviderDecorator && !hasComponentV2) {
142    reportProviderRequiresComponentV2(hasProviderDecorator, hasComponent, node, context);
143  }
144}
145// Verify that @Provider is being used in the class
146function validateProviderInClass(member: arkts.ClassProperty, context: UISyntaxRuleContext): void {
147  const hasProviderDecorator = findDecorator(member, PresetDecorators.PROVIDER);
148  if (hasProviderDecorator) {
149    context.report({
150      node: hasProviderDecorator,
151      message: rule.messages.providerOnlyInStruct,
152      fix: (hasProviderDecorator) => {
153        const startPosition = arkts.getStartPosition(hasProviderDecorator);
154        const endPosition = arkts.getEndPosition(hasProviderDecorator);
155        return {
156          range: [startPosition, endPosition],
157          code: '',
158        };
159      }
160    });
161  }
162}
163// Verify that the current identifier is an illegally initialized @Consumer member variable
164function reportValidateConsumer(
165  currentNode: arkts.Identifier,
166  callExpName: string,
167  componentv2WithConsumer: MultiMap<string, string>,
168  context: UISyntaxRuleContext
169): void {
170  if (componentv2WithConsumer.get(callExpName).includes(currentNode.dumpSrc())) {
171    context.report({
172      node: currentNode.parent,
173      message: rule.messages.forbiddenInitialization,
174      data: {
175        value: currentNode.dumpSrc(),
176        structName: callExpName
177      },
178      fix: () => {
179        const startPosition = arkts.getStartPosition(currentNode.parent);
180        const endPosition = arkts.getEndPosition(currentNode.parent);
181        return {
182          range: [startPosition, endPosition],
183          code: '',
184        };
185      }
186    });
187  }
188}
189// Verify that the @Consumer-decorated property is initialized
190function validateConsumerInitialization(node: arkts.CallExpression, componentv2WithConsumer: MultiMap<string, string>,
191  context: UISyntaxRuleContext): void {
192  const callExpName: string = node.expression.dumpSrc();
193  if (componentv2WithConsumer.has(callExpName)) {
194    const queue: Array<arkts.AstNode> = [node];
195    while (queue.length > 0) {
196      const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode;
197      if (arkts.isIdentifier(currentNode)) {
198        reportValidateConsumer(currentNode, callExpName, componentv2WithConsumer, context);
199      }
200      const children = currentNode.getChildren();
201      for (const child of children) {
202        queue.push(child);
203      }
204    }
205  }
206}
207function collectStructsWithConsumer(node: arkts.AstNode, componentv2WithConsumer: MultiMap<string, string>): void {
208  // Used to document all V2 structs that use '@Consumer'
209  if (arkts.nodeType(node) === arkts.Es2pandaAstNodeType.AST_NODE_TYPE_ETS_MODULE) {
210    // Breadth traversal is done through while and queues
211    const queue: Array<arkts.AstNode> = [node];
212    while (queue.length > 0) {
213      const currentNode: arkts.AstNode = queue.shift() as arkts.AstNode;
214      // Filter and record the nodes of the tree
215      rememberStructName(currentNode, componentv2WithConsumer);
216      const children = currentNode.getChildren();
217      for (const child of children) {
218        queue.push(child);
219      }
220    }
221  }
222}
223function validateStructDecoratorsAndMembers(node: arkts.AstNode, context: UISyntaxRuleContext): void {
224  if (arkts.isStructDeclaration(node)) {
225    const hasComponentV2 = getAnnotationUsage(node, PresetDecorators.COMPONENT_V2);
226    const hasComponent = getAnnotationUsage(node, PresetDecorators.COMPONENT_V1);
227    node.definition.body.forEach(member => {
228      if (arkts.isMethodDefinition(member)) {
229        validateConsumerOnMethod(member, context);
230      }
231      if (arkts.isClassProperty(member)) {
232        validateMemberDecorators(member, hasComponentV2, hasComponent, node, context);
233      }
234    });
235  }
236}
237function validateProviderInClasses(node: arkts.AstNode, context: UISyntaxRuleContext): void {
238  if (arkts.isClassDeclaration(node)) {
239    node.definition?.body.forEach(member => {
240      if (arkts.isClassProperty(member)) {
241        validateProviderInClass(member, context);
242      }
243    });
244  }
245}
246const rule: UISyntaxRule = {
247  name: 'consumer-provider-decorator-check',
248  messages: {
249    consumerOnlyOnMember: `'@Consumer' can only decorate member property.`,
250    multipleBuiltInDecorators: `The struct member variable can not be decorated by multiple built-in decorators.`,
251    providerRequiresComponentV2: `The '@Provider' decorator can only be used in a 'struct' decorated with '@ComponentV2'.`,
252    providerOnlyInStruct: `The '@Provider' decorator can only be used with 'struct'.`,
253    forbiddenInitialization: `Property '{{value}}' in the custom component '{{structName}}' cannot be initialized here (forbidden to specify).`,
254  },
255  setup(context) {
256    // Used to record the names of the corresponding structs and member variables that are @consumer modified
257    let componentv2WithConsumer: MultiMap<string, string> = new MultiMap();
258    return {
259      parsed: (node): void => {
260        collectStructsWithConsumer(node, componentv2WithConsumer);
261        validateStructDecoratorsAndMembers(node, context);
262        validateProviderInClasses(node, context);
263        if (arkts.isCallExpression(node)) {
264          validateConsumerInitialization(node, componentv2WithConsumer, context);
265        }
266      },
267    };
268  },
269};
270
271export default rule;
272