/** * @angular-modernizer/plugin-angular - Interface Duplication Transform Rule * * Thin wrapper (≤50 lines) following the "Thin Rule, Thick Orchestrator" pattern. * All business logic is in InterfaceDuplicationTransformOrchestrator. * * Transformation: * - Groups of 3+ interfaces sharing a suffix and common properties are consolidated * into a base interface + branded type aliases using unique symbols. * - All cross-references (imports, type annotations, generics, re-exports) are * updated automatically across the project. * * Guarantees: Idempotent, Atomic, Reversible. * * @example * ```typescript * const rule = new InterfaceDuplicationTransformRule(); * const result = await rule.transform(context); * if (result.modified) console.info('Interfaces consolidated'); * ``` */ import type { TransformRule, TransformContext, TransformResult, } from '@angular-modernizer/plugin-system'; import type { PublicApi } from '@angular-modernizer/api'; import { InterfaceDuplicationTransformOrchestrator } from '../orchestrators/interface-duplication-transform.orchestrator.js'; export class InterfaceDuplicationTransformRule implements TransformRule { public readonly id = 'angular:interface-duplication-transform'; public readonly name = 'Interface Duplication Transform'; public readonly description = 'Consolidates duplicate interface families into a base interface + branded type aliases'; public readonly category = 'modernization'; public readonly tags = [ 'angular', 'typescript', 'duplication', 'interface', 'branded-types', ]; private readonly orchestrator = new InterfaceDuplicationTransformOrchestrator(); async transform( context: TransformContext, ): Promise { if (context.filePath.endsWith('.d.ts')) { return { ruleId: this.id, modified: false, message: 'Skipped declaration file', filePath: context.filePath, }; } const initialTexts = new Map( context.project .getSourceFiles() .map((sf) => [sf.getFilePath(), sf.getFullText()]), ); await this.orchestrator.run(context); const modified = context.project .getSourceFiles() .some((sf) => sf.getFullText() !== initialTexts.get(sf.getFilePath())); return { ruleId: this.id, modified, message: modified ? 'Interface families consolidated with branded types' : 'No interface duplication groups found', filePath: context.filePath, }; } }