Skip to content

[typespec-ts] wip split emitter into adapter / codemodel / codegen#3981

Draft
maorleger wants to merge 35 commits into
Azure:mainfrom
maorleger:squad-rewrite
Draft

[typespec-ts] wip split emitter into adapter / codemodel / codegen#3981
maorleger wants to merge 35 commits into
Azure:mainfrom
maorleger:squad-rewrite

Conversation

@maorleger
Copy link
Copy Markdown
Member

@maorleger maorleger commented May 18, 2026

This is the refactor I've wanted for the TypeSpec TS modular emitter for a while: make it boring.

The core problem I noticed was that the old path was reading TCGC types, shaping emitter state, and rendering with ts-morph all mixed together across src/modular/. That works until it doesn't. It makes it much harder to reason about boundaries, test the adaptation layer directly, or move code around without accidentally dragging TCGC assumptions everywhere. The real symptoms show up as hard-to-trace bugs that belong in one layer but live in another.

What I wanted from this refactor:

  • Enforce a three-layer pipeline: TCGC input → adapter (converts to pure IR) → code model (pure data, no side effects) → codegen (renders with ts-morph)
  • Keep the swap in-place so we validate against the existing emitter behavior the entire time instead of incubating a parallel implementation that might diverge
  • Make it obvious where a bug belongs: TCGC adaptation, pure IR shaping, or ts-morph rendering
  • Make the eventual cleanup / deletion story less painful once the dust settles

This PR builds off of #3970 (the seed POC for the separation). The in-place modular refactor landed in stages 0-6, and I ran the entire suite against real Azure specs to shake out customer-found issues. Stages 0-6 are now wired: src/tcgcadapter/adapter.ts is the only place that imports TCGC directly and adapts it into the pure IR in src/codemodel/index.ts. From there, src/codegen/ owns the ts-morph rendering for clients, operations, classical clients/operations, models, enums, unions, response types, API options, LRO helpers, and index files. In other words, "what the SDK is" stays separate from "how we print it".

I also started a north-star package at packages/typespec-ts-pristine/ (commits 6c0f156 through 03abed9, 9 slices in). This imagines what we'd write today knowing what we know now — a clean-room emitter with zero internal monorepo dependencies (no rlc-common, no typespec-ts), liftable with a cp -r. It has a comparator script that validates output equivalence. Early demo results: spread fixture matches at 95.2%, overloads_modular at 85.7%. See packages/typespec-ts-pristine/docs/DESIGN.md for the architecture blueprint.

Worth flagging: the end goal isn't to land this rewrite in autorest.typescript. The emitter is being migrated as-is into typespec-azure, and the real plan is to do this refactor there once the migration lands. This PR is exploratory — it proves the shape works against real fixtures and gets the boring pipeline working so we know what we're committing to.

I could have hidden this behind a feature flag or kept it additive for longer; however, I'd rather do the swap in place and keep one source of truth if possible. The migration is still very much WIP.

Still not done in this PR:

  • ContextManager is still an ambient singleton instead of explicit data flow
  • the adapter still leans on a few helpers from src/modular/helpers/
  • there is still no dedicated src/tcgcadapter/naming.ts module yet

Validation on this branch is green:

  • pnpm build
  • 662 unit tests passing
  • modular + azure-modular integration passing
  • smoke passing

Scope-wise this is 25 files under packages/typespec-ts/ plus a tiny .gitignore housekeeping change for local team-state files. The last commit is just that housekeeping and not part of the architectural story.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

maorleger and others added 14 commits May 18, 2026 17:25
Tasks executed:
1. ✅ Decisions archive: No archival needed (decisions.md < 20KB threshold)
2. ✅ Decision inbox: Merged ripley-staged-refactor-plan.md + copilot-directive into decisions.md
   - Summary of 9-stage pipeline refactor (stages 1–9, stage 10 dropped)
   - Key decisions: swap-in-place, adapter testing focus, readability-first organization
   - Directives: skip lint guard, focus adapter tests, skip package separation
3. ✅ Orchestration log: Created 2026-05-15T2238Z-ripley.md (not git-tracked per .gitignore)
4. ✅ Session log: Created 2026-05-15T2238Z-refactor-plan.md (not git-tracked per .gitignore)
5. ✅ Cross-agent history: Appended Ripley plan summary to dallas/history.md and parker/history.md
6. ✅ History summarization: All history.md < 15KB (no summarization needed)
7. ✅ Git commit: Staged 3 files (decisions.md, dallas/history.md, parker/history.md)
8. ✅ Health report: decisions.md 0→1710 bytes, inbox count 2→0 (after merge)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ne (Stage 1)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ns (Stage 2)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix Stage 3 adapter: add missing isRLCMultiEndpoint import
- Fix child TSClient construction: add allowOptionalSubscriptionId and usesNamespacedContextType properties
- Rewrite adaptFunctionDeclaration for safe handling of OptionalKind types from ts-morph
- Update adapter test expectations for new IR properties (TSClient 16→18, TSMethod 9→17)
- Validate: build ✅, unit tests ✅ (662/662 passing), format ✅
- Document session: .squad/log/2026-05-16T0107Z-stage3-scribe-fixes.md
- Update decisions.md: Stage 2/3 completion notes

Stage 3 now unblocked. Parker ready to implement model/enum/union adapters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Stage 4)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…line (Stage 5)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e (Stage 6a)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ts, serializer placeholders

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ular codegen

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@maorleger maorleger changed the title Squad rewrite WIP: simplify emitter architecture May 18, 2026
@maorleger maorleger changed the title WIP: simplify emitter architecture [typespec-ts] wip split emitter into adapter / codemodel / codegen May 18, 2026
maorleger and others added 8 commits May 18, 2026 22:40
High-level architecture map for the typespec-ts emitter:  entry,
RLC vs Modular split, the three-layer TCGC adapter -> code model -> codegen
pipeline, framework/reference system, two end-to-end flows, and current
known follow-ups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sion

package.json continued to advertise ./api/<resource> subpath exports, but the rewritten root index only re-exported ./api/index.js. That left the source graph unable to reach those barrels, so the dist subpath files and root OptionalParams re-exports were never produced during build.

Restore the missing top-level api/** recursion in root index emission so the producer once again reaches every generated api subbarrel instead of trying to clean things up downstream.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The rewritten client-context renderer was treating every api-version parameter as required based on isApiVersion alone, even when the adapter had already recorded a client default and left the parameter optional. That changed generated *Context interfaces from apiVersion?: string to apiVersion: string for defaulted clients.

Make requiredness follow the adapted TSClientParameter metadata instead of the api-version marker, and lock it in with a modular unit test that renders the new pipeline's client context for a versioned client with a defaulted api-version.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… models renderer

The new pipeline's models renderer was still walking the legacy global emit queue, so it bypassed the adapter's filtered TSCodeModel.models/enums/unions set and reintroduced paging result types like *ListResult onto the public surface.

Switch file selection to the filtered IR and look raw sdk types up only for the legacy declaration/serializer helpers that are still shared with src/modular/emitModels.ts. The production path remains src/codegen/models.ts; the legacy file stays as a rendering helper while this removes the direct TCGC dependency from the codegen layer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply formatter output for the client-context and model-renderer regression fixes after re-running build and unit validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… placeholders

The new filtered-IR renderer in src/codegen/models.ts only walked
codeModel.models/enums/unions and skipped array/dict helper types from the
emitQueue. The serializer builders (buildSerializerFunction.ts,
buildDeserializerFunction.ts) still emit refkey(type, 'serializer') /
refkey(type, 'deserializer') references for those helpers. With no matching
declaration registered, the binder left __PLACEHOLDER_*__ tokens unresolved —
causing 26 TS2304 errors in the SCVMM and NetworkAnalytics repros.

Strategy A: walk emitQueue entries of kind 'array'/'dict' and call emitType()
for each, mirroring what the legacy emitTypes() in emitModels.ts did. This
registers the serializer/deserializer refkeys so the binder can substitute them.

A TODO comment and .squad/decisions/inbox/dallas-models-helpers.md document
the follow-up to migrate these helper types into TSCodeModel IR, removing the
emitQueue side-channel dependency from the codegen layer.

Validated:
- pnpm build: clean
- npm run unit-test (typespec-ts): 664/664 passed
- NetworkAnalytics.Management regen: zero __PLACEHOLDER_ matches, zero TS2304

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
maorleger and others added 4 commits May 20, 2026 21:18
Implement Strategy B for modular model helpers by adapting array, dict, and named nullable wrapper types into TSCodeModel.helperTypes and consuming that IR from codegen/models.ts instead of the legacy emitQueue patch.

This keeps helper registration in the adapter, removes the dead Strategy A loop from the renderer, and leaves the extensible-enum follow-up (U1) explicitly scoped to adaptEnums.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These two files were committed before .gitignore caught .squad/. Untracking
so the squad-rewrite branch stays free of squad meta-files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three-layer pipeline architecture (adapter → code model → renderer) following
the proven patterns from typespec-rust and autorest.go. Contains:

- Design document (docs/DESIGN.md) with full surface inventory and IR contract
- Code model IR types (src/codemodel/) — pure data, no TCGC imports
- Adapter stub (src/tcgcadapter/) — TCGC boundary, only layer that imports TCGC
- Renderer stub (src/codegen/) — consumes IR only, one function per file kind
- Comparator scaffold (src/comparator/) — diff tool interface for validation
- Package infrastructure (package.json, tsconfig.json, README.md)

All stubs compile cleanly. No implementation yet — architecture only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
maorleger and others added 9 commits May 28, 2026 19:40
…ls + comparator on spread

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… selection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…SDK metadata

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ly complete

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…loads_modular

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant