|
| 1 | +# Project Context |
| 2 | + |
| 3 | +- **Owner:** Maor Leger |
| 4 | +- **Project:** autorest.typescript — TypeSpec TS emitter refactor to align architecture with `typespec-rust` and `autorest.go` emitters. |
| 5 | +- **Primary reading list:** |
| 6 | + - `~/workspace/emitter-chain/typespec-rust` — TypeSpec Rust emitter (the closer cousin — same TypeSpec front-end) |
| 7 | + - `~/workspace/emitter-chain/autorest.go` — Go autorest generator |
| 8 | + - `/home/maorleger/workspace/emitter-chain/go-rust.md` — synthesized architecture doc comparing the two. START HERE. |
| 9 | +- **Target codebase:** `packages/typespec-ts/src/` — especially `codemodel/` (already follows a data/render split: types.ts, build-*.ts, render-*.ts), `modular/`, `rlc/`, `framework/`, `static-helpers/`. |
| 10 | +- **Stack note:** TS emitter is built on the TypeSpec compiler + TCGC (@azure-tools/typespec-client-generator-core) + ts-morph for code rendering. Whatever IR pattern exists in Rust/Go needs to fit into this pipeline. |
| 11 | +- **Maintenance-mode:** `packages/autorest.typescript/` — do not analyze for this refactor. |
| 12 | +- **Created:** 2026-05-15 |
| 13 | + |
| 14 | +## Learnings |
| 15 | + |
| 16 | +<!-- Append new learnings below. Each entry is something lasting about the project. --> |
| 17 | + |
| 18 | +### 2026-05-15 — TS Emitter vs Rust/Go Architecture Delta |
| 19 | + |
| 20 | +**Pipeline shape confirmed:** |
| 21 | +- Entry: `src/index.ts` `$onEmit()` — 500+ lines, drives everything via nested async functions |
| 22 | +- RLC path: `src/transform/transform.ts:43` `transformRLCModel()` is a clean TCGC→IR adapter, producing `RLCModel` from `@azure-tools/rlc-common`. This is the model to copy for Modular. |
| 23 | +- Modular path: NO intermediate IR. `src/modular/build*.ts` functions receive `SdkClientType<SdkServiceOperation>` (TCGC type) directly. Rendering and TCGC consumption are fused in the same function. |
| 24 | +- `src/codemodel/` does NOT exist (despite history.md description). It is a target location, not existing code. |
| 25 | +- `ContextManager` singleton at `src/contextManager.ts:41` carries: `outputProject`, `tcgcContext`, `sdkTypes`, `binder`, `dependencies`, `rlcMetaTree`, `symbolMap`. |
| 26 | + |
| 27 | +**Key citations to reuse:** |
| 28 | +- `src/index.ts:203` — is-modular-library branch point |
| 29 | +- `src/index.ts:357` — flat `for (subClient of clientMap)` loop (no recursive tree walk) |
| 30 | +- `src/index.ts:411–417` — final ts-morph file write loop |
| 31 | +- `src/transform/transform.ts:43` — `transformRLCModel()` — RLC adapter; use as pattern |
| 32 | +- `src/modular/buildOperations.ts:50` — `buildOperationFiles()` — imports SdkClientType directly |
| 33 | +- `src/modular/emitModels.ts:20–35` — imports ~10 TCGC types directly |
| 34 | +- `src/modular/helpers/namingHelpers.ts:13` — naming spread across helpers, no dedicated layer |
| 35 | +- `src/framework/hooks/sdkTypes.ts:79` — `provideSdkTypes()` — partial TCGC wrapper |
| 36 | +- `src/framework/hooks/binder.ts:34` — `Binder` interface — good pattern for explicit DI |
| 37 | + |
| 38 | +**Rust/Go citations confirmed:** |
| 39 | +- Rust adapter: `typespec-rust/packages/typespec-rust/src/tcgcadapter/adapter.ts:58` — `class Adapter` |
| 40 | +- Rust IR root: `typespec-rust/packages/typespec-rust/src/codemodel/crate.ts:13` — `interface Crate` |
| 41 | +- Rust naming: `typespec-rust/packages/typespec-rust/src/tcgcadapter/naming.ts:18` — `getEscapedReservedName` |
| 42 | +- Go IR root: `autorest.go/packages/codemodel.go/src/codeModel.ts:9` — `interface CodeModel` |
| 43 | +- Go naming: `autorest.go/packages/naming.go/src/naming.ts:9` — `getEscapedReservedName`, `ensureNameCase` |
| 44 | + |
| 45 | +**Top 3 deltas (filed to .squad/decisions/inbox/lambert-architecture-delta-v1.md):** |
| 46 | +1. No language-specific IR for Modular path (Rust: `Crate`, Go: `CodeModel`, TS Modular: nothing) |
| 47 | +2. No dedicated adapter/transform phase for Modular (TCGC leaks into every build* function) |
| 48 | +3. ContextManager ambient singleton vs explicit data flow / injectable DI |
| 49 | + |
| 50 | +**Impedance mismatches noted:** |
| 51 | +- ts-morph AST vs string builders — ts-morph is *correct* for TS emitter; intent (separation) matters, not mechanism |
| 52 | +- Rust trait/impl vs TS structural typing — IR should use discriminated unions (kind: "model"|"enum"|...) |
| 53 | +- Go `FsFacilities` injectable FS — modular path actually close to this already (in-memory Project, single write loop) |
| 54 | +- Go `fixStutteringTypeNames()` named pass — TS has ad-hoc `normalizeName()` calls; consolidation needed |
| 55 | + |
| 56 | +### 2026-05-15 — PR #3970 POC Findings (addendum) |
| 57 | + |
| 58 | +**PR #3970 (`origin/poc-emitter-separation`, commit `4459962`) — confirmed details:** |
| 59 | +- 5 new files only. Zero existing files modified. `src/index.ts` and `src/modular/buildClientContext.ts` are identical to main. |
| 60 | +- New dirs: `src/codemodel/index.ts` (IR), `src/tcgcadapter/adapter.ts` (adapter), `src/codegen/{emitter,clients,index}.ts` (renderer) |
| 61 | +- The new pipeline is NOT yet wired into `$onEmit` — pure additive infrastructure |
| 62 | +- Unit tests 639 pass, smoke tests pass — validates the IR shape compiles and is logically sound |
| 63 | +- `TSCodeModel` shape: `{ clients: TSClient[], settings: TSGenerationSettings }` — correct root |
| 64 | +- `TSClient` has `id`, `name`, `modularName`, `contextTypeName`, `parameters: TSClientParameter[]`, `apiVersion`, `endpoint`, `credential`, `operationGroups`, `methods`, `children`, `path` |
| 65 | +- Models/enums/unions are NOT yet in the IR — only client context scope covered |
| 66 | +- Adapter at `adapter.ts:75` — `adaptToCodeModel(input)`, `adaptSingleClient(...)` at line 94 |
| 67 | +- Adapter reuses `src/modular/helpers/` helpers (lines 29, 30, 40–43) — pragmatic, needs migration plan |
| 68 | +- Codegen at `codegen/clients.ts` — `emitClientContext(project, client, settings)` — zero TCGC imports ✓ |
| 69 | +- `useDependencies()` / `resolveReference()` still called in codegen — acceptable (narrow framework hooks, not TCGC) |
| 70 | +- First-stage refactor: wire `adaptSingleClient` + `emitFromCodeModel` into `index.ts` `generateModularSources()` replacing `buildClientContext()` call — one-for-one swap, integration-test validated |
| 71 | + |
| 72 | +**Key file citations:** |
| 73 | +- POC IR root: `packages/typespec-ts/src/codemodel/index.ts:26` — `interface TSCodeModel` |
| 74 | +- POC adapter entry: `packages/typespec-ts/src/tcgcadapter/adapter.ts:75` — `adaptToCodeModel()` |
| 75 | +- POC codegen entry: `packages/typespec-ts/src/codegen/emitter.ts:25` — `emitFromCodeModel()` |
| 76 | +- POC client renderer: `packages/typespec-ts/src/codegen/clients.ts:31` — `emitClientContext()` |
| 77 | +- Wiring target: `packages/typespec-ts/src/index.ts` around line 361 (`buildClientContext` call) |
| 78 | + |
| 79 | +### 2026-05-15 — plan.md Reconciliation (addendum) |
| 80 | + |
| 81 | +**plan.md is Maor's post-POC writeup (`poc-emitter-separation` + `poc-emitter-separation-complete`). Key takeaways:** |
| 82 | + |
| 83 | +Doubly validated (plan + go-rust.md + source): |
| 84 | +- Three-layer pipeline, one-pass build, directory enforcement sufficient, smoke tests as oracle, getOperationFunction() as choke point |
| 85 | + |
| 86 | +Divergences from Go/Rust (noted in pattern note §Reconciling): |
| 87 | +- PRD 2 self-contradiction: marked done locally, also advised against as standalone step → flag for Ripley |
| 88 | +- `CodeExpr = (string | SymbolRef)[]` (plan §2.2): TS-specific, correct divergence, must stay in codegen not codemodel |
| 89 | +- `TSTypeRef` deferred: OK but use typed discriminants (TSMethodKind, TSLroMetadata) as mitigation |
| 90 | + |
| 91 | +Critical "aspirational vs shipped" finding: |
| 92 | +- ALL plan.md "✅ Done" items are local only. NOTHING from PRDs 1–13 is on main. Even the index.ts wiring (PRD 1 scope item) is not committed in PR #3970. |
| 93 | + |
| 94 | +Missing from plan vs Go/Rust: |
| 95 | +1. Adapter type cache (Go: `Map<string, go.WireType>`) — add to PRD 7 design |
| 96 | +2. Named naming layer `src/tcgcadapter/naming.ts` — no PRD for this |
| 97 | +3. ESLint `no-restricted-imports` on `src/codegen/` and `src/codemodel/` — cheap layer guard |
| 98 | +4. `Readonly<TSCodeModel>` in codegen signatures — one-way flow enforcement |
| 99 | + |
| 100 | +Key plan.md citations for future reference: |
| 101 | +- §2.1: operationHelpers.ts 3321 LOC choke point; extractOperationShape() as fix |
| 102 | +- §2.2: CodeExpr pattern; resolveReference side effects |
| 103 | +- §2.3: refkey is process-local, not serializable → stable semantic IDs in IR |
| 104 | +- §2.5: addDeclaration is renderer concern; store declarationRefkey in IR |
| 105 | +- §2.13: full TSCodeModel needs models/enums/unions/helpers at root |
| 106 | +- §PRD sequence: PRD 3 → PRD 4 → PRDs 5,6,8 parallel → PRD 7 → PRDs 9,10,11 → PRD 12 → PRD 13 |
| 107 | + |
| 108 | +### 2026-05-18T19:09:04.148+00:00 — PR #3981 description rewrite |
| 109 | + |
| 110 | +- Rewrote the PR title/body in Maor's voice to explain the Rust/Go-inspired three-layer migration (`tcgcadapter/` → `codemodel/` → `codegen/`), call out stages 0-6 as the current in-place swap, and explicitly list the three deferred deltas. |
| 111 | + |
| 112 | +## 2026-05-18T21:30Z — ARCHITECTURE.md for packages/typespec-ts |
| 113 | +Wrote `packages/typespec-ts/ARCHITECTURE.md` (~270 lines): entry-point summary, |
| 114 | +RLC vs Modular framing, ASCII diagram of the three-layer Modular pipeline, |
| 115 | +TCGC adapter / code model / codegen sections with file links, framework + static |
| 116 | +helpers section, two end-to-end flows (full $onEmit, single paged operation), |
| 117 | +testing pointer table, legacy notes, and deferred work (ContextManager singleton, |
| 118 | +adapter→modular/helpers coupling, missing naming.ts, codegen/models.ts still |
| 119 | +imports TCGC). Verified against src/index.ts, src/tcgcadapter/adapter.ts, |
| 120 | +src/codemodel/index.ts, src/codegen/*.ts, and CONTRIBUTING.md. Not committed — |
| 121 | +Maor will review and commit. |
| 122 | + |
| 123 | +### 2026-05-18T21:01:22.901+00:00 — Paging helper import must stay module-pinned |
| 124 | + |
| 125 | +- Root cause: `packages/typespec-ts/src/modular/helpers/operationHelpers.ts` emitted `PagedAsyncIterableIterator` as a free symbol in public signatures, while `packages/typespec-ts/src/codegen/indexFiles.ts` and `packages/typespec-ts/src/modular/buildRootIndex.ts` also exposed the same type through barrels. That left multiple plausible import sources and led to the post-hoc cleanup in `packages/typespec-ts/src/codegen/pagingImports.ts`. |
| 126 | +- Fix shape: pin the public type to `static-helpers/pagingHelpers.js` at the emission sites (`src/codegen/operations.ts`, `src/codegen/classicalClient.ts`, `src/codegen/classicalOperations.ts`) and pin barrel re-exports to the same concrete module (`src/codegen/indexFiles.ts`, `src/modular/buildRootIndex.ts`). The renderer now writes the import/export edge directly instead of asking a later sweep to guess. |
| 127 | +- Prior-art note: this matches the Rust/Go pattern where symbol provenance rides with the import edge rather than getting reconstructed from a barrel after the fact. In the TS emitter, the equivalent primitive is an explicit `moduleSpecifier` on the generated import/export. |
0 commit comments