-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
React: Improve error handling of component manifest generation #32855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React: Improve error handling of component manifest generation #32855
Conversation
📝 WalkthroughWalkthroughAdds NodePath-backed tracking to CSF parsing (meta and story declaration paths), tightens Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Parser as CsfFile
participant Generator as ManifestGenerator
participant Snippet as getCodeSnippet
participant Enricher as EnrichCsf
Generator->>Parser: parse file (BabelFile.path: NodePath)
Parser-->>Generator: return CSF with _metaStatementPath & _storyDeclarationPath
Generator->>Snippet: getCodeSnippet(csf, storyName, component?)
Snippet->>Snippet: lookup declaration via csf._storyDeclarationPath[storyName]
Snippet->>Snippet: normalize initializer (.bind, TS wrappers), merge meta/story args
alt unresolved / invalid
Snippet-->>Generator: throw code-frame error
else
Snippet-->>Generator: return AST node (VariableDeclaration | FunctionDeclaration)
end
Generator->>Enricher: provide AST node for formatting
Enricher->>Enricher: recast.print(node).code -> format(...)
alt format/print fails
Enricher-->>Generator: record per-example error
else
Enricher-->>Generator: attach formatted snippet to docs.source.code
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
✨ Finishing touches
Comment |
|
View your CI Pipeline Execution ↗ for commit 74728f2
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (12)
code/renderers/react/src/enrichCsf.ts (1)
21-27: Consider logging snippet generation failures for observability.The try-catch silently swallows errors during code snippet generation. While avoiding user-facing errors for non-critical features is reasonable, consider logging failures (using Storybook's node-logger) to help maintainers identify and fix issues in snippet generation. This is especially valuable during development and debugging.
+import { logger } from 'storybook/internal/node-logger'; + export const enrichCsf: PresetPropertyFn<'experimental_enrichCsf'> = async (input, options) => { const features = await options.presets.apply('features'); if (!features.experimentalCodeExamples) { return; } return async (csf: CsfFile, csfSource: CsfFile) => { const promises = Object.entries(csf._storyPaths).map(async ([key, storyExport]) => { if (!csfSource._meta?.component) { return; } const { format } = await getPrettier(); let node; try { node = getCodeSnippet(storyExport, csfSource._metaNode, csfSource._meta?.component); } catch (e) { - // don't bother the user if we can't generate a snippet + logger.debug(`Failed to generate snippet for story "${key}":`, e); return; }As per coding guidelines.
code/renderers/react/src/componentManifest/generator.test.ts (1)
218-218: Verify: Should successful cases showerror: undefined?The snapshots now include
error: undefinedfor successful manifest generation. Consider whether theerrorfield should be omitted entirely when there's no error (using optional field semantics) rather than explicitly setting it toundefined. This would make the successful case output cleaner and reduce payload size.Current behavior:
{ error: undefined, name: "Button", ... }Alternative:
{ name: "Button", ... } // error field omitted when undefinedAlso applies to: 322-322
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (5)
30-33: Avoid eager code-frame construction in invariant for performanceWrap the
buildCodeFrameError(...).messagein a lazy function, as elsewhere, to avoid doing work on the happy path.- invariant( - declarations.length === 1, - storyExportPath.buildCodeFrameError('Expected one story declaration').message - ); + invariant( + declarations.length === 1, + () => storyExportPath.buildCodeFrameError('Expected one story declaration').message + );
84-89: Unwrap TS non‑null expressions during normalizationHandle
TSNonNullExpression(expr!) like other TS wrappers so downstream checks see the inner expression.- normalizedInit = normalizedInit.isTSSatisfiesExpression() - ? normalizedInit.get('expression') - : normalizedInit.isTSAsExpression() - ? normalizedInit.get('expression') - : normalizedInit; + normalizedInit = normalizedInit.isTSSatisfiesExpression() + ? normalizedInit.get('expression') + : normalizedInit.isTSAsExpression() + ? normalizedInit.get('expression') + : normalizedInit.isTSNonNullExpression?.() + ? normalizedInit.get('expression') + : normalizedInit;
253-255: Add code‑frame context when component name is missingSurface a code‑framed message to improve debugging; you have
storyExportPathin scope.- invariant(componentName, 'Could not generate snippet without component name.'); + invariant( + componentName, + () => + storyExportPath.buildCodeFrameError( + 'Could not generate snippet without component name. Did you set meta.component?' + ).message + );
326-342: Correct handling of namespaced JSX attributes (e.g., xlink:href)
toAttrbuildst.jsxIdentifier(key)for all keys. For namespaced attrs containing:, this should be aJSXNamespacedName; otherwise the AST can be semantically off. Keep your existing validation; just construct the right node.-const toAttr = (key: string, value: t.Node): t.JSXAttribute | null => { - if (t.isBooleanLiteral(value)) { - // Keep falsy boolean attributes by rendering an explicit expression container - return value.value - ? t.jsxAttribute(t.jsxIdentifier(key), null) - : t.jsxAttribute(t.jsxIdentifier(key), t.jsxExpressionContainer(value)); - } - - if (t.isStringLiteral(value)) { - return t.jsxAttribute(t.jsxIdentifier(key), t.stringLiteral(value.value)); - } - - if (t.isExpression(value)) { - return t.jsxAttribute(t.jsxIdentifier(key), t.jsxExpressionContainer(value)); - } - return null; // non-expression nodes are not valid as attribute values -}; +const toAttr = (key: string, value: t.Node): t.JSXAttribute | null => { + const makeName = (): t.JSXIdentifier | t.JSXNamespacedName | null => { + if (key.includes(':')) { + const [ns, local] = key.split(':'); + if (!ns || !local) return null; + return t.jsxNamespacedName(t.jsxIdentifier(ns), t.jsxIdentifier(local)); + } + return t.jsxIdentifier(key); + }; + const name = makeName(); + if (!name) return null; + + if (t.isBooleanLiteral(value)) { + // Keep falsy boolean attributes by rendering an explicit expression container + return value.value + ? t.jsxAttribute(name, null) + : t.jsxAttribute(name, t.jsxExpressionContainer(value)); + } + if (t.isStringLiteral(value)) { + return t.jsxAttribute(name, t.stringLiteral(value.value)); + } + if (t.isExpression(value)) { + return t.jsxAttribute(name, t.jsxExpressionContainer(value)); + } + return null; // non-expression nodes are not valid as attribute values +};
496-522: Minor duplication of injection logic
makeInjectedPiecesduplicates attr filtering logic already computed earlier. Consider extracting a small helper to compute injected attrs and invalid spread once to keep behavior consistent.code/renderers/react/src/componentManifest/generator.ts (5)
33-34: Usemapinstead offlatMapwith async callback
flatMapprovides no benefit here (callbacks return non-array Promises) and is misleading.- const components = await Promise.all( - singleEntryPerComponent.flatMap(async (entry): Promise<ReactComponentManifest> => { + const components = await Promise.all( + singleEntryPerComponent.map(async (entry): Promise<ReactComponentManifest> => {
60-66: Rename loop variable to avoid shadowingpathimportThe
[storyName, path]binding shadows the top‑levelpathimport and hurts readability.- .map(([storyName, path]) => { + .map(([storyName, storyExportPath]) => { try { return { name: storyName, - snippet: recast.print(getCodeSnippet(path, csf._metaNode ?? null, name)).code, + snippet: recast.print(getCodeSnippet(storyExportPath, csf._metaNode ?? null, name)).code, };
78-86: Likely unreachable!entry.componentPathbranchYou filter
entry.componentPathtruthy earlier (Line 26), so this guard should never trigger. If intentional for future resilience, keep it; otherwise remove to reduce noise.
92-101: Preserve original I/O error in messageIncluding the cause eases debugging.
- error: { - message: `Could not read the component file located at ${entry.componentPath}`, - }, + error: { + message: `Could not read the component file located at ${entry.componentPath}: ${e.message}`, + },
110-111: Enrich docgen error with file contextAdd the component path (and/or name) to make the error actionable.
- const error = !docgen ? { message: 'Docgen could not find a component' } : undefined; + const error = !docgen + ? { message: `Docgen could not find a component in ${entry.componentPath} for "${name}"` } + : undefined;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
code/core/src/csf-tools/CsfFile.ts(4 hunks)code/core/src/types/modules/core-common.ts(1 hunks)code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx(1 hunks)code/renderers/react/src/componentManifest/generateCodeSnippet.ts(5 hunks)code/renderers/react/src/componentManifest/generator.test.ts(6 hunks)code/renderers/react/src/componentManifest/generator.ts(3 hunks)code/renderers/react/src/componentManifest/utils.ts(1 hunks)code/renderers/react/src/enrichCsf.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/enrichCsf.tscode/renderers/react/src/componentManifest/generateCodeSnippet.test.tsxcode/renderers/react/src/componentManifest/utils.tscode/renderers/react/src/componentManifest/generator.test.tscode/core/src/types/modules/core-common.tscode/renderers/react/src/componentManifest/generator.tscode/renderers/react/src/componentManifest/generateCodeSnippet.tscode/core/src/csf-tools/CsfFile.ts
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/enrichCsf.tscode/renderers/react/src/componentManifest/generateCodeSnippet.test.tsxcode/renderers/react/src/componentManifest/utils.tscode/renderers/react/src/componentManifest/generator.test.tscode/core/src/types/modules/core-common.tscode/renderers/react/src/componentManifest/generator.tscode/renderers/react/src/componentManifest/generateCodeSnippet.tscode/core/src/csf-tools/CsfFile.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/enrichCsf.tscode/renderers/react/src/componentManifest/generateCodeSnippet.test.tsxcode/renderers/react/src/componentManifest/utils.tscode/renderers/react/src/componentManifest/generator.test.tscode/core/src/types/modules/core-common.tscode/renderers/react/src/componentManifest/generator.tscode/renderers/react/src/componentManifest/generateCodeSnippet.tscode/core/src/csf-tools/CsfFile.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/enrichCsf.tscode/renderers/react/src/componentManifest/generateCodeSnippet.test.tsxcode/renderers/react/src/componentManifest/utils.tscode/renderers/react/src/componentManifest/generator.test.tscode/core/src/types/modules/core-common.tscode/renderers/react/src/componentManifest/generator.tscode/renderers/react/src/componentManifest/generateCodeSnippet.tscode/core/src/csf-tools/CsfFile.ts
code/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsxcode/renderers/react/src/componentManifest/generator.test.ts
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)
**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsxcode/renderers/react/src/componentManifest/generator.test.ts
**/*.@(test|spec).{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.@(test|spec).{ts,tsx,js,jsx}: Unit tests should import and execute the functions under test rather than only asserting on syntax patterns
Mock external dependencies in tests usingvi.mock()(e.g., filesystem, loggers)
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsxcode/renderers/react/src/componentManifest/generator.test.ts
🧠 Learnings (6)
📚 Learning: 2025-09-17T08:11:47.197Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.197Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Document complex mock behaviors
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to access and implement mock behaviors
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-10-13T13:33:14.659Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-13T13:33:14.659Z
Learning: Applies to **/*.@(test|spec).{ts,tsx,js,jsx} : Mock external dependencies in tests using `vi.mock()` (e.g., filesystem, loggers)
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to type and access mocked functions
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.197Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.197Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use type-safe mocking with vi.mocked()
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
🧬 Code graph analysis (4)
code/renderers/react/src/enrichCsf.ts (1)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
getCodeSnippet(18-275)
code/renderers/react/src/componentManifest/generator.test.ts (1)
code/renderers/react/src/componentManifest/generator.ts (1)
componentManifestGenerator(20-141)
code/renderers/react/src/componentManifest/generator.ts (6)
code/core/src/csf-tools/CsfFile.ts (1)
loadCsf(1058-1062)code/core/src/types/modules/core-common.ts (1)
ComponentManifest(348-358)code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
getCodeSnippet(18-275)code/renderers/react/src/componentManifest/utils.ts (1)
invariant(14-19)code/core/src/csf-tools/enrichCsf.ts (1)
extractDescription(158-178)code/renderers/react/src/componentManifest/jsdocTags.ts (1)
extractJSDocInfo(5-21)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
code/renderers/react/src/componentManifest/utils.ts (1)
invariant(14-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: normal
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (10)
code/core/src/csf-tools/CsfFile.ts (3)
36-36: LGTM: Type safety improvement.Changing from
anytoNodePath<t.Program>provides better type safety and aligns with Babel's actual API.
304-304: LGTM: NodePath tracking for enhanced error reporting.The addition of
_metaStatementPathcomplements the existing_metaStatementfield and provides access to Babel's NodePath API for generating better error messages with code frames. The implementation correctly maintains consistency by setting both fields together in all code paths.Also applies to: 490-497, 504-504
1052-1055: LGTM: Appropriate configuration for error handling context.Adding
highlightCode: falseto the BabelFileClass options is reasonable for server-side tooling where automatic syntax highlighting may not be needed or where custom error formatting is preferred.code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx (1)
80-94: LGTM! Improved error handling test.The test now properly validates that invalid story exports throw descriptive errors with code frames, making debugging easier for developers.
code/renderers/react/src/enrichCsf.ts (1)
32-34: LGTM! Cleaner code with extracted node variable.The refactoring improves readability by separating snippet generation from formatting.
code/renderers/react/src/componentManifest/utils.ts (1)
14-19: LGTM! Well-designed invariant helper.The invariant function properly supports lazy message evaluation for performance and uses TypeScript's assertion signature for type narrowing. This is a clean, reusable utility for runtime checks.
code/renderers/react/src/componentManifest/generator.test.ts (3)
12-12: Simplified mock implementation.The change from a spy-based mock to a direct implementation is cleaner and more explicit about the mock behavior (returning null result type).
418-482: LGTM! Well-structured test helpers.The
getManifestForStoryandwithCSF3helper functions significantly improve test readability and maintainability by reducing duplication.
484-615: LGTM! Comprehensive error scenario coverage.The three new test cases effectively cover important edge cases:
- Missing component field in meta
- Component exported from another file (re-export)
- Unknown/invalid story expressions
Each test validates that appropriate error messages with code frames are generated, which will greatly improve the developer experience when debugging manifest generation issues.
code/core/src/types/modules/core-common.ts (1)
348-358: Design is intentional and correctly typed; no changes required.The optional
namefield is intentionally paired with theerrorfield as an error-reporting mechanism. The code ingenerator.tsshows thatnameis only absent whencomponentNamecannot be extracted (meta.component not specified), and in that case, theerrorfield explains why. All other error scenarios includename. This is a valid pattern where the manifest can report processing failures while still providing apathreference for debugging. The type definition correctly reflects this design.
JReinhold
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM apart from a few comments.
I'll let @shilman judge the AST stuff, I don't know enough about it to approve it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (2)
24-41: Solid error handling with room for minor optimization.The normalization logic correctly handles both
FunctionDeclarationandVariableDeclaratorpaths, and the use of code-frame errors significantly improves debugging.Consider simplifying the invariant pattern at lines 30–35 to avoid constructing an Error object just to extract its message:
- invariant( - init.isExpression(), - () => - storyDeclaration.buildCodeFrameError('Expected story initializer to be an expression') - .message - ); + if (!init.isExpression()) { + throw storyDeclaration.buildCodeFrameError('Expected story initializer to be an expression'); + }This pattern can be applied to similar invariant calls throughout the file (e.g., lines 66–74).
64-76: Consider more specific error messages for call expression validation.The fallback logic for call expressions is correct, but both invariants (lines 66–69 and 71–74) use the same generic message "Could not evaluate story expression."
More specific messages would help users identify whether the issue is argument count or argument type:
invariant( args.length === 1, - () => storyPath.buildCodeFrameError('Could not evaluate story expression').message + () => storyPath.buildCodeFrameError('Story factory must have exactly one argument').message ); const storyArgument = args[0]; invariant( storyArgument.isExpression(), - () => storyPath.buildCodeFrameError('Could not evaluate story expression').message + () => storyPath.buildCodeFrameError('Story factory argument must be an expression').message );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.gitignore(1 hunks)code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx(4 hunks)code/renderers/react/src/componentManifest/generateCodeSnippet.ts(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
🧬 Code graph analysis (1)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
code/renderers/react/src/componentManifest/utils.ts (1)
invariant(14-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: normal
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (11)
.gitignore (1)
68-69: Note: File scope mismatch with PR objectives.The provided file (
.gitignore) does not align with the PR objectives, which focus on improving React error handling for component manifest generation, AST tracking, and schema extensions. Only a subset of files appears to be under review.The
.gitignoreentry addition itself is straightforward and low-risk. However, please clarify:
- What is
.junie? (artifact, build output, tool-specific file?)- Are other files changed in this PR pending review?
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (10)
18-23: LGTM! Signature refactoring aligns with CSF1/CSF2 flexibility.The broadened signature correctly accepts both variable declarators and function declarations, and the optional
componentNameallows for stories that may not need explicit component wrapping. The mixed return type properly reflects the different output formats.
79-83: LGTM! TypeScript type assertion unwrapping is handled correctly.The nested ternary pattern cleanly unwraps both
satisfiesandasexpressions.
86-100: LGTM! Function detection logic is comprehensive.The code correctly identifies arrow functions, function expressions, and function declarations, and appropriately throws a code-frame error for unsupported story shapes.
102-118: LGTM! CSF2 render function extraction is well-structured.The code correctly prioritizes an explicit
render()function when present in CSF2 object stories.
120-138: LGTM! Args merging follows CSF semantics.The code correctly merges
meta.argsandstory.argswith proper override behavior, and appropriately separates valid JSX attribute names from invalid ones.
140-156: LGTM! Arrow function JSX transformation is correct.The code properly handles arrow functions that directly return JSX elements or fragments, applying spread transformation and arg inlining as needed.
158-211: LGTM! Block body transformation comprehensively handles function variants.The code correctly extracts block bodies from function declarations, arrow functions, and function expressions, transforming only return statements that return JSX. The preservation of function form (declaration vs. expression) in the output is appropriate.
214-236: LGTM! Fallback JSX generation is sound.The invariant at line 217 ensures
componentNameis present before constructing the JSX element, preventing potential null/undefined errors. The fallback correctly builds a simple arrow function returning the component with merged args.
542-559: LGTM! Children injection logic is correct.The code correctly recurses into children and only injects
merged.childrenwhen appropriate (args spread present, no existing children, and children available in merged args). This prevents unintentionally overwriting existing children.
586-590: LGTM! Signature broadening improves reusability.Changing the parameter from a specific export declaration type to a generic
NodePath<t.Node>makes the function more flexible while maintaining correctness.
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
code/renderers/react/src/componentManifest/generator.ts (2)
58-77: Strong per-story error handling; remove unnecessary filter.The per-story error handling with try/catch is excellent and aligns with the PR objective. Each story failure is captured individually without breaking the entire manifest generation.
However, the
.filter(Boolean)on line 77 is unnecessary since themapcallback always returns an object (either withsnippetorerror), never a falsy value.Apply this diff to remove the unnecessary filter:
}) - .filter(Boolean); +;
89-103: Robust file reading error handling.The try/catch around file reading is excellent. The non-null assertion on line 92 is safe due to the early return at line 79, but consider extracting
entry.componentPathto a variable after the check for improved clarity.Optional: Extract
componentPathafter the check for improved type safety and clarity:if (!entry.componentPath) { const message = `No component file found for the "${name}" component.`; return { ...base, name, examples, error: { message }, }; } + const componentPath = entry.componentPath; let componentFile; try { - componentFile = await readFile(path.join(process.cwd(), entry.componentPath!), 'utf-8'); + componentFile = await readFile(path.join(process.cwd(), componentPath), 'utf-8'); } catch (e) { invariant(e instanceof Error); return { ...base, name, examples, error: { - message: `Could not read the component file located at ${entry.componentPath}`, + message: `Could not read the component file located at ${componentPath}`, }, }; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
code/renderers/react/src/componentManifest/generator.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/componentManifest/generator.ts
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/componentManifest/generator.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/componentManifest/generator.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/componentManifest/generator.ts
🧬 Code graph analysis (1)
code/renderers/react/src/componentManifest/generator.ts (5)
code/core/src/csf-tools/CsfFile.ts (1)
loadCsf(1060-1064)code/core/src/types/modules/core-common.ts (1)
ComponentManifest(348-358)code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
getCodeSnippet(18-237)code/renderers/react/src/componentManifest/utils.ts (1)
invariant(14-19)code/core/src/csf-tools/enrichCsf.ts (1)
extractDescription(158-178)
🔇 Additional comments (4)
code/renderers/react/src/componentManifest/generator.ts (4)
47-56: Excellent error handling with code context.This change perfectly aligns with the PR objective to surface errors with line numbers and surrounding context instead of silently skipping components. Using
buildCodeFrameErrorprovides valuable debugging information.
79-87: Good error surfacing for missing component paths.This properly handles the case where
componentPathis missing, surfacing the error in the manifest instead of silently skipping. This works in tandem with the relaxed filter condition on line 26.
111-111: Good docgen failure surfacing.Computing and attaching the error when docgen fails to find a component ensures failures are visible in the manifest instead of being silently ignored.
119-129: Consistent manifest structure with proper error propagation.The final manifest assembly properly spreads the base object and includes the computed error field. The populated
examplesarray correctly overwrites the empty array frombase, ensuring all story snippets and their errors are included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
code/renderers/react/src/componentManifest/generator.ts (1)
42-60: Remove unnecessary.filter(Boolean).The
.map()callback always returns an object (either withsnippetorerror), so the.filter(Boolean)on line 60 is redundant. All mapped values are truthy.Apply this diff:
}) - .filter(Boolean); +;Otherwise, the per-story error handling looks excellent and aligns well with the PR objectives—errors are captured with their messages rather than being hidden.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
code/renderers/react/src/componentManifest/generator.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/componentManifest/generator.ts
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/componentManifest/generator.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/componentManifest/generator.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/componentManifest/generator.ts
🧬 Code graph analysis (1)
code/renderers/react/src/componentManifest/generator.ts (6)
code/core/src/csf-tools/CsfFile.ts (1)
loadCsf(1060-1064)code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
getCodeSnippet(18-237)code/renderers/react/src/componentManifest/utils.ts (1)
invariant(14-19)code/core/src/types/modules/core-common.ts (1)
ComponentManifest(348-358)code/core/src/csf-tools/enrichCsf.ts (1)
extractDescription(158-178)code/renderers/react/src/componentManifest/jsdocTags.ts (1)
extractJSDocInfo(5-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (6)
code/renderers/react/src/componentManifest/generator.ts (6)
37-40: LGTM!The extraction of
id,importPath, andnameis clear and sets up the base data for manifest generation.
62-67: LGTM!The base object pattern avoids duplication across the multiple return paths and ensures consistent manifest structure. The
satisfiesclause provides good type safety.
69-78: LGTM!The missing componentName handling is excellent:
- Clear, actionable error message
- Uses
buildCodeFrameErrorto provide line numbers and context- Falls back gracefully when
_metaStatementPathis unavailable- Aligns perfectly with the PR objective to surface errors with context
80-104: LGTM!The error handling for missing or unreadable component files is solid:
- Returns partial manifests with
nameandexampleswhen available- Provides clear error messages about what went wrong
- The non-null assertion on line 93 is safe because we're inside the else branch
- Preserving partial data helps users understand what succeeded before the failure
This aligns well with the PR objective to report errors clearly while providing maximum context.
106-130: LGTM!The docgen handling and final manifest assembly are well-structured:
- Sets an error when docgen fails but continues processing to provide as much data as possible
- Includes all available metadata (description, summary, tags, examples)
- The error field is conditionally included only when needed
- Spreading the base object maintains consistency across all return paths
This approach maximizes the information available to users even when parts of the manifest generation fail, which aligns perfectly with the PR objectives.
26-26: The subtype filter is necessary and correct—it is not redundant.The type definition shows that
StoryIndexEntryentries can havesubtype: 'story' | 'test'. Test entries sharetype: 'story'with regular stories but havesubtype: 'test'. The second filter correctly excludes these test entries from the component manifest, which should only include regular stories, not tests. The filter is defensive and intentional, not unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
528-547: Good addition ofpathForNodepublic export.Exporting this function aligns with coding guidelines for unit testing. The implementation correctly traverses the program and stops early upon finding the target node.
One potential optimization: if
pathForNodeis called multiple times in a single transformation (e.g., for multiple stories or meta objects), consider caching the traversal results or using a NodePath lookup map to avoid repeated full-program traversals.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts(6 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.ts
🧬 Code graph analysis (1)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (2)
code/core/src/csf-tools/CsfFile.ts (2)
CsfFile(281-1042)meta(964-966)code/renderers/react/src/componentManifest/utils.ts (1)
invariant(14-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: normal
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (15)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (15)
1-4: LGTM!The imports are appropriate for the CSF-driven refactor.
19-36: LGTM!The normalization logic correctly handles both function declarations and variable declarators, with appropriate error reporting using code frames.
38-72: LGTM!The Template.bind and factory pattern handling is robust. The invariant at lines 61-64 correctly enforces that factory calls must have exactly one argument, which is a reasonable constraint for CSF story factories.
74-96: LGTM!The TypeScript wrapper stripping and function resolution logic correctly handles the various CSF story formats.
130-149: LGTM!The transformation logic for arrow functions with direct JSX returns correctly applies spread expansion and args inlining.
151-187: LGTM!The block statement transformation correctly handles return statements and reconstructs the appropriate function type.
189-192: LGTM!The fallback return correctly wraps function expressions in a variable declaration while preserving function declarations.
194-211: LGTM!The fallback component synthesis correctly handles cases where no story function exists, appropriately handling invalid attribute names via spread syntax.
224-289: LGTM!The helper functions are well-structured and handle the various node types appropriately.
291-317: LGTM!The
getArgsMemberKeyfunction comprehensively handles member expressions, including optional chaining.
319-397: LGTM!The
inlineArgsInJsxfunction correctly handles recursive transformation of JSX trees, appropriately inliningargs.*references and handling self-closing elements.
399-489: LGTM!The
transformArgsSpreadsInJsxfunction correctly expands{...args}spreads while avoiding duplicate attributes and preserving insertion order.
491-526: LGTM!The
resolveBindIdentifierInitfunction correctly resolves template identifiers, handling both top-level and exported variable declarations.
107-118: ****The concern about
FunctionDeclarationis based on an AST misunderstanding. The code correctly handles all valid render function patterns:
- In object property positions, functions can only be
FunctionExpression(e.g.,render: function Name() {}) orArrowFunctionExpressionFunctionDeclarationis a top-level statement syntax and cannot appear as an object property value- The codebase has 5 examples of
render: function Name()patterns, all of which areFunctionExpressionnodes and are correctly matched byv.isFunctionExpression()The current implementation is correct.
Likely an incorrect or invalid review comment.
213-222: Correct the type annotation formergedto eliminate unnecessary defensive logic.The type
Record<string, t.Node>on line 128 is overly broad. Values inmergedcome from object properties and are alwayst.Expressioninstances. Using a correct type eliminates the need for the defensivet.isExpression(v)check on line 219 and prevents confusion.Change line 128 to:
const merged: Record<string, t.Expression | undefined> = { ...metaArgs, ...storyArgs };Then simplify line 219 to use the value directly without the fallback, or remove the unnecessary check entirely since all values will be expressions.
Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
code/renderers/react/src/componentManifest/generator.test.ts (1)
536-636: Add tests for missing componentPath and meta.render extraction.
- Missing componentPath should surface a clear top-level error.
- Validate snippet extraction when only meta.render is provided. This aligns with the PR’s error-reporting goals and a past comment.
I can draft these tests if helpful.
code/renderers/react/src/componentManifest/generator.ts (1)
78-92: Same here: don’t crash on non-Error when reading component file.- } catch (e) { - invariant(e instanceof Error); + } catch (e) { return { ...base, name, examples, error: { - message: `Could not read the component file located at ${entry.componentPath}`, + message: e instanceof Error + ? `Could not read the component file located at ${entry.componentPath}` + : String(e), }, }; }
🧹 Nitpick comments (10)
code/core/src/csf-tools/enrichCsf.test.ts (1)
17-21: DRY the fallback and add a direct “no-title” test.Factor the fallback into a shared const and add one test that omits userTitle to assert 'Unknown' is used. Keeps intent explicit.
code/renderers/react/src/componentManifest/utils.ts (1)
14-23: LGTM; small ergonomic tweak.Consider naming the error for easier filtering in logs.
Apply:
- throw new Error((typeof message === 'function' ? message() : message) ?? 'Invariant failed'); + const err = new Error( + (typeof message === 'function' ? message() : message) ?? 'Invariant failed' + ); + err.name = 'InvariantError'; + throw err;code/renderers/react/src/componentManifest/generator.test.ts (3)
10-13: Use spy: true for module mocks per test guidelines.Add spy mode to all vi.mock calls so you can introspect/override as needed.
As per coding guidelines.
-vi.mock('node:fs/promises', async () => (await import('memfs')).fs.promises); -vi.mock('node:fs', async () => (await import('memfs')).fs); -vi.mock('tsconfig-paths', () => ({ loadConfig: () => ({ resultType: null!, message: null! }) })); +vi.mock('node:fs/promises', async () => (await import('memfs')).fs.promises, { spy: true }); +vi.mock('node:fs', async () => (await import('memfs')).fs, { spy: true }); +vi.mock( + 'tsconfig-paths', + () => ({ loadConfig: vi.fn(() => ({ resultType: null as any, message: null as any })) }), + { spy: true } +);
96-205: Add cleanup for mocks/filesystem between tests.beforeEach returning a function isn’t executed by Vitest. Add afterEach to reset memfs and restore the cwd spy.
Apply:
beforeEach(() => { vi.spyOn(process, 'cwd').mockReturnValue('/app'); vol.fromJSON( { // ... }, '/app' ); - return () => vol.reset(); }); + +afterEach(() => { + vol.reset(); + (process.cwd as unknown as { mockRestore: () => void }).mockRestore?.(); +});As per coding guidelines.
418-483: withCSF3 helper: import fn for syntactic completeness.Not required for parsing, but importing fn avoids accidental lint or transform issues in the future.
return dedent` import type { Meta } from '@storybook/react'; + import { fn } from 'storybook/test'; import { Button } from './Button';code/renderers/react/src/enrichCsf.ts (2)
20-21: Avoid per-story Prettier import.Call getPrettier once outside the map to cut overhead.
- const promises = Object.keys(csf._stories).map(async (key) => { - if (!csfSource._meta?.component) { - return; - } - const { format } = await getPrettier(); + const { format } = await getPrettier(); + const promises = Object.keys(csf._stories).map(async (key) => { + if (!csfSource._meta?.component) { + return; + }Also applies to: 32-39
17-19: Surface missing component as an inline error instead of skipping.Early return hides the problem. Prefer injecting a docs.source message like “Could not generate snippet without component name.”
Do consumers expect docs.source.code to show error text, or should we add docs.source.error?
code/renderers/react/src/componentManifest/generator.ts (2)
30-33: Deterministic component representative per group.Picking group[0] from Object.values order can be unstable. Sort entries in each group (e.g., by exportName or id) and take the first.
-const singleEntryPerComponent = Object.values(groupByComponentId).flatMap((group) => - group && group?.length > 0 ? [group[0]] : [] -); +const singleEntryPerComponent = Object.values(groupByComponentId).flatMap((group) => { + if (!group?.length) return []; + const [first] = [...group].sort((a, b) => a.id.localeCompare(b.id)); + return [first]; +});
58-59: Minor: filter(Boolean) on examples is redundant.The map always returns an object; drop the filter.
- const examples = Object.keys(csf._stories) - .map((storyName) => { + const examples = Object.keys(csf._stories).map((storyName) => { // ... - }) - .filter(Boolean); + });code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
528-547: Document the new public exportpathForNode.This function is now part of the public API and serves as a bridge between AST nodes and Babel NodePaths. Consider adding JSDoc comments to document:
- The purpose (converting an AST node to its corresponding traversal path)
- When to use it (CSF-driven processing when you have nodes but need paths)
- Performance characteristics (traverses the entire program)
Example documentation:
+/** + * Find the NodePath for a given AST node within a program. + * @param program - The program path to search within + * @param target - The AST node to find + * @returns The NodePath for the target node, or undefined if not found + */ export function pathForNode<T extends t.Node>( program: NodePath<t.Program>, target: T | undefined ): NodePath<T> | undefined {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
code/core/src/csf-tools/enrichCsf.test.ts(1 hunks)code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx(6 hunks)code/renderers/react/src/componentManifest/generateCodeSnippet.ts(6 hunks)code/renderers/react/src/componentManifest/generator.test.ts(6 hunks)code/renderers/react/src/componentManifest/generator.ts(3 hunks)code/renderers/react/src/componentManifest/utils.ts(1 hunks)code/renderers/react/src/enrichCsf.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
🧰 Additional context used
📓 Path-based instructions (7)
code/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx
Files:
code/renderers/react/src/componentManifest/generator.test.tscode/core/src/csf-tools/enrichCsf.test.ts
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)
**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together
Files:
code/renderers/react/src/componentManifest/generator.test.tscode/core/src/csf-tools/enrichCsf.test.ts
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/componentManifest/generator.test.tscode/renderers/react/src/componentManifest/generateCodeSnippet.tscode/core/src/csf-tools/enrichCsf.test.tscode/renderers/react/src/enrichCsf.tscode/renderers/react/src/componentManifest/utils.tscode/renderers/react/src/componentManifest/generator.ts
**/*.@(test|spec).{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.@(test|spec).{ts,tsx,js,jsx}: Unit tests should import and execute the functions under test rather than only asserting on syntax patterns
Mock external dependencies in tests usingvi.mock()(e.g., filesystem, loggers)
Files:
code/renderers/react/src/componentManifest/generator.test.tscode/core/src/csf-tools/enrichCsf.test.ts
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/componentManifest/generator.test.tscode/renderers/react/src/componentManifest/generateCodeSnippet.tscode/core/src/csf-tools/enrichCsf.test.tscode/renderers/react/src/enrichCsf.tscode/renderers/react/src/componentManifest/utils.tscode/renderers/react/src/componentManifest/generator.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/componentManifest/generator.test.tscode/renderers/react/src/componentManifest/generateCodeSnippet.tscode/core/src/csf-tools/enrichCsf.test.tscode/renderers/react/src/enrichCsf.tscode/renderers/react/src/componentManifest/utils.tscode/renderers/react/src/componentManifest/generator.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/componentManifest/generator.test.tscode/renderers/react/src/componentManifest/generateCodeSnippet.tscode/core/src/csf-tools/enrichCsf.test.tscode/renderers/react/src/enrichCsf.tscode/renderers/react/src/componentManifest/utils.tscode/renderers/react/src/componentManifest/generator.ts
🧠 Learnings (7)
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to access and implement mock behaviors
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-10-13T13:33:14.659Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-13T13:33:14.659Z
Learning: Applies to **/*.@(test|spec).{ts,tsx,js,jsx} : Mock external dependencies in tests using `vi.mock()` (e.g., filesystem, loggers)
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to type and access mocked functions
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.197Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.197Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use type-safe mocking with vi.mocked()
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-10-13T13:33:14.659Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-13T13:33:14.659Z
Learning: Applies to scripts/**/*.{ts,js,mjs} : In Node.js scripts, use `storybook/internal/node-logger` instead of `console.*`
Applied to files:
code/renderers/react/src/enrichCsf.ts
📚 Learning: 2025-10-13T13:33:14.659Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-13T13:33:14.659Z
Learning: Applies to code/**/*.{ts,tsx,js,jsx,mjs} : In application code, use Storybook loggers instead of `console.*` (client code: `storybook/internal/client-logger`; server code: `storybook/internal/node-logger`)
Applied to files:
code/renderers/react/src/enrichCsf.ts
🧬 Code graph analysis (5)
code/renderers/react/src/componentManifest/generator.test.ts (1)
code/renderers/react/src/componentManifest/generator.ts (1)
componentManifestGenerator(20-131)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (2)
code/core/src/csf-tools/CsfFile.ts (2)
CsfFile(281-1042)meta(964-966)code/renderers/react/src/componentManifest/utils.ts (1)
invariant(15-23)
code/core/src/csf-tools/enrichCsf.test.ts (1)
code/core/src/csf-tools/CsfFile.ts (1)
loadCsf(1060-1064)
code/renderers/react/src/enrichCsf.ts (2)
code/core/src/common/utils/formatter.ts (1)
getPrettier(1-6)code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
getCodeSnippet(6-211)
code/renderers/react/src/componentManifest/generator.ts (6)
code/renderers/react/src/componentManifest/utils.ts (2)
groupBy(2-12)invariant(15-23)code/core/src/csf-tools/CsfFile.ts (1)
loadCsf(1060-1064)code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
getCodeSnippet(6-211)code/core/src/types/modules/core-common.ts (1)
ComponentManifest(348-358)code/core/src/csf-tools/enrichCsf.ts (1)
extractDescription(158-178)code/renderers/react/src/componentManifest/jsdocTags.ts (1)
extractJSDocInfo(5-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: normal
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (7)
code/renderers/react/src/componentManifest/generator.ts (1)
36-40: Confirm type of csf._meta?.component used for name.If component can be non-string in some CSF variants, guard to fallback to title segment.
-const name = csf._meta?.component ?? entry.title.split('/').at(-1)!; +const metaName = csf._meta?.component; +const name = typeof metaName === 'string' ? metaName : entry.title.split('/').at(-1)!;code/renderers/react/src/enrichCsf.ts (1)
90-103: Change aligns with documented API and existing consumer expectations — verified.Downstream consumers confirm the expectation:
- Manager addon reads
parameter.source?.codeas primary field, falling back toparameter.source?.originalSource- Source block directly accesses
source.code- Vue3 renderer documents
parameters.docs.source.codeas a valid story parameterThe implementation correctly adds the
codefield via the snippet while preserving existing source properties through the spread operator (...docsParameter.source), maintaining backward compatibility with anyoriginalSourcethat may be present.code/renderers/react/src/componentManifest/generateCodeSnippet.ts (5)
6-17: LGTM! Error handling and private field access are appropriate.The refactored signature and CSF-driven approach improve error reporting. The use of
csf._storyDeclarationPath,csf._metaNode, andcsf._storyPathsto access private fields is acceptable since no public accessors exist for these fields in the CsfFile API. The error framing usingbuildCodeFrameErrorprovides good context for debugging.
19-96: LGTM! Story normalization logic handles multiple CSF patterns correctly.The normalization flow properly handles:
- Function declarations vs. variable declarators
- Template.bind(...) pattern resolution
- Factory function unwrapping
- TypeScript type annotations (satisfies/as)
- Both function-based and object-based stories
Error messages provide clear context via
buildCodeFrameError.
137-192: LGTM! Efficient JSX transformation with proper change tracking.The transformation logic correctly:
- Handles arrow functions with direct JSX bodies
- Processes block statements with return statements
- Tracks changes and only creates new declarations when needed
- Returns the appropriate declaration type (FunctionDeclaration vs. VariableDeclaration)
- Preserves the original function when no transformations apply
319-397: LGTM! Recursive inlining handles all JSX node types correctly.The
inlineArgsInJsxfunction properly:
- Recursively processes nested JSX elements and fragments
- Inlines
args.fooreferences into concrete values- Handles the special
args.childrencase- Removes attributes when inlined values are null
- Tracks changes with the
||=operator
399-489: LGTM! Spread expansion logic correctly avoids duplicate attributes.The
transformArgsSpreadsInJsxfunction properly:
- Expands
{...args}into concrete JSX attributes- Tracks existing attribute names to avoid duplicates
- Preserves the insertion position of the first spread
- Handles invalid JSX attribute names via spread objects
- Injects children when appropriate
| const examples = Object.keys(csf._stories) | ||
| .map((storyName) => { | ||
| try { | ||
| return { | ||
| name: storyName, | ||
| snippet: recast.print(getCodeSnippet(csf, storyName, name)).code, | ||
| }; | ||
| } catch (e) { | ||
| invariant(e instanceof Error); | ||
| return { | ||
| name: storyName, | ||
| error: { | ||
| message: e.message, | ||
| }, | ||
| }; | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capture unknown exceptions without crashing.
Using invariant(e instanceof Error) can rethrow and abort generation. Always coerce to a string.
- .map((storyName) => {
+ .map((storyName) => {
try {
return {
name: storyName,
snippet: recast.print(getCodeSnippet(csf, storyName, name)).code,
};
} catch (e) {
- invariant(e instanceof Error);
+ const message = e instanceof Error ? e.message : String(e);
return {
name: storyName,
error: {
- message: e.message,
+ message,
},
};
}
})🤖 Prompt for AI Agents
In code/renderers/react/src/componentManifest/generator.ts around lines 41 to
57, the current catch uses invariant(e instanceof Error) which can throw and
abort generation; instead remove the invariant and safely coerce any thrown
value to a string (e.g., use const msg = e instanceof Error ? e.message :
String(e) or JSON.stringify fallback) and return the story object with error: {
message: msg } so unknown exceptions are captured without crashing.
| const promises = Object.keys(csf._stories).map(async (key) => { | ||
| if (!csfSource._meta?.component) { | ||
| return; | ||
| } | ||
| const { format } = await getPrettier(); | ||
|
|
||
| let node; | ||
| let snippet; | ||
| try { | ||
| const code = recast.print( | ||
| getCodeSnippet(storyExport, csfSource._metaNode, csfSource._meta?.component) | ||
| ).code; | ||
|
|
||
| // TODO read the user config | ||
| snippet = await format(code, { filepath: join(process.cwd(), 'component.tsx') }); | ||
| node = getCodeSnippet(csfSource, key, csfSource._meta?.component); | ||
| } catch (e) { | ||
| // don't bother the user if we can't generate a snippet | ||
| return; | ||
| if (!(e instanceof Error)) { | ||
| return; | ||
| } | ||
| snippet = e.message; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t drop non-Error throws; coerce to string.
Returning early on non-Error hides failures; always surface a message.
Apply:
- try {
- node = getCodeSnippet(csfSource, key, csfSource._meta?.component);
- } catch (e) {
- if (!(e instanceof Error)) {
- return;
- }
- snippet = e.message;
- }
+ try {
+ node = getCodeSnippet(csfSource, key, csfSource._meta?.component);
+ } catch (e) {
+ snippet = e instanceof Error ? e.message : String(e);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const promises = Object.keys(csf._stories).map(async (key) => { | |
| if (!csfSource._meta?.component) { | |
| return; | |
| } | |
| const { format } = await getPrettier(); | |
| let node; | |
| let snippet; | |
| try { | |
| const code = recast.print( | |
| getCodeSnippet(storyExport, csfSource._metaNode, csfSource._meta?.component) | |
| ).code; | |
| // TODO read the user config | |
| snippet = await format(code, { filepath: join(process.cwd(), 'component.tsx') }); | |
| node = getCodeSnippet(csfSource, key, csfSource._meta?.component); | |
| } catch (e) { | |
| // don't bother the user if we can't generate a snippet | |
| return; | |
| if (!(e instanceof Error)) { | |
| return; | |
| } | |
| snippet = e.message; | |
| } | |
| const promises = Object.keys(csf._stories).map(async (key) => { | |
| if (!csfSource._meta?.component) { | |
| return; | |
| } | |
| const { format } = await getPrettier(); | |
| let node; | |
| let snippet; | |
| try { | |
| node = getCodeSnippet(csfSource, key, csfSource._meta?.component); | |
| } catch (e) { | |
| snippet = e instanceof Error ? e.message : String(e); | |
| } |
🤖 Prompt for AI Agents
In code/renderers/react/src/enrichCsf.ts around lines 16 to 31, the catch block
currently returns early for non-Error throws which hides failures; change it to
always capture a message instead: remove the early return and set snippet to the
thrown value coerced to a string (e.g. snippet = e instanceof Error ? e.message
: String(e)) so non-Error throws are surfaced and processing continues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
code/renderers/react/src/componentManifest/generator.test.ts (1)
12-12: Remove unnecessary non-null assertion operators.The non-null assertion operators (
!) on literalnullvalues are contradictory and unnecessary. TypeScript allowsnullas a valid value without the assertion.Apply this diff:
-vi.mock('tsconfig-paths', () => ({ loadConfig: () => ({ resultType: null!, message: null! }) })); +vi.mock('tsconfig-paths', () => ({ loadConfig: () => ({ resultType: null, message: null }) }));
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
code/core/src/types/modules/core-common.ts(1 hunks)code/renderers/react/src/componentManifest/generator.test.ts(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- code/core/src/types/modules/core-common.ts
🧰 Additional context used
📓 Path-based instructions (7)
code/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx
Files:
code/renderers/react/src/componentManifest/generator.test.ts
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)
**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together
Files:
code/renderers/react/src/componentManifest/generator.test.ts
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/componentManifest/generator.test.ts
**/*.@(test|spec).{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.@(test|spec).{ts,tsx,js,jsx}: Unit tests should import and execute the functions under test rather than only asserting on syntax patterns
Mock external dependencies in tests usingvi.mock()(e.g., filesystem, loggers)
Files:
code/renderers/react/src/componentManifest/generator.test.ts
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/componentManifest/generator.test.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/componentManifest/generator.test.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/componentManifest/generator.test.ts
🧠 Learnings (5)
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to access and implement mock behaviors
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-10-13T13:33:14.659Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-13T13:33:14.659Z
Learning: Applies to **/*.@(test|spec).{ts,tsx,js,jsx} : Mock external dependencies in tests using `vi.mock()` (e.g., filesystem, loggers)
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to type and access mocked functions
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.197Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.197Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use type-safe mocking with vi.mocked()
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Applied to files:
code/renderers/react/src/componentManifest/generator.test.ts
🧬 Code graph analysis (1)
code/renderers/react/src/componentManifest/generator.test.ts (1)
code/renderers/react/src/componentManifest/generator.ts (1)
componentManifestGenerator(20-131)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: normal
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (3)
code/renderers/react/src/componentManifest/generator.test.ts (3)
418-467: LGTM! Well-structured test helper.The
getManifestForStoryhelper effectively encapsulates the boilerplate for testing individual story scenarios, reducing duplication and improving test maintainability.
469-482: LGTM! Clean CSF3 test wrapper.The
withCSF3helper provides consistent CSF3 boilerplate, making the test cases more readable and maintainable.
484-636: LGTM! Comprehensive error handling tests.These three test cases directly address the PR objectives by validating that manifest generation errors are surfaced clearly with line numbers and code context. The tests cover important edge cases:
- Fallback behavior when component name is missing
- Re-export errors with clear messaging about unsupported patterns
- Unknown expression errors with helpful context
The error messages include both the error description and surrounding code context, making it easy for users to identify and debug issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
code/renderers/react/src/componentManifest/generator.ts (1)
41-57: Don’t assert inside catch; coerce and continue (prevents aborting generation).invariant(e instanceof Error) can throw and terminate manifest generation. Coerce unknown throws to string and include file/story context in the error message.
- .map((storyName) => { + .map((storyName) => { try { return { name: storyName, snippet: recast.print(getCodeSnippet(csf, storyName, name)).code, }; } catch (e) { - invariant(e instanceof Error); + const message = e instanceof Error ? e.message : String(e); return { name: storyName, - error: { - message: e.message, - }, + error: { message: `Snippet generation failed for ${entry.importPath}#${storyName}: ${message}` }, }; } })
🧹 Nitpick comments (5)
code/renderers/react/src/componentManifest/generator.ts (5)
34-34: Use map, not flatMap, with async callbacks.flatMap ignores the “flattening” benefit when the callback returns a Promise; it reads as a mistake.
- singleEntryPerComponent.flatMap(async (entry): Promise<ReactComponentManifest> => { + singleEntryPerComponent.map(async (entry): Promise<ReactComponentManifest> => {
68-76: Avoid redundant fields; base already includes name/examples.Keeps objects smaller and prevents accidental divergence.
if (!entry.componentPath) { const message = `No component file found for the "${name}" component.`; return { ...base, - name, - examples, error: { message }, }; }
113-122: Same redundancy in final return; name/examples are already in base.Remove duplicates; rely on base.
return { ...base, - name, description: manifestDescription?.trim(), summary: tags.summary?.[0], import: tags.import?.[0], reactDocgen: docgen, jsDocTags: tags, - examples, error, };
14-14: Drop unused invariant import after fixes.Prevents dead code warnings.
-import { groupBy, invariant } from './utils'; +import { groupBy } from './utils';
60-66: Useentry.componentPathwith fallback toimportPathfor thepathfield.The ComponentManifest.path should reference the component file, not the story file. In the test setup,
componentPath: './src/stories/Button.tsx'andimportPath: './src/stories/Button.stories.ts'are distinct. Currently, path is always set to importPath (story file), which doesn't align with the interface name semantically.The suggested change
path: entry.componentPath ?? importPathcorrectly prioritizes the component file when available while maintaining consistency in the error path (which spreads...base). The test snapshot will need updating to reflect the new path value:'./src/stories/Button.tsx'.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
code/renderers/react/src/componentManifest/generator.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/componentManifest/generator.ts
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/componentManifest/generator.ts
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/componentManifest/generator.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/componentManifest/generator.ts
🧬 Code graph analysis (1)
code/renderers/react/src/componentManifest/generator.ts (6)
code/renderers/react/src/componentManifest/utils.ts (2)
groupBy(2-12)invariant(15-23)code/core/src/csf-tools/CsfFile.ts (1)
loadCsf(1060-1064)code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
getCodeSnippet(6-211)code/core/src/types/modules/core-common.ts (1)
ComponentManifest(348-358)code/core/src/csf-tools/enrichCsf.ts (1)
extractDescription(158-178)code/renderers/react/src/componentManifest/jsdocTags.ts (1)
extractJSDocInfo(5-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: normal
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (2)
code/renderers/react/src/componentManifest/generator.ts (2)
24-29: Double filtering by type/subtype — confirm intent.If type === 'story' already guarantees story entries, the subtype filter may be redundant; if not, keep both. Just flagging for clarity.
37-37: Remove the suggested type guard - no issue exists.The code already ensures
csf._meta?.componentis a string. InCsfFile.tsline 395, the component property is explicitly assigned viarecast.print(p.value, {}).code, which converts the AST node to a string. The typeStaticMeta.componentis correctly defined asstring | undefined(line 263), and the fallback handles the undefined case.Likely an incorrect or invalid review comment.
| try { | ||
| componentFile = await readFile(path.join(process.cwd(), entry.componentPath!), 'utf-8'); | ||
| } catch (e) { | ||
| invariant(e instanceof Error); | ||
| return { | ||
| ...base, | ||
| name, | ||
| examples, | ||
| error: { | ||
| message: `Could not read the component file located at ${entry.componentPath}`, | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue: remove invariant in catch; preserve original cause.
This can also abort the run. Include the fs error message for debugging.
- } catch (e) {
- invariant(e instanceof Error);
- return {
- ...base,
- name,
- examples,
- error: {
- message: `Could not read the component file located at ${entry.componentPath}`,
- },
- };
- }
+ } catch (e) {
+ const message = e instanceof Error ? e.message : String(e);
+ return {
+ ...base,
+ error: {
+ message: `Could not read the component file at ${entry.componentPath}: ${message}`,
+ },
+ };
+ }🤖 Prompt for AI Agents
In code/renderers/react/src/componentManifest/generator.ts around lines 80 to
91, the catch block currently calls invariant(e instanceof Error) which can
abort the run and discards the original fs error; change the catch to not use
invariant, instead capture the thrown value safely (e.g. derive a message via e
instanceof Error ? e.message : String(e)) and include that message (and
optionally stack) in the returned error object so the original filesystem error
is preserved for debugging while still returning the manifest error.
| const error = !docgen | ||
| ? { | ||
| message: `Could not parse props information for the located at ${entry.componentPath}`, | ||
| } | ||
| : undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix typo and improve clarity of docgen error.
Minor grammar; also clarify the target file.
- ? {
- message: `Could not parse props information for the located at ${entry.componentPath}`,
- }
+ ? {
+ message: `Could not parse props information for the component file located at ${entry.componentPath}`,
+ }
: undefined;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const error = !docgen | |
| ? { | |
| message: `Could not parse props information for the located at ${entry.componentPath}`, | |
| } | |
| : undefined; | |
| const error = !docgen | |
| ? { | |
| message: `Could not parse props information for the component file located at ${entry.componentPath}`, | |
| } | |
| : undefined; |
🤖 Prompt for AI Agents
In code/renderers/react/src/componentManifest/generator.ts around lines 100 to
104, the error message contains a typo ("the located at") and is unclear about
the target; update the message to correct grammar and explicitly reference the
component/file path (e.g. "Could not parse props information for the component
located at ${entry.componentPath}." or "for the file located at
${entry.filePath}."), leaving the rest of the logic unchanged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx (1)
13-15: Use public CsfFile getter instead of private_meta.Replace
csf._meta?.componentwithcsf.meta?.componentto avoid coupling to internals.- .map((name) => getCodeSnippet(csf, name, csf._meta?.component ?? 'ComponentTitle')) + .map((name) => getCodeSnippet(csf, name, csf.meta?.component ?? 'ComponentTitle'))code/renderers/react/src/componentManifest/generateCodeSnippet.ts (2)
14-17: Avoid throwing a string; always throw an Error/SyntaxError.Ensure consistent error objects when story is missing.
- if (!storyDeclaration) { - const message = 'Expected story to be a function or variable declaration'; - throw csf._storyPaths[storyName]?.buildCodeFrameError(message) ?? message; - } + if (!storyDeclaration) { + const message = 'Expected story to be a function or variable declaration'; + const err = csf._storyPaths[storyName]?.buildCodeFrameError(message); + throw err ?? new SyntaxError(message); + }
208-213: ValidatecomponentNamefor a safe JSX identifier.Guard against invalid identifiers (e.g., names with dots/spaces) to avoid generating invalid JSX. Fallback to a safe default.
- const name = t.jsxIdentifier(componentName); + const safeName = t.isValidIdentifier(componentName) ? componentName : 'Component'; + const name = t.jsxIdentifier(safeName);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx(7 hunks)code/renderers/react/src/componentManifest/generateCodeSnippet.ts(6 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,json,html,ts,tsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (useyarn lint:js:cmd <file>)
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.tscode/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Export functions from modules when they need to be unit-tested
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.tscode/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
code/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In application code, use Storybook loggers instead of
console.*(client code:storybook/internal/client-logger; server code:storybook/internal/node-logger)
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.tscode/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do not use
console.log,console.warn, orconsole.errordirectly unless in isolated files where importing loggers would significantly increase bundle size
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.tscode/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
code/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)
**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
**/*.@(test|spec).{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.@(test|spec).{ts,tsx,js,jsx}: Unit tests should import and execute the functions under test rather than only asserting on syntax patterns
Mock external dependencies in tests usingvi.mock()(e.g., filesystem, loggers)
Files:
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
🧠 Learnings (1)
📚 Learning: 2025-10-13T13:33:14.659Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-13T13:33:14.659Z
Learning: Applies to **/*.{ts,tsx,js,jsx,mjs} : Export functions from modules when they need to be unit-tested
Applied to files:
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx
🧬 Code graph analysis (2)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (2)
code/core/src/csf-tools/CsfFile.ts (2)
CsfFile(281-1042)meta(964-966)code/renderers/react/src/componentManifest/utils.ts (1)
invariant(15-23)
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx (1)
code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
getCodeSnippet(6-225)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: normal
- GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (7)
code/renderers/react/src/componentManifest/generateCodeSnippet.test.tsx (6)
79-87: LGTM: error is surfaced with codeframe.Good assertion on the thrown SyntaxError with location context.
100-107: LGTM: CSF4 zero‑arg factory support.Covers
meta.story()and expected fallback rendering.
219-239: LGTM: meta‑level render precedence.Validates merging and inlining from
meta.render+ story args.
284-288: LGTM: block‑body transform snapshot.Confirms JSX transform inside function block preserves structure.
543-548: LGTM: multiple spreads scenario.Ordering and de‑duplication look correct.
550-569: LGTM: top‑level export functions.Keeps
functionform and correctly inlines args.code/renderers/react/src/componentManifest/generateCodeSnippet.ts (1)
116-120: Review comment is incorrect—BabelFile already has the correctpathproperty.The local
BabelFileinterface incode/core/src/csf-tools/CsfFile.ts(lines 31–39) includespath: NodePath<t.Program>, which matches the expected type forpathForNode's first parameter. The call site at line 116 (pathForNode(csf._file.path, metaObj)) is correctly typed. No refactoring or type widening is required.Likely an incorrect or invalid review comment.
| invariant( | ||
| init.isExpression(), | ||
| () => | ||
| storyDeclaration.buildCodeFrameError('Expected story initializer to be an expression') | ||
| .message | ||
| ); | ||
| storyPath = init; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Preserve SyntaxError + codeframe by throwing the Babel error directly (not via invariant).
Using invariant(...messageFromBuildCodeFrameError...) downgrades to Error and can drop SyntaxError typing. Throw the codeframe error object directly to align with the PR goal of better error surfacing.
- invariant(
- init.isExpression(),
- () =>
- storyDeclaration.buildCodeFrameError('Expected story initializer to be an expression')
- .message
- );
+ if (!init.isExpression()) {
+ throw storyDeclaration.buildCodeFrameError('Expected story initializer to be an expression');
+ }
...
- invariant(
- args.length === 1,
- () => storyPath.buildCodeFrameError('Could not evaluate story expression').message
- );
+ if (args.length !== 1) {
+ throw storyPath.buildCodeFrameError('Could not evaluate story expression');
+ }
const storyArg = args[0];
- invariant(
- storyArg.isExpression(),
- () => storyPath.buildCodeFrameError('Could not evaluate story expression').message
- );
+ if (!storyArg.isExpression()) {
+ throw storyPath.buildCodeFrameError('Could not evaluate story expression');
+ }Also applies to: 65-75
🤖 Prompt for AI Agents
In code/renderers/react/src/componentManifest/generateCodeSnippet.ts around
lines 25-31 (and similarly 65-75), replace the invariant(...) pattern that
passes storyDeclaration.buildCodeFrameError(...).message with directly throwing
the Babel/codeframe Error object returned by
storyDeclaration.buildCodeFrameError(...). Specifically, when init is not an
expression (and the other similar check), call const err =
storyDeclaration.buildCodeFrameError('...'); throw err; so the original
SyntaxError/codeframe type and stack are preserved instead of converting to a
plain Error via invariant.
* add GET handler to /mcp path * merge changelogs * update manifest type to match upcoming manifest changes in storybookjs/storybook#32855 * add changesets
b390162
into
10.1-with-canary-release
Closes #32848
What I did
Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!
Documentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsMake sure this PR contains one of the labels below:
Available labels
bug: Internal changes that fixes incorrect behavior.maintenance: User-facing maintenance tasks.dependencies: Upgrading (sometimes downgrading) dependencies.build: Internal-facing build tooling & test updates. Will not show up in release changelog.cleanup: Minor cleanup style change. Will not show up in release changelog.documentation: Documentation only changes. Will not show up in release changelog.feature request: Introducing a new feature.BREAKING CHANGE: Changes that break compatibility in some way with current major version.other: Changes that don't fit in the above categories.🦋 Canary release
This pull request has been released as version
0.0.0-pr-32855-sha-350cba2a. Try it out in a new sandbox by runningnpx [email protected] sandboxor in an existing project withnpx [email protected] upgrade.More information
0.0.0-pr-32855-sha-350cba2akasper/error-manifest350cba2a1761660872)To request a new release of this pull request, mention the
@storybookjs/coreteam.core team members can create a new canary release here or locally with
gh workflow run --repo storybookjs/storybook publish.yml --field pr=32855Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores