/** * InterfaceDuplicationTransformOrchestrator * * Thick orchestrator for the interface-duplication-transform rule. * Detects groups of 3+ similar interfaces and consolidates them into: * - A base interface containing the common properties * - One branded type alias per original interface * (const with `unique symbol` for runtime checks) * - A type guard per branded type * - Updated cross-references across the project * * Same-file groups: base interface written to the same file as the original interfaces. * Cross-file groups: base interface written to a dedicated shared models file at * `src/app/libs/shared/models/base/base-{suffix}.model.ts` (configurable via * `baseModelsPath` config option). Each original file receives an import of the * base interface. * * Brand design: the `unique symbol` const is emitted for each branded type. * The type alias itself does NOT use a computed property name (which would trigger * "type literal computed property must be a literal or unique symbol" TS errors in * some in-memory project configurations). The runtime type guard checks the symbol * property via `(req as Record)[BRAND] === true`. * * Three Guarantees: * 1. Idempotent — detects violations on each run; exits immediately when * no groups are found (safe to run multiple times) * 2. Atomic — snapshot + rollback on any failure, including newly * created base files (removed on rollback) * 3. Reversible — project must compile cleanly after each successful run */ import type { TransformContext } from '@angular-modernizer/plugin-system'; import type { PublicApi } from '@angular-modernizer/api'; export declare class InterfaceDuplicationTransformOrchestrator { run(context: TransformContext): Promise; private detectGroups; /** * Pass 1 (read-only): Extract all data from live AST nodes for every group. * Must be called before any replaceWithText() mutations. After mutation, * stored InterfaceDeclaration references become "forgotten" and cannot be read. * * Base interface names are unique per group. Multiple groups that share the * same suffix (e.g. several distinct `Data` subgroups) receive disambiguated * names: first group → `IBaseData`, second → `IBaseData2`, third → `IBaseData3`. * This prevents the second group from reusing the base interface created for * the first and losing its own common properties. */ private prepareAllGroupData; /** * Pass 2 (mutating): Apply a single group's transform using only strings and * SourceFile refs (both remain valid after prior replaceWithText() calls). * Re-fetches each interface by name — returns early if already replaced by * a prior group (idempotency guard in replaceInterfaceWithBrandedType). * * For cross-file groups: creates the base file if needed and injects an import * of the base interface into each spec's source file. * Type guards always go in spec.sourceFile to avoid circular imports between * the base file and the spec files. */ private applyGroupTransform; private buildSpecs; /** FooDialogRequest → FOO_DIALOG_REQUEST_BRAND */ private toBrandConstName; /** * For cross-file groups: ensures the base interface file has imports for every * custom type referenced in the common properties. * * For each non-built-in type name found in property type text, we scan the spec * source files for an existing named import of that type and copy it to the base * file. Relative `./` module specifiers are re-based from the spec file's * directory to the base file's directory. Path-alias specifiers (e.g. * `@enterprise-shared/...`) are used as-is — they are project-wide and resolve the * same regardless of which file they appear in. */ private ensureCommonPropertyTypeImports; private insertBaseInterface; /** * Ensures that `specFile` has an import of `baseInterfaceName` from `baseFilePath`. * Used for cross-file groups where the base interface lives in a shared models file. */ private ensureBaseImport; private replaceInterfaceWithBrandedType; private insertTypeGuard; private resolveAllCrossReferences; /** * BFS propagation of an interface rename through import/re-export chains. * * Starting from `startFilePath` (where `oldName` was replaced by `newName`), * walks the export graph to find every file that references `oldName` and * renames each occurrence. * * Returns the set of file paths that were actually modified (plus `startFilePath`). * The caller uses this set to limit TypeReference updates — files NOT in this set * may have their own (unrelated) declaration or import of `oldName` from a * completely different source, and their type references must not be renamed. * * Handles three patterns per file: * A) `export * from 'providing'` — file transitively re-exports newName; * no text change needed, but add to providing set for downstream scanning. * B) `export { oldName } from 'providing'` — explicit named re-export; rename. * C) `import { oldName } from 'providing'` — named import; rename. * D) `export { oldName }` (no module specifier) — re-export of local binding * (e.g. barrel that imported then re-exported); rename after C updates import. */ private propagateRename; /** * Updates every TypeReference node in `sf` whose type name is exactly `oldName`. * * A single TypeReference traversal covers all cases where the name is used as a * type: parameter types, variable/property declarations, return types, array element * types, union/intersection members, generic type arguments, type aliases, type * assertions (`as X`), conditional type constraints, and more. * * Nodes are collected up-front and processed in reverse document order so that * earlier text positions remain valid while later ones are rewritten. * * The caller is responsible for only invoking this on files that were confirmed to * be part of the rename propagation chain (i.e., files returned by `propagateRename`). * This function adds a last-resort guard: skip if the file still has a LOCAL * declaration of `oldName` — renaming usages there would create broken references. */ private updateAllTypeReferences; /** * Detects the project root by looking for the `src/app` pattern in source file * paths (standard Angular project layout). Falls back to common path prefix. */ private detectProjectRoot; /** "DialogRequest" → "dialog-request", "Dto" → "dto", "Config" → "config" */ private toKebabCase; /** * Returns a map of ALL property names (own + inherited) to their type info (type text + optionality). * * Own properties are resolved from direct PropertySignature declarations. * Inherited properties (from `extends`) are resolved by following the type * symbol's declarations up the extends chain via the TypeScript type system. * Falls back to `{ typeText: 'unknown', optional: false }` only when the declaration cannot be found. */ private getFullPropertyInfoMap; private getPropertyNames; private intersect; } //# sourceMappingURL=interface-duplication-transform.orchestrator.d.ts.map