Skip to content

Angular: Fix single-quoted literal union types not inferring select control#34902

Closed
asiokun wants to merge 1 commit into
storybookjs:nextfrom
asiokun:fix/angular-compodoc-single-quote-union-types
Closed

Angular: Fix single-quoted literal union types not inferring select control#34902
asiokun wants to merge 1 commit into
storybookjs:nextfrom
asiokun:fix/angular-compodoc-single-quote-union-types

Conversation

@asiokun
Copy link
Copy Markdown

@asiokun asiokun commented May 26, 2026

Problem

Fixes #12641, Fixes #33779

Angular's Compodoc emits TypeScript literal union types using single-quoted strings (e.g. 'S' | 'M' | 'L'). The previous extractEnumValues implementation called JSON.parse() on each segment without pre-processing, but single-quoted strings are not valid JSON — causing the entire parse to throw and fall back to null, resulting in an object control instead of the expected select/radio.

Double-quoted unions like "primary" | "secondary" already worked correctly.

Root Cause

// Before (broken for single-quoted literals)
return compodocType.split('|').map((value) => JSON.parse(value));
//  JSON.parse("'S'") throws → catch returns null → object control

Fix

Parse each pipe-delimited segment individually:

  1. Trim whitespace from each segment
  2. Strip matching surrounding single or double quotes to extract the bare string literal
  3. Fall back to JSON.parse for non-string literals (numbers, booleans, null)
  4. Fall back to the raw segment string if JSON.parse also fails

Also strengthens the enum childs guard with optional chaining (?.value != null instead of .value truthiness, which excluded the string "0").

Regression tests added

Two new test cases in compodoc.test.ts:

  • "'primary' | 'secondary'"{ name: 'enum', value: ['primary', 'secondary'] } (single-quoted, 2 items → radio)
  • "'S' | 'M' | 'L'"{ name: 'enum', value: ['S', 'M', 'L'] } (single-quoted, 3 items → radio)

The existing '"primary" | "secondary"' test (double-quoted) continues to pass.

Manual Testing

With Angular input<'S' | 'M' | 'L'>() or @Input() size: 'S' | 'M' | 'L':

  • Before: Controls panel shows an object input box
  • After: Controls panel shows radio buttons (≤5 options) or a select dropdown (>5 options) with the correct values

Bounty: $110 on Opire.dev

Summary by CodeRabbit

Tests

  • Added test coverage for enum union types expressed with single quotes.

Bug Fixes

  • Improved enum value extraction to correctly handle null and falsy values (0, false).
  • Enhanced string union parsing with better quote unwrapping and fallback handling.

Review Change Stack

…ontrol

Compodoc emits union types with single-quoted string literals (e.g. 'S' | 'M' | 'L')
that previously failed JSON.parse, causing extractEnumValues to return null and
falling back to an object control instead of select/radio.

Fix: parse each segment individually, stripping surrounding single or double quotes
before returning the literal string value. Keeps JSON.parse fallback for non-string
literals (numbers, booleans, null).

Also strengthens the enum childs check with optional chaining and adds regression tests.

Closes storybookjs#12641
Closes storybookjs#33779
@github-actions
Copy link
Copy Markdown
Contributor

Fails
🚫

PR is not labeled with one of: ["cleanup","BREAKING CHANGE","feature request","bug","documentation","maintenance","build","dependencies"]

🚫

PR is not labeled with one of: ["ci:normal","ci:merged","ci:daily","ci:docs"]

🚫 PR description is missing the mandatory "#### Manual testing" section. Please add it so that reviewers know how to manually test your changes.

Generated by 🚫 dangerJS against c996bbb

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

📝 Walkthrough

Walkthrough

This PR enhances the Angular compodoc type extraction to better handle enum union types with single-quoted literals. The extractEnumValues function is updated to parse quoted segments and fall back gracefully when JSON parsing fails, while new test cases verify the behavior with quoted enum union strings.

Changes

Enum Union Type Parsing

Layer / File(s) Summary
Enum union parsing and test coverage
code/frameworks/angular/src/client/compodoc.ts, code/frameworks/angular/src/client/compodoc.test.ts
extractEnumValues now validates enum child values using value != null (including falsy values), splits string unions on `

🎯 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
code/frameworks/angular/src/client/compodoc.ts (1)

133-148: 💤 Low value

Consider handling escaped quotes within string literals.

The current quote-stripping logic handles simple cases well but doesn't account for escaped quotes within the string (e.g., 'it\'s' or "say \"hi\""). If compodoc ever emits such values, slice(1, -1) would leave the backslash-escaped quote intact rather than unescaping it.

This is likely a non-issue in practice since TypeScript union literals rarely contain escaped quotes, but worth noting for completeness.

🤖 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 - 148, The
current quoted-string branch in the compodoc parser (inside the compodocType ->
segments -> parsed mapping) naively does slice(1,-1) which leaves
backslash-escaped quotes intact; update that branch to remove the outer quotes
then unescape escape sequences so `'it\'s'` and `"say \"hi\""` become its and
say "hi". Concretely: detect the quote char, strip it, then create a normalized
double-quoted string (escape any internal double quotes) and run JSON.parse on
it to let the JSON unescaping handle backslashes; keep the existing fallback
JSON.parse/return-segment logic for non-quoted segments.
🤖 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.

Nitpick comments:
In `@code/frameworks/angular/src/client/compodoc.ts`:
- Around line 133-148: The current quoted-string branch in the compodoc parser
(inside the compodocType -> segments -> parsed mapping) naively does slice(1,-1)
which leaves backslash-escaped quotes intact; update that branch to remove the
outer quotes then unescape escape sequences so `'it\'s'` and `"say \"hi\""`
become its and say "hi". Concretely: detect the quote char, strip it, then
create a normalized double-quoted string (escape any internal double quotes) and
run JSON.parse on it to let the JSON unescaping handle backslashes; keep the
existing fallback JSON.parse/return-segment logic for non-quoted segments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f772c40-60d4-4ae0-af5e-dd69ec11f416

📥 Commits

Reviewing files that changed from the base of the PR and between 11e2a8c and c996bbb.

📒 Files selected for processing (2)
  • code/frameworks/angular/src/client/compodoc.test.ts
  • code/frameworks/angular/src/client/compodoc.ts

@valentinpalkovic
Copy link
Copy Markdown
Contributor

Superseded by #34887

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: Storybook 10 no longer infers controls from Typescript literal union types Problem with storybook-controls of type "select"

2 participants