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