/** * @angular-modernizer/plugin-angular - DTO Object Literal Transform Rule * * Rewrites direct `new X()` DTO instantiation followed by sequential property * assignments into typed object literals, making property mapping explicit and * type-safe. * * This transformation follows the "Thin Rule, Thick Orchestrator" pattern: * - Rule: Thin wrapper (≤50 lines) delegating to orchestrator * - Orchestrator: Contains all business logic for transformation * * Guarantees: * - Idempotent: Safe to run multiple times (no NewExpression remains after rewrite) * - Atomic: All or nothing per source file * - Reversible: Can be undone via git reset */ import type { TransformRule, TransformContext, TransformResult, } from '@angular-modernizer/plugin-system'; import type { PublicApi } from '@angular-modernizer/api'; import { DtoObjectLiteralOrchestrator, type DtoDetectionConfig, } from '../orchestrators/dto-object-literal.orchestrator.js'; const DEFAULT_CONFIG: DtoDetectionConfig = { classNamePatterns: [/.*Response$/, /.*Dto$/, /.*Result$/], }; /** * DTO Object Literal Transform Rule — rewrites direct DTO instantiation to object literals. * * @example * ```typescript * // Before * const r = new UserResponse(); * r.id = 1; * r.name = 'Alice'; * * // After * const r = { id: 1, name: 'Alice' }; * ``` */ export class DtoObjectLiteralTransformRule implements TransformRule { public readonly id = 'angular:dto-object-literal-transform'; public readonly name = 'DTO Object Literal Transform'; public readonly description = 'Rewrites direct new X() DTO instantiation with sequential property assignments into typed object literals'; public readonly category = 'modernization'; public readonly tags = ['angular', 'dto', 'object-literal', 'type-safety']; private readonly orchestrator = new DtoObjectLiteralOrchestrator(); private readonly config: DtoDetectionConfig; constructor(config?: Partial) { this.config = { ...DEFAULT_CONFIG, ...config }; } async transform( context: TransformContext, ): Promise { const { sourceFile, filePath } = context; if (filePath.endsWith('.d.ts')) { return { ruleId: this.id, modified: false, message: 'Skipped declaration file', filePath, }; } const initialText = sourceFile.getFullText(); const result = this.orchestrator.run(context, this.config); const modified = sourceFile.getFullText() !== initialText; return { ruleId: this.id, modified, message: modified ? `Rewrote ${result.changeCount} DTO instantiation(s) as object literals (${result.skippedCount} skipped)` : 'No DTO object literal changes needed', filePath, changeCount: result.changeCount, }; } }