/** * @angular-modernizer/plugin-angular - Presentational Component Violation Rule * * Detects Angular components that should be presentational (dumb) but contain * business logic, service dependencies, or state management. * * Presentational components should focus solely on rendering UI and delegate * all business logic to parent (smart/container) components through * @Input/@Output communication. * * Detection targets: * 1. Service dependencies injected via constructor or inject() * 2. .subscribe() calls in lifecycle hooks (excluding takeUntilDestroyed) * 3. Methods calling services without a corresponding @Output / output() signal * 4. Business logic methods (keyword + complexity > 3 branching tokens) * 5. Complex local state (arrays, Maps, Sets of 3+ non-Input properties) */ import type { AnalysisRule, AnalysisContext, AnalysisResult } from '@angular-modernizer/plugin-system'; /** * Dialog detection configuration for PresentationalComponentViolationRule. * * Dialog components are container components — they manage service calls and * local state by design. They should not be flagged as presentational violations. * * Configure in `.angular-modernizer.json`: * ```json * { * "rules": { * "angular:presentational-component-violation": { * "dialogDetection": { * "injectedClassNames": ["MatDialogRef", "MAT_DIALOG_DATA"], * "baseClassNames": ["AppDialogBaseComponent", "BaseDialogComponent"] * } * } * } * } * ``` */ export interface DialogDetectionConfig { /** * Constructor-injected or inject()-based class names that mark a component * as a dialog. Any component that has one of these as a dependency is skipped. * Default: `['MatDialogRef']` */ injectedClassNames: string[]; /** * Base class names that mark a component as a dialog. * Any component extending one of these classes is skipped. * Default: `[]` */ baseClassNames: string[]; } export interface PresentationalComponentViolationConfig { dialogDetection: DialogDetectionConfig; /** * Class name patterns that identify "Action Tile" components. * When a component whose name matches any of these patterns also has a * `service-dependency` violation, the violation severity is escalated * to `error` and supplementary action-tile guidance is added to the message. * * Default: `['TileComponent', 'ActionTile', 'ActionTileComponent']` */ actionTilePatterns: string[]; } /** * Presentational Component Violation Analysis Rule * * @example * ```typescript * const rule = new PresentationalComponentViolationRule(); * const violations = await rule.analyze(context); * // violations contain service-dependency, subscription-in-lifecycle, * // missing-output, business-logic, complex-state-management entries * ``` */ export declare class PresentationalComponentViolationRule implements AnalysisRule { readonly id = "angular:presentational-component-violation"; readonly name = "Presentational Component Violation"; readonly description = "Detects components that should be presentational but contain service dependencies, business logic, or complex state management"; readonly severity: "warning"; readonly category = "angular-architecture"; readonly tags: string[]; private readonly config; constructor(config?: { dialogDetection?: Partial; actionTilePatterns?: string[]; }); analyze(context: AnalysisContext): Promise; private isComponent; /** * Returns true when the component is a dialog — i.e. it extends a configured * dialog base class OR injects a configured dialog token/service. * * Checks both injection styles: * - Constructor param: `constructor(private ref: MatDialogRef) {}` * - inject() field: `private ref = inject(MatDialogRef)` */ private isDialogComponent; /** * Collect all field names that are service dependencies — covering both * constructor injection and inject() function calls. * Used by detectServiceDependencies AND detectMissingOutputs to keep the * service taxonomy consistent between both detectors. */ private collectServiceFieldNames; private isServiceType; private detectServiceDependencies; private serviceDependencyViolation; private detectSubscriptionsInLifecycle; private detectMissingOutputs; /** * Collect output names from both @Output() decorators and output() signals. */ private collectOutputNames; /** * Convention-based matching: deleteClick, deleteEvent, deleteChange, onDelete. * Avoids bidirectional substring matching which causes false negatives * ('edit' would suppress 'editable'; 'save' would suppress 'savedItemsList'). */ private hasCorrespondingOutput; private detectBusinessLogicMethods; private detectComplexStateManagement; private isInputOrOutput; /** * Simple UI-toggle state that is legitimate in presentational components. * Uses startsWith/endsWith to avoid matching mid-word (e.g. 'listItems' contains 'is'). */ private isSimpleUIState; /** * Complex state: arrays, Maps, Sets. * Uses ts-morph Type API rather than string comparison to handle union types * (e.g. 'string | null' must not count as complex). */ private isComplexStateType; /** * Detects dialog component getters that embed validation logic (≥2 IfStatements). * Validation logic in getters couples the dialog to its data model and makes * testing harder — it belongs in the data model or a dedicated validator service. */ private analyzeDialogGetterValidation; /** * Returns true when the component class name matches any of the configured * action-tile patterns (substring check, case-sensitive). */ private matchesActionTilePattern; /** * If the component is an action tile, escalate any `service-dependency` * violations: severity promoted to `error` in metadata and guidance appended * to the message. All other violations are returned unchanged. */ private applyActionTileEscalation; } //# sourceMappingURL=presentational-component-violation.rule.d.ts.map