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 { getAnnotationName, PresetDecorators } from '../utils'; 18import { UISyntaxRule, UISyntaxRuleContext } from './ui-syntax-rule'; 19 20// Decorators that cannot be repeated 21const validDecorators = [ 22 PresetDecorators.ENTRY, 23 PresetDecorators.COMPONENT_V1, 24 PresetDecorators.COMPONENT_V2, 25 PresetDecorators.REUSABLE_V1, 26 PresetDecorators.PREVIEW, 27 PresetDecorators.REUSABLE_V2, 28 PresetDecorators.CUSTOM_DIALOG, 29]; 30 31function checkForDuplicateDecorators(context: UISyntaxRuleContext, node: arkts.StructDeclaration): void { 32 // Initialize a map to record decorators and their occurrences 33 const decoratorCounts: Map<string, { count: number, annotations: arkts.AnnotationUsage[] }> = new Map(); 34 if (!node.definition || !node.definition.annotations) { 35 return; 36 } 37 // Record all decorators and their counts 38 node.definition.annotations.forEach((annotation) => { 39 const decoratorName = getAnnotationName(annotation); 40 if (!validDecorators.includes(decoratorName)) { 41 return; 42 } 43 44 if (decoratorCounts.has(decoratorName)) { 45 const decoratorInfo = decoratorCounts.get(decoratorName)!; 46 decoratorInfo.count += 1; 47 decoratorInfo.annotations.push(annotation); 48 } else { 49 decoratorCounts.set(decoratorName, { count: 1, annotations: [annotation] }); 50 } 51 }); 52 53 // Process decorators with more than one occurrence 54 decoratorCounts.forEach(({ count, annotations }, decoratorName) => { 55 if (count <= 1) { 56 return; 57 } 58 // Report errors for all occurrences except the last one 59 for (let i = 0; i < annotations.length - 1; i++) { 60 const prevAnnotation = annotations[i]; 61 reportDuplicateDecorator(context, prevAnnotation); 62 } 63 // For the last occurrence, report an error but do not provide a fix 64 const lastAnnotation = annotations[annotations.length - 1]; 65 context.report({ 66 node: lastAnnotation, 67 message: rule.messages.duplicateDecorator, 68 }); 69 }); 70} 71 72function reportDuplicateDecorator(context: UISyntaxRuleContext, annotation: arkts.AnnotationUsage): void { 73 context.report({ 74 node: annotation, 75 message: rule.messages.duplicateDecorator, 76 fix: () => { 77 const startPosition = arkts.getStartPosition(annotation); 78 const endPosition = arkts.getEndPosition(annotation); 79 return { 80 range: [startPosition, endPosition], 81 code: '', 82 }; 83 }, 84 }); 85} 86 87const rule: UISyntaxRule = { 88 name: 'no-duplicate-decorators', 89 messages: { 90 duplicateDecorator: `Duplicate decorators for struct are not allowed.`, 91 }, 92 setup(context) { 93 return { 94 parsed: (node): void => { 95 if (!arkts.isStructDeclaration(node)) { 96 return; 97 } 98 checkForDuplicateDecorators(context, node); 99 }, 100 }; 101 }, 102}; 103 104export default rule;