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