fix(typescript-input): preserve original enum member names#3037
Conversation
quicktype's TypeScript input converts .ts sources to JSON Schema via typescript-json-schema, which drops each enum member's original identifier and keeps only its runtime value. Downstream, enum case names were synthesized from the value alone, so values that don't look like valid identifiers (e.g. "000") produced mangled names like The000 instead of the original member name Foo. This affected every target language that renders named enum cases (TypeScript, Swift, etc.), not just TypeScript output. Walk the TypeScript program's EnumDeclaration nodes after schema generation and attach a qt-accessors map (case value -> original member name) to each string enum's schema definition. quicktype-core already consumes qt-accessors as a per-case naming hint without touching the serialized value, so no core changes were needed. Added unit tests in test/unit/typescript-input.test.ts: one asserting the qt-accessors map is generated correctly, and one driving the full pipeline from TypeScript source to rendered TypeScript output to confirm the original member names (not The000/The001/The002) appear in the generated enum. Fixes #1158 Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
|
This typescript input thing is becoming a huge hack, plus it binds us to a specific TypeScript version. We should seriously investigate whether we can port this over to tree-sitter. |
# Conflicts: # packages/quicktype-typescript-input/src/index.ts # test/unit/typescript-input.test.ts
|
Agreed that the typescript-json-schema dependency and its pinned TypeScript version are a growing liability, and a tree-sitter-based front end is worth a serious look. That's a substantial rearchitecture of the whole For what it's worth, this change tries hard not to deepen the coupling. It doesn't touch the schema-generation internals; it only walks the already-parsed program's I'll open a separate issue for the tree-sitter investigation so it doesn't get lost. |
|
Filed as #3069 for the tree-sitter investigation. |
…r names The 'Kotlin: enum cases retain their TypeScript string values' test on master asserted the pre-fix synthesized case name (Bringtofront, derived from the value "bringtofront"). With this branch's fix, the original TS member name BRING_TO_FRONT is preserved and rendered as BringToFront, so update the assertion to the corrected output. Co-Authored-By: Claude <noreply@anthropic.com>
Bug
When quicktype converts TypeScript string enums, generated code loses the original member names when the enum's value doesn't look like a valid identifier. For example:
produced (with
--no-prefer-unions, the setting under which quicktype's TS output actually emitsenumblocks):instead of the expected
Foo = "000",Bar = "001",Baz = "002". The same underlying issue also mangles enum case names for other target languages (e.g. Swift), as reported in a follow-up comment on the issue.Root cause
quicktype's TypeScript input (
packages/quicktype-typescript-input) converts.tssource files into a JSON Schema via the third-partytypescript-json-schemapackage, and that schema is then fed into quicktype-core's ordinary JSON Schema input pipeline — there's no TypeScript-specific handling downstream at all.typescript-json-schema's enum conversion computes each member's original identifier but never writes it into the schema — only the runtime value survives (e.g.{"enum": ["000", "001", "002"]}). quicktype-core then has no choice but to synthesize a case identifier from the value itself; for"000"that synthesis falls back to prefixing"The"to produce a legal identifier. The original TS member name (Foo) is lost before it ever reaches quicktype-core.Fix
quicktype-core already supports attaching a per-case naming hint to a string-enum schema via a
"qt-accessors"object (case value → preferred identifier) — this existing mechanism (packages/quicktype-core/src/attributes/AccessorNames.ts) lets a case get a specific name while its serialized value stays untouched.schemaForTypeScriptSources(packages/quicktype-typescript-input/src/index.ts) now walks the TypeScript program'sEnumDeclarationnodes after schema generation and, for each string-valued enum, attaches aqt-accessorsmap from each member's value to its original TS identifier on the corresponding schema definition. No changes to quicktype-core were needed. Numeric TypeScript enums are unaffected/untouched, since quicktype doesn't currently render them as target-language enums at all.Test coverage
This bug lives entirely in the TypeScript input conversion, which the JSON/JSON-Schema-driven fixture harness has no way to exercise (fixtures only drive JSON/JSON-Schema inputs through target-language outputs; there's no "TypeScript source as input" fixture pipeline in this repo). Added two unit tests to
test/unit/typescript-input.test.ts(extending the existing precedent file for this input package):schemaForTypeScriptSourcesnow attaches the correctqt-accessorsmap for a string enum.schemaForTypeScriptSources→JSONSchemaInput→quicktype()with the TypeScript target (preferUnions: false) — and asserts the actual rendered output containsFoo = "000",Bar = "001",Baz = "002"and does not containThe000/The001/The002.Both tests fail against the pre-fix code and pass after the fix.
Verification
npm run buildpasses.npx vitest run test/unit/typescript-input.test.ts— 12/12 passed.--src enum.ts --lang typescript --just-types --no-prefer-unions) — output now correctly showsFoo,Bar,Baz.case shortNameinstead of a mangled name derived from the value, confirming the fix is language-agnostic since it's applied at the schema level.Fixes #1158
🤖 Generated with Claude Code