• 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, getAnnotationUsage, PresetDecorators } from '../utils';
18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule';
19
20const BUILD_NAME: string = 'build';
21const BUILD_ROOT_NUM: number = 1;
22const STATEMENT_LENGTH: number = 1;
23
24function isBuildOneRoot(statements: readonly arkts.Statement[], buildNode: arkts.Identifier,
25  context: UISyntaxRuleContext): void {
26  if (statements.length > STATEMENT_LENGTH && buildNode) {
27    context.report({
28      node: buildNode,
29      message: rule.messages.invalidBuildRootCount,
30    });
31  }
32}
33
34function checkBuildRootNode(node: arkts.AstNode, context: UISyntaxRuleContext): void {
35  const loadedContainerComponents = context.containerComponents;
36  if (!arkts.isStructDeclaration(node)) {
37    return;
38  }
39  const entryDecoratorUsage = getAnnotationUsage(node, PresetDecorators.ENTRY);
40  node.definition.body.forEach(member => {
41    // Determine the number of root node
42    if (!arkts.isMethodDefinition(member) || getIdentifierName(member.name) !== BUILD_NAME) {
43      return;
44    }
45    const blockStatement = member.scriptFunction.body;
46    if (!blockStatement || !arkts.isBlockStatement(blockStatement)) {
47      return;
48    }
49    const buildNode = member.scriptFunction.id;
50    const statements = blockStatement.statements;
51    // rule1: The 'build' method cannot have more than one root node.
52    if (buildNode) {
53      isBuildOneRoot(statements, buildNode, context);
54    }
55    if (statements.length !== BUILD_ROOT_NUM) {
56      return;
57    }
58    // Determine whether it is a container component
59    const expressionStatement = statements[0];
60    if (!arkts.isExpressionStatement(expressionStatement)) {
61      return;
62    }
63    const callExpression = expressionStatement.expression;
64    if (!arkts.isCallExpression(callExpression)) {
65      return;
66    }
67    const componentName = callExpression.expression.dumpSrc();
68    let isContainer: boolean = false;
69    loadedContainerComponents?.forEach(container => {
70      if (componentName.includes(container)) {
71        isContainer = true;
72      }
73    });
74    // rule2: If the component is decorated by '@Entry',
75    // its 'build' function can have only one root node, which must be a container component.
76    if (entryDecoratorUsage && !isContainer && buildNode) {
77      context.report({
78        node: buildNode,
79        message: rule.messages.invalidBuildRoot,
80      });
81    }
82  });
83}
84
85const rule: UISyntaxRule = {
86  name: 'build-root-node',
87  messages: {
88    invalidBuildRootCount: `The 'build' method cannot have more than one root node.`,
89    invalidBuildRoot: `If the component is decorated by '@Entry', its 'build' function can have only one root node, which must be a container component.`
90  },
91  setup(context) {
92    return {
93      parsed: (node): void => {
94        checkBuildRootNode(node, context);
95      },
96    };
97  },
98};
99
100export default rule;
101