Repository navigation
fix(http-server-js): keep JSON escapes in generated string literals - #12135
Merged
Timothee Guerin (timotheeguerin) merged 1 commit intoOct 8, 2026
Conversation
escapeUnsafeChars is applied to JSON.stringify output, but it also escaped backslashes, so it doubled the escapes JSON.stringify had written. A string literal type containing a quote generated code that does not parse, and the emitter crashed formatting it; one containing a backslash or a newline generated a different string. Leave backslashes alone. The CodeQL js/bad-code-sanitization example this map comes from has the same backslash entry, but its regex never matches it.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The focused fix preserves JSON semantics and includes meaningful regression coverage and a valid changelog entry.
0 open findings
What changed in this PR
Fixes double-escaped JSON string literals generated by the JavaScript HTTP server emitter.
Changes:
- Preserves escapes produced by
JSON.stringify. - Adds unit and emitter-level regression coverage.
- Adds the required bug-fix changelog entry.
| File | Description |
|---|---|
packages/http-server-js/src/common/reference.ts |
Stops re-escaping JSON backslashes. |
packages/http-server-js/test/scalar.test.ts |
Tests escaped literals and generated output. |
.chronus/changes/http-server-js-string-literal-escapes-2026-9-7-23-15-0.md |
Records the user-visible fix. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
Timothee Guerin (timotheeguerin)
approved these changes
Oct 8, 2026
Timothee Guerin (timotheeguerin)
enabled auto-merge
October 8, 2026 12:20
commit: |
Contributor
|
All changed packages have been documented.
Show changes
|
Zach Bimson (bimsonz)
added a commit
to bimsonz/typespec
that referenced
this pull request
Oct 8, 2026
Fixes the enum half of microsoft#2954. Union variants are not included, per the triage comment. `@encodedName` on an enum member was accepted but ignored. It now sets the value the member is serialized as. Without it, a member is serialized as its explicit value, or as its name when it has none. ```tsp enum ConversationStatus { // Keeps 0 for protobuf, serialized as "unknown" in JSON @Encodedname("application/json", "unknown") CONVERSATION_STATUS_UNSPECIFIED: 0, @Encodedname("application/json", "ready") CONVERSATION_STATUS_READY: 1, } ``` A dual protobuf and JSON surface needs this: protobuf requires integer values and JSON wants strings, and overriding the name alone does not help because the value always wins. Only `application/json` is resolved, as OpenAPI cannot express a different enum value per media type. ### Changes - compiler: `resolveEncodedEnumMemberValue(program, member, mimeType)` returns the encoded name if one is declared, else the member value, else the name. A JSON-based mime type such as `application/merge-patch+json` falls back to the `application/json` name. Example, default and discriminator values use it. `getDiscriminatedUnionFromInheritance` gets a `(program, type, discriminator)` overload and `(type, discriminator)` is deprecated, as `getDiscriminatedUnion` was in microsoft#6059. Since an encoded name is now a member's value, the conflict check compares it with the other members' values rather than their names. - openapi3, json-schema: enum schemas, member references and discriminator mappings use the resolved value; an encoded integer member is emitted as a string. openapi3 validates an enum server variable on the resolved values, so an integer enum whose members are all encoded can be used as one. - http-server-js: enum values, member literal types and union variant differentiation. - http-server-csharp: `JsonStringEnumMemberName` carries the encoded name, and a non-integer enum whose members are all encoded is typed as the enum instead of `double`. - emitter-framework: TypeScript and Python enum declarations, union and value expressions and atoms use the resolved value, so they match the discriminator keys from the typekit. A Python enum with an encoded member gives every member its value instead of `auto()`, which fails at import beside a string on Python 3.13 and later; other Python enums are unchanged. Rendering `EnumMember`, `ValueExpression` or `Atom` for an enum value now needs a `TspContext`, so this changeset is `breaking` (a minor bump at 0.x). Fixes on `main` that came up in review are in microsoft#12135 (http-server-js string literals) and in two http-server-csharp PRs to follow (string escaping, and one `JsonPropertyName` per property). Until those land, an encoded name containing a quote or a backslash generates code that does not parse, does not compile or holds a different value, in an http-server-js member literal type and in C#, as a string value containing one does on `main`. ### Compatibility The compiler changeset is `fix`: microsoft#2954 is triaged as a bug, and the decorator was accepted and ignored, so this falls under the bug exception in the breaking change policy. It does change output for any spec that already has `@encodedName` on an enum member, and such a spec can now report `invalid-discriminated-union-variant`, `invalid-discriminator-value` or `encoded-name-conflict`. Encoding only some members of an integer enum gives a mixed-type enum, which OpenAPI 3.0 rejects with `enum-unique-type`. TCGC computes enum values as `value ?? name`, so clients generated through it keep the member value until it is updated. No project in azure-rest-api-specs uses `@encodedName` on an enum member. ### Testing - compiler, openapi3, json-schema, emitter-framework, http-server-js and http-server-csharp tests pass, and the website build's regenerated docs are committed. - The C# output for integer, float and string enums with encoded members compiles under .NET 10 and round-trips through the generated `JsonSerializationProvider`, and the Python output imports under Python 3.13 and 3.14.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out of #11980, where it came up in review.
escapeUnsafeCharsis applied toJSON.stringifyoutput, but its regex also matched\, so it doubled the escapesJSON.stringifyhad already written:a"b"a\\"b"c\d"c\\\\d"c\\dx, newline,y"x\\ny"x\ny, a backslash and annThe first crashes the emitter:
model Cat { kind: "a\"b" }fails withEmitter "@typespec/http-server-js" crashed!because prettier cannot parsekind: "a\\"b";.This removes
\from the regex and the map. The map comes from the example in CodeQL'sjs/bad-code-sanitizationhelp, which has the same\entry but a regex that never matches it. The control-character entries never matchJSON.stringifyoutput and are left as they are.Testing
scalar.test.ts:escapeUnsafeChars(JSON.stringify(v))parses back tov, and string literal types containing a quote, a backslash or a newline are emitted as the same strings. Both fail onmain.test:e2egives the same result as onmain.