Angular: Fix single-quoted union types in compodoc control inference#34904
Angular: Fix single-quoted union types in compodoc control inference#34904Giazz73-Desavoie wants to merge 1 commit into
Conversation
…ference Compodoc emits TypeScript literal union types with single-quoted strings (e.g. 'S' | 'M' | 'L') which JSON.parse rejects, causing the union to fall back to an object control instead of the expected select/radio control. Parse each pipe-delimited segment individually: strip surrounding single quotes and unescape inner escaped quotes before falling back to JSON.parse for double-quoted strings, numbers, and booleans. Return null for the whole union if any segment cannot be parsed, preserving existing behaviour for opaque types. Also tighten the enum-child guard from truthiness (x.value) to x.value != null so enum values like "" are correctly included. Fixes storybookjs#33779
📝 WalkthroughWalkthroughThe PR enhances ChangesAngular Compodoc Union Enum Extraction
🎯 2 (Simple) | ⏱️ ~10 minutes Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@code/frameworks/angular/src/client/compodoc.ts`:
- Around line 133-147: The single-quoted branch in the compodocType parser
(inside the values mapping) only unescapes \', but must decode all JavaScript
escape sequences; replace the simple replace with logic that takes the inner
content (s.slice(1, -1)), safely constructs a JSON string literal by escaping
backslashes and double quotes, then call JSON.parse on '"' + innerEscaped + '"'
to decode sequences (so use JSON.parse to produce the final decoded string
instead of only replacing /\\'/g), returning that decoded value in the values
array and keeping the existing fallback to JSON.parse for non-quoted segments;
update the code where compodocType and values are handled to use this decoding
path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7642f1bb-880c-42b8-af33-6f9863c1d98a
📒 Files selected for processing (2)
code/frameworks/angular/src/client/compodoc.test.tscode/frameworks/angular/src/client/compodoc.ts
| const values = compodocType.split('|').map((segment): unknown => { | ||
| const s = segment.trim(); | ||
| // Compodoc emits TypeScript literal unions with single-quoted strings (e.g. 'S' | 'M' | 'L'), | ||
| // which JSON.parse rejects. Strip the surrounding quotes and unescape inner escaped quotes. | ||
| if (s.length >= 2 && s[0] === "'" && s[s.length - 1] === "'") { | ||
| return s.slice(1, -1).replace(/\\'/g, "'"); | ||
| } | ||
| try { | ||
| return JSON.parse(s); | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| }); | ||
|
|
||
| return values.some((v) => v === undefined) ? null : values; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In JavaScript/TypeScript single-quoted string literals, are escape sequences such as \n, \t, \\, \", \', and \uXXXX interpreted the same as in double-quoted strings, and what runtime string values do they produce?
💡 Result:
In JavaScript and TypeScript, single-quoted ('...') and double-quoted ("...") string literals are semantically identical regarding escape sequences [1][2]. All standard escape sequences—including \n, \t, \, ", ', and \uXXXX—are interpreted and processed in exactly the same way regardless of whether the string is wrapped in single or double quotes [3][4][2]. When these escape sequences are used, they produce the following runtime string values: \n: Line Feed (LF) character (U+000A) [3][4] \t: Character Tabulation (horizontal tab) (U+0009) [3][4] \: Backslash (Reverse Solidus) (U+005C) [3][4] ": Quotation Mark (U+0022) [3][4] ': Apostrophe (U+0027) [3][4] \uXXXX: A character represented by the hexadecimal Unicode code point XXXX (e.g., \u0041 produces 'A') [3][5] Because both quote types are treated identically, the only functional difference is which character must be escaped to avoid prematurely terminating the string: you must escape the quote character used to define the literal [4][2]. For example, in a single-quoted string, a literal single quote must be escaped as ', while a double quote can appear literally [2]. Conversely, in a double-quoted string, a literal double quote must be escaped as ", while a single quote can appear literally [2]. All other escape sequences remain fully interchangeable [2].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
- 2: https://dev.to/darkmavis1980/single-quotes-vs-double-quotes-in-yaml-how-they-differ-from-javascript-and-typescript-20hj
- 3: https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html
- 4: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar
- 5: https://mathiasbynens.be/notes/javascript-escapes
Decode all valid escape sequences in the single-quoted enum segment parser (lines 133-147 in code/frameworks/angular/src/client/compodoc.ts)
The current single-quoted branch only unescapes \' (s.slice(1, -1).replace(/\\'/g, "'")) and leaves other valid TypeScript/JavaScript string escapes (e.g. \n, \t, \\, \", \uXXXX) undecoded, so inferred enum option values can diverge from the actual runtime strings.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@code/frameworks/angular/src/client/compodoc.ts` around lines 133 - 147, The
single-quoted branch in the compodocType parser (inside the values mapping) only
unescapes \', but must decode all JavaScript escape sequences; replace the
simple replace with logic that takes the inner content (s.slice(1, -1)), safely
constructs a JSON string literal by escaping backslashes and double quotes, then
call JSON.parse on '"' + innerEscaped + '"' to decode sequences (so use
JSON.parse to produce the final decoded string instead of only replacing
/\\'/g), returning that decoded value in the values array and keeping the
existing fallback to JSON.parse for non-quoted segments; update the code where
compodocType and values are handled to use this decoding path.
|
Superseded by #34887 |
|
Updated the single-quoted segment parser to use Regarding the "superseded by #34887" note: our approach differs in one important way — we return We also include two test cases in |
What changed
extractEnumValuesincode/frameworks/angular/src/client/compodoc.tsnow handles TypeScript literal union types that Compodoc emits with single-quoted strings (e.g.'S' | 'M' | 'L').Previously,
JSON.parse("'S'")would throw, causing the entire union to be treated as an opaque type and fall back to anobjectcontrol. Users had to manually declareoptionsin their stories to get aselect/radiocontrol — defeating the purpose of automatic control inference.Root cause
Compodoc serialises Angular
input<'S' | 'M' | 'L'>()as the raw TypeScript string'S' | 'M' | 'L'(single-quoted), whileJSON.parseonly accepts double-quoted strings. The catch branch silently returnednull, hiding the inference failure.Fix
Parse each pipe-delimited segment individually:
\'sequences.JSON.parse(handles double-quoted strings, numbers, booleans,null).nullfor the whole union — preserving the existing behaviour for opaque or complex types.Also tightened the enum-child guard from
x.value(truthiness) tox.value != null, so enum values like""are included correctly.Tests
Added two test cases to the existing
extractTypetable incompodoc.test.ts:"'primary' | 'secondary'"→{ name: 'enum', value: ['primary', 'secondary'] }"'S' | 'M' | 'L'"→{ name: 'enum', value: ['S', 'M', 'L'] }The existing
'"primary" | "secondary"'case continues to pass unchanged.Manual testing
--docsor equivalent).sizearg should display as a select control with optionsS,M,Linstead of an empty object control.Fixes #33779
Summary by CodeRabbit
Bug Fixes
Tests