• 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 { getIdentifierName, getAnnotationName, PresetDecorators } from '../utils';
18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule';
19
20const WRAPBUILDER_NAME: string = 'wrapBuilder';
21// Collect all the function names that are decorated with @Builder
22function collectBuilderFunctions(node: arkts.EtsScript, builderFunctionNames: string[]): void {
23  node.statements.forEach((statement) => {
24    if (!arkts.isFunctionDeclaration(statement)) {
25      return;
26    }
27    const annotations = statement.annotations;
28    if (!annotations) {
29      return;
30    }
31    annotations.forEach((annotation) => {
32      const decoratorName = getAnnotationName(annotation);
33      // Find all the functions that are decorated with @Builder and note their names
34      if (!decoratorName.includes(PresetDecorators.BUILDER)) {
35        return;
36      }
37      const functionName = statement.scriptFunction.id?.name;
38      if (!functionName || builderFunctionNames.includes(functionName)) {
39        return;
40      }
41      builderFunctionNames.push(functionName);
42    });
43  });
44}
45
46// Verify that the wrapBuilder's arguments are decorated with @Builder
47function validateWrapBuilderArguments(
48  member: arkts.ClassProperty,
49  context: UISyntaxRuleContext,
50  builderFunctionNames: string[]
51): void {
52  member.getChildren().forEach((child) => {
53    if (!arkts.isCallExpression(child) || child.expression.dumpSrc() !== WRAPBUILDER_NAME) {
54      return;
55    }
56    let functionName: string | undefined;
57    child.arguments.forEach(firstArgument => {
58      if (arkts.isMemberExpression(firstArgument)) {
59        functionName = getIdentifierName(firstArgument.property);
60      } else if (arkts.isIdentifier(firstArgument)) {
61        functionName = firstArgument.name;
62      }
63      // Verify that wrapBuilder's arguments are decorated with @Builder
64      // rule1: The wrapBuilder accepts only a function decorated by '@Builder'
65      if (functionName && !builderFunctionNames.includes(functionName)) {
66        context.report({
67          node: firstArgument,
68          message: rule.messages.invalidBuilderCheck,
69        });
70      }
71    });
72  });
73}
74
75function validateWrapBuilder(
76  node: arkts.StructDeclaration,
77  builderFunctionNames: string[],
78  context: UISyntaxRuleContext
79): void {
80  node.definition.body.forEach(member => {
81    if (!arkts.isClassProperty(member)) {
82      return;
83    }
84    validateWrapBuilderArguments(member, context, builderFunctionNames);
85  });
86}
87
88const rule: UISyntaxRule = {
89  name: 'wrap-builder-check',
90  messages: {
91    invalidBuilderCheck: 'The wrapBuilder accepts only a function decorated by @Builder.',
92  },
93  setup(context) {
94    let builderFunctionNames: string[] = [];
95    return {
96      parsed: (node): void => {
97        if (arkts.isEtsScript(node)) {
98          collectBuilderFunctions(node, builderFunctionNames);
99        }
100        if (!arkts.isStructDeclaration(node)) {
101          return;
102        }
103        validateWrapBuilder(node, builderFunctionNames, context);
104      },
105    };
106  },
107};
108
109export default rule;
110