Skip to content

docs(client-generator): ADRs and helper-surface analysis for the generator rewrite - #3047

Draft
RomanHotsiy wants to merge 2 commits into
feat/agent-friendlyfrom
docs/generator-rewrite-adrs
Draft

docs(client-generator): ADRs and helper-surface analysis for the generator rewrite#3047
RomanHotsiy wants to merge 2 commits into
feat/agent-friendlyfrom
docs/generator-rewrite-adrs

Conversation

@RomanHotsiy

@RomanHotsiy RomanHotsiy commented Aug 21, 2026

Copy link
Copy Markdown
Member

What/Why/How?

Design docs only — no code changes. Records the decisions behind the planned
generator rewrite before any of it is implemented, plus the measured analysis
those decisions rest on.

Three ADRs

  • [ADR-0020] Self-contained generator folders, ejected as source. Today python/go/php eject as their own type-stripped source, while the seven TypeScript-family generators esbuild-bundle ~24 modules — the ejected typescript.mjs is 178 kB opening with __defProp shims and inlining copies of authoring/ code that is already public API. Every generator becomes one folder with the same stage skeleton, ejected as .ts — including the single-file generators, which are re-grouped into the same stages rather than grandfathered. Self-containment was never the goal on its own; leaving a 953-line python/index.ts and a 1169-line go/index.ts whole would keep the very asymmetry this removes. Their existing functions sort into the stages as they are, so it is a re-grouping and not a rewrite. The .replaceAll("'../../authoring/index.js'", …) import rewrite is replaced by real package specifiers plus a guard test.
  • [ADR-0021] Text printers — one common printer plus one per language. Supersedes ADR-0001, which still specifies ts.factory AST codegen; emitters/ts.ts and emitters/package-client.ts no longer exist and every generator emits text. The boundary is syntax vs shape: the printer owns identifier safety, escaping, literals, comments and layout; the generator writes classes and signatures as template literals so the emitted code stays readable in the file the user owns.
  • [ADR-0022] Runtime is inline or a sibling module; package mode removed. Amends ADR-0017 point 3. Package mode is the sole reason entry-weight.test.ts exists, it makes the TypeScript runtime dual-purpose, and it silently ignores an ejected runtime the user edited. runtime: 'module' serves its actual purpose — deduplication — while staying zero-dependency.

The analysis (docs/helper-surface.md) is a point-in-time inventory: 87 files, 15,913 lines, 183 exported values, measured by reachability, direct symbol use, and toolkit use.

Two findings drove the decisions:

  • authoring/ is not a neutral toolkit in practice — it is the non-TypeScript toolkit. Ten of its sixteen helpers have exactly three consumers, always go/php/python. No TypeScript-family generator uses Printer, docText, identifierFor, or any schema-shape helper; TypeScript keeps a complete shadow implementation in emitters/.
  • The seven TypeScript generators share four functions totalling 27 lines (safeIdent, pascalCase, codeLiteral, codeString). The "large shared TypeScript emitter layer" is mostly the typescript generator's own body living in a shared directory.

Twelve duplications are catalogued with file:line evidence. Three are defects, not untidiness:

  • Two TypeScript string escapers with different security policies — codeString escapes U+2028/U+2029; sanitizeCodeString also escapes </> to stop a </script> breakout. Which protection applies depends on which one the caller imported.
  • Python and Go have no string escaper at all — 19 and 28 raw JSON.stringify calls.
  • Two pagination resolvers implementing the same precedence rule; they can disagree about whether an operation paginates.

Reference

Follow-up to #3016. Implementation will land in separate PRs against that branch.

Testing

Documentation only; no code changed, so no tests apply. Cross-document links and every cited file:line were verified against the source.

Note: the pre-commit hook was bypassed. npm run lint currently fails on ~200 pre-existing files across all packages because the local node_modules has oxlint@1.79.0 against a declared ^1.48.0, so newer rules fire. Zero errors are in the files this PR touches.

Screenshots (optional)

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

No code changes, so no direct security impact. The analysis does document two
existing security-relevant issues for the rewrite to fix: the divergent
TypeScript string-escaping policies (</script> breakout protection applies
only on one path) and the absence of any defined escaping policy in the Python
and Go generators.

🤖 Generated with Claude Code


Note

Low Risk
Documentation only; no runtime or API code changes. The ADRs do commit to later breaking removals of package-mode runtime.

Overview
Adds design docs for the planned client-generator rewrite: three accepted ADRs plus a point-in-time helper inventory. No implementation.

ADR-0020 makes every generator a self-contained folder ejected as TypeScript source (no esbuild bundle or import rewrite). ADR-0021 supersedes ADR-0001: codegen is text via a structural Printer plus per-language syntax printers. ADR-0022 amends ADR-0017 by dropping runtime: 'package' in favor of 'inline' | 'module'.

helper-surface.md measures sharing (TS-family generators share four functions / 27 lines) and catalogs twelve duplications, including divergent string escapers and two pagination resolvers. The ADR index marks 0001 superseded and 0017 amended.

Reviewed by Cursor Bugbot for commit 75f9e00. Bugbot is set up for automated code reviews on this repo. Configure here.

…alyze the helper surface

Three ADRs for the self-contained generator rewrite, plus the measured
inventory they rest on.

ADR-0020 makes every generator a self-contained folder ejected as source,
replacing the esbuild bundle that inlines already-public toolkit code.
ADR-0021 records the text-printer architecture the code already uses and
supersedes ADR-0001, which still documents the removed ts.factory codegen.
ADR-0022 drops runtime: package for a sibling runtime module, and amends
ADR-0017 point 3.

helper-surface.md catalogues twelve duplications with file:line evidence.
Three are defects rather than untidiness: two TypeScript string escapers
with different escaping policies, no escaper at all in python and go, and
two pagination resolvers that can disagree about whether an operation
paginates.
@RomanHotsiy
RomanHotsiy requested a review from a team as a code owner August 21, 2026 06:41
@changeset-bot

changeset-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a58a6a4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@RomanHotsiy
RomanHotsiy marked this pull request as draft August 21, 2026 06:43
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 78.05% (🎯 77%) 13595 / 17418
🔵 Statements 77.85% (🎯 77%) 14615 / 18773
🔵 Functions 82.12% (🎯 81%) 2771 / 3374
🔵 Branches 71.17% (🎯 71%) 10044 / 14111
File CoverageNo changed files found.
Generated in workflow #11438 for commit a58a6a4 by the Vitest Coverage Report Action

…actored too

python, go, and php are already self-contained, so ADR-0020 could be read as
leaving them alone — the split was mentioned only as a migration cost. Make it
a decision: self-containment was never the goal on its own, and leaving a
953-line python and a 1169-line go whole would keep the asymmetry the ADR
removes.

Both docs now show the re-grouping is not a rewrite: the existing functions in
all three generators sort into the same stages as they are.
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