Skip to content

fix(parse): unwrap TSSatisfiesExpression in named-identifier default-export init#633

Open
darioabc wants to merge 1 commit into
tajo:mainfrom
darioabc:fix/parse-tssatisfies-named-default-export
Open

fix(parse): unwrap TSSatisfiesExpression in named-identifier default-export init#633
darioabc wants to merge 1 commit into
tajo:mainfrom
darioabc:fix/parse-tssatisfies-named-default-export

Conversation

@darioabc

@darioabc darioabc commented May 7, 2026

Copy link
Copy Markdown

Background

Modern Storybook codebases use the satisfies operator to attach the Meta<typeof X> type to a story's meta object while still allowing TypeScript to widen meta.component to its actual type:

const meta = {
  title: "Components/Button",
  component: Button,
} satisfies Meta<typeof Button>;
export default meta;

Ladle's getDefaultExport walker handles TSSatisfiesExpression / TSAsExpression when the direct declaration is wrapped, e.g. export default { … } satisfies Meta<…>;. But it does not unwrap the same expression when it appears as the init of a named identifier that is then re-exported by name (the snippet above).

Effect: objNode ends up pointing at the TSSatisfiesExpression node rather than the underlying ObjectExpression. The subsequent objNode.properties.forEach walks an empty array (the satisfies node has no .properties), so the title and meta fields silently never reach the result. Story files using this pattern are dropped from the index — Ladle reports 0 stories indexed when the codebase has hundreds, with no error or warning.

Verified in a 312-story production codebase. Without the patch: 0/312 indexed. With: 312/312.

Change

packages/ladle/lib/cli/vite-plugin/parse/get-default-export.js: after resolving a named identifier to its init expression, also unwrap TSAsExpression / TSSatisfiesExpression. Mirrors the existing handling for the direct-declaration case at the same site.

Three lines of logic, ~12 LOC including the explanatory comment.

Test plan

  • Two new unit tests in packages/ladle/tests/parse/get-default-export.test.ts:
    • Get default export through named identifier with satisfies``
    • Get default export through named identifier with as``
  • Both tests fail without the fix with the exact production-observed error: "Can't parse the default title and meta of file.js. Meta must be serializable and title a string literal.". Both pass with the fix applied.
  • All existing parser tests continue to pass (pnpm --filter @ladle/react test — 9 suites / 58 tests, up from 56).
  • pnpm typecheck and pnpm lint clean.

Reviewer notes

The fix mirrors the existing direct-declaration unwrap (the if ([…].includes(astPath.node.declaration.type)) block four lines above). Pattern symmetry: every place where Ladle resolves the "default-exported object", strip the satisfies / as wrapper.

No public API change; pure parser robustness improvement.

Optional follow-up: extract the unwrap into a helper (e.g. unwrapTsTypeAssertions(node)) to centralise the "resolve to ObjectExpression" rules and prevent the next site from drifting again. Not done in this PR to keep the diff minimal.

Out of scope

  • The CSF v3 runtime support in composeEnhancers (separate PR).

…export init

Ladle's getDefaultExport walker unwraps TSSatisfiesExpression /
TSAsExpression when the *direct declaration* is wrapped, e.g.
`export default { … } satisfies Meta<…>;`. It did not unwrap
the same expression when it appears as the **init** of a named
identifier that is then re-exported by name:

  const meta = { title: 'X', component: Y } satisfies Meta<typeof Y>;
  export default meta;

The named-identifier branch resolves objNode to the init expression
but does not strip the `satisfies` wrapper, so objNode.properties
is undefined and title/meta silently never reach the result. Story
files using this pattern were dropped from the index.

Mirror the existing direct-declaration unwrap. ~6 LOC. Add unit tests
for the `satisfies` and `as` named-identifier patterns.
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