/** * @angular-modernizer/plugin-angular - Too Many Inputs Rule * * Detects Angular components that have too many @Input properties. * Components with many inputs can become complex and hard to maintain. * * Detection Patterns: * - Components with more than a configurable number of @Input properties * - Components that might benefit from being split into smaller components * * This rule promotes component composition and maintainability. */ import { SyntaxKind, type ClassDeclaration } from 'ts-morph'; import type { AnalysisRule, AnalysisContext, AnalysisResult, } from '@angular-modernizer/plugin-system'; /** * Too Many Inputs Rule - detects components with too many @Input properties. */ export class TooManyInputsRule implements AnalysisRule { public readonly id = 'angular:too-many-inputs'; public readonly name = 'Too Many Inputs'; public readonly description = 'Detects Angular components that have too many @Input properties'; public readonly severity = 'warning'; public readonly category = 'angular-component'; public readonly tags = ['angular', 'component', 'inputs', 'maintainability']; // Configurable threshold for maximum number of inputs private readonly maxInputs = 5; async analyze(context: AnalysisContext): Promise { const violations: AnalysisResult[] = []; const sourceFile = context.sourceFile; // Find all class declarations const classes = sourceFile.getDescendantsOfKind( SyntaxKind.ClassDeclaration, ); for (const classDecl of classes) { const violation = this.analyzeClass(classDecl, sourceFile.getFilePath()); if (violation) { violations.push(violation); } } return violations; } private analyzeClass( classDecl: ClassDeclaration, filePath: string, ): AnalysisResult | null { const className = classDecl.getName(); if (!className) { return null; } // Check if this is a component const componentDecorator = this.getComponentDecorator(classDecl); if (!componentDecorator) { return null; } // Count @Input properties const inputCount = this.countInputs(classDecl); if (inputCount <= this.maxInputs) { return null; } const startLine = classDecl.getStartLineNumber(); const startColumn = classDecl.getStart() - classDecl.getStartLinePos(); return { ruleId: this.id, message: `Component '${className}' has ${inputCount} @Input properties, which exceeds the recommended maximum of ${this.maxInputs}. Consider splitting this component or using a configuration object.`, filePath, line: startLine, column: startColumn, suggestedFix: `Consider using an input configuration object or splitting the component into smaller, focused components`, metadata: { className, violationType: 'too-many-inputs', inputCount, maxInputs: this.maxInputs, isComponent: true, framework: 'angular', }, }; } private getComponentDecorator(classDecl: ClassDeclaration) { const decorators = classDecl.getDecorators(); return decorators.find((decorator) => decorator.getName() === 'Component'); } private countInputs(classDecl: ClassDeclaration): number { const properties = classDecl.getProperties(); return properties.filter((prop) => { const decorators = prop.getDecorators(); return decorators.some((decorator) => decorator.getName() === 'Input'); }).length; } }