11import { type NodePath , types as t } from 'storybook/internal/babel' ;
22
3- function invariant ( condition : any , message ?: string | ( ( ) => string ) ) : asserts condition {
4- if ( condition ) {
5- return ;
6- }
7- throw new Error ( typeof message === 'function' ? message ( ) : message ) ;
8- }
3+ import { invariant } from './utils' ;
94
105function buildInvalidSpread ( entries : Array < [ string , t . Node ] > ) : t . JSXSpreadAttribute | null {
116 if ( entries . length === 0 ) {
@@ -23,19 +18,34 @@ function buildInvalidSpread(entries: Array<[string, t.Node]>): t.JSXSpreadAttrib
2318export function getCodeSnippet (
2419 storyExportPath : NodePath < t . ExportNamedDeclaration > ,
2520 metaObj : t . ObjectExpression | null | undefined ,
26- componentName : string
21+ componentName ? : string
2722) : t . VariableDeclaration {
28- const declaration = storyExportPath . get ( 'declaration' ) as NodePath < t . Declaration > ;
29- invariant ( declaration . isVariableDeclaration ( ) , 'Expected variable declaration' ) ;
23+ const declaration = storyExportPath . get ( 'declaration' ) ;
24+ invariant (
25+ declaration . isVariableDeclaration ( ) ,
26+ ( ) => storyExportPath . buildCodeFrameError ( 'Expected story to be a variable declaration' ) . message
27+ ) ;
3028
31- const declarator = declaration . get ( 'declarations' ) [ 0 ] as NodePath < t . VariableDeclarator > ;
32- const init = declarator . get ( 'init' ) as NodePath < t . Expression > ;
33- invariant ( init . isExpression ( ) , 'Expected story initializer to be an expression' ) ;
29+ const declarations = declaration . get ( 'declarations' ) ;
30+ invariant (
31+ declarations . length === 1 ,
32+ storyExportPath . buildCodeFrameError ( 'Expected one story declaration' ) . message
33+ ) ;
34+
35+ const declarator = declarations [ 0 ] ;
36+ const init = declarator . get ( 'init' ) ;
37+ invariant (
38+ init . isExpression ( ) ,
39+ ( ) => declarator . buildCodeFrameError ( 'Expected story initializer to be an expression' ) . message
40+ ) ;
3441
3542 const storyId = declarator . get ( 'id' ) ;
36- invariant ( storyId . isIdentifier ( ) , 'Expected named const story export' ) ;
43+ invariant (
44+ storyId . isIdentifier ( ) ,
45+ ( ) => declaration . buildCodeFrameError ( 'Expected story to have a name' ) . message
46+ ) ;
3747
38- let story : NodePath < t . Expression > | null = init ;
48+ let normalizedInit : NodePath < t . Expression > = init ;
3949
4050 if ( init . isCallExpression ( ) ) {
4151 const callee = init . get ( 'callee' ) ;
@@ -50,47 +60,63 @@ export function getCodeSnippet(
5060 if ( obj . isIdentifier ( ) && isBind ) {
5161 const resolved = resolveBindIdentifierInit ( storyExportPath , obj ) ;
5262 if ( resolved ) {
53- story = resolved ;
63+ normalizedInit = resolved ;
5464 }
5565 }
5666 }
5767
5868 // Fallback: treat call expression as story factory and use first argument
59- if ( story === init ) {
69+ if ( init === normalizedInit ) {
6070 const args = init . get ( 'arguments' ) ;
61- if ( args . length === 0 ) {
62- story = null ;
63- } else {
64- const storyArgument = args [ 0 ] ;
65- invariant ( storyArgument . isExpression ( ) ) ;
66- story = storyArgument ;
67- }
71+ invariant (
72+ args . length === 1 ,
73+ ( ) => init . buildCodeFrameError ( 'Could not evaluate story expression' ) . message
74+ ) ;
75+ const storyArgument = args [ 0 ] ;
76+ invariant (
77+ storyArgument . isExpression ( ) ,
78+ ( ) => init . buildCodeFrameError ( 'Could not evaluate story expression' ) . message
79+ ) ;
80+ normalizedInit = storyArgument ;
6881 }
6982 }
7083
84+ normalizedInit = normalizedInit . isTSSatisfiesExpression ( )
85+ ? normalizedInit . get ( 'expression' )
86+ : normalizedInit . isTSAsExpression ( )
87+ ? normalizedInit . get ( 'expression' )
88+ : normalizedInit ;
89+
7190 // If the story is already a function, try to inline args like in render() when using `{...args}`
7291
73- // Otherwise it must be an object story
74- const storyObjPath =
75- story == null || story . isArrowFunctionExpression ( ) || story . isFunctionExpression ( )
76- ? null
77- : story . isTSSatisfiesExpression ( )
78- ? story . get ( 'expression' )
79- : story . isTSAsExpression ( )
80- ? story . get ( 'expression' )
81- : story ;
92+ let story : NodePath < t . ArrowFunctionExpression | t . FunctionExpression | t . ObjectExpression > ;
93+ if ( normalizedInit . isArrowFunctionExpression ( ) || normalizedInit . isFunctionExpression ( ) ) {
94+ story = normalizedInit ;
95+ } else if ( normalizedInit . isObjectExpression ( ) ) {
96+ story = normalizedInit ;
97+ } else {
98+ throw normalizedInit . buildCodeFrameError (
99+ 'Expected story to be csf factory, function or an object expression'
100+ ) ;
101+ }
82102
83- const storyProperties = storyObjPath ?. isObjectExpression ( )
84- ? storyObjPath . get ( 'properties' ) . filter ( ( p ) => p . isObjectProperty ( ) )
85- : [ ] ;
103+ const storyProperties = story ?. isObjectExpression ( )
104+ ? story . get ( 'properties' ) . filter ( ( p ) => p . isObjectProperty ( ) )
105+ : // Find CSF2 properties
106+ [ ] ;
86107
87108 // Prefer an explicit render() when it is a function (arrow/function)
88109 const renderPath = storyProperties
89110 . filter ( ( p ) => keyOf ( p . node ) === 'render' )
90111 . map ( ( p ) => p . get ( 'value' ) )
91- . find ( ( value ) => value . isExpression ( ) ) ;
112+ . find (
113+ ( value ) : value is NodePath < t . ArrowFunctionExpression | t . FunctionExpression > =>
114+ value . isArrowFunctionExpression ( ) || value . isFunctionExpression ( )
115+ ) ;
92116
93- const storyFn = renderPath ?? story ;
117+ const storyFn =
118+ renderPath ??
119+ ( story . isArrowFunctionExpression ( ) ? story : story . isFunctionExpression ( ) ? story : undefined ) ;
94120
95121 // Collect args: meta.args and story.args as Record<string, t.Node>
96122 const metaArgs = metaArgsRecord ( metaObj ?? null ) ;
@@ -112,7 +138,7 @@ export function getCodeSnippet(
112138 . map ( ( [ k , v ] ) => toAttr ( k , v ) )
113139 . filter ( ( a ) : a is t . JSXAttribute => Boolean ( a ) ) ;
114140
115- if ( storyFn ?. isArrowFunctionExpression ( ) || storyFn ?. isFunctionExpression ( ) ) {
141+ if ( storyFn ) {
116142 const fn = storyFn . node ;
117143
118144 // Only handle arrow function with direct JSX expression body for now
@@ -224,6 +250,8 @@ export function getCodeSnippet(
224250 // Build spread for invalid-only props, if any
225251 const invalidSpread = buildInvalidSpread ( invalidEntries ) ;
226252
253+ invariant ( componentName , 'Could not generate snippet without component name.' ) ;
254+
227255 const name = t . jsxIdentifier ( componentName ) ;
228256
229257 const openingElAttrs : Array < t . JSXAttribute | t . JSXSpreadAttribute > = [
0 commit comments