Skip to content

Csf-tools: Prevent false positive ObjectExpression warning on valid default exports#34993

Open
surendravarikallu wants to merge 1 commit into
storybookjs:nextfrom
surendravarikallu:fix/issue-31104
Open

Csf-tools: Prevent false positive ObjectExpression warning on valid default exports#34993
surendravarikallu wants to merge 1 commit into
storybookjs:nextfrom
surendravarikallu:fix/issue-31104

Conversation

@surendravarikallu

@surendravarikallu surendravarikallu commented May 31, 2026

Copy link
Copy Markdown

Description

Storybook logged a warning CSF Parsing error: Expected 'ObjectExpression' when the default export of a preview/story configuration was an imported reference or expression (e.g. an Identifier, CallExpression, or MemberExpression) that cannot be parsed statically. These exports are valid at runtime but could not be parsed to an ObjectExpression by the static parser.

This PR:

  • Guards default export and module.exports checks in ConfigFile to skip warnings on Identifier, CallExpression, and MemberExpression nodes.
  • Adds unit tests to cover the warning exclusion logic.

Manual testing

  • Created a preview config with export default config; (imported reference) and verified that the CSF Parsing error console warning is no longer logged.

…efault exports

Storybook logged a warning 'CSF Parsing error: Expected 'ObjectExpression'' when the default export of a preview/story configuration was an imported reference or expression (e.g. an Identifier, CallExpression, or MemberExpression). These resolved exports are valid at runtime but cannot be resolved statically to an ObjectExpression by the parser.

- Guard default export checks in ConfigFile to skip warning on Identifier, CallExpression, and MemberExpression nodes.
- Guard module.exports checks similarly.
- Added tests to cover the warning exclusion logic.
@github-actions

github-actions Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor
Fails
🚫

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

Generated by 🚫 dangerJS against cbaef0b

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

ConfigFile.parse() now conditionally emits "Expected ObjectExpression" warnings when parsing export default and module.exports assignments. Instead of warning for all non-ObjectExpression resolutions, it skips Identifier, CallExpression, and MemberExpression forms that cannot be statically resolved, with corresponding test coverage validating no warnings are emitted for these cases.

Changes

Config Parser Warning Suppression

Layer / File(s) Summary
Conditional warning guards for unresolved exports
code/core/src/csf-tools/ConfigFile.ts
The parse() method's ExportDefaultDeclaration (lines 224–229) and module.exports ExpressionStatement (lines 312–317) handlers gate the "Expected ObjectExpression" warning so that Identifier, CallExpression, and MemberExpression forms are ignored while other non-ObjectExpression values still trigger the warning.
Test coverage for unresolved exports
code/core/src/csf-tools/ConfigFile.test.ts
Test imports add vi from vitest and logger for spy assertions. A new test (lines 256–279) verifies that parsing three export default forms (identifier, call expression, member expression) does not emit logger.warn calls.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • storybookjs/storybook#33885: Both PRs modify export default parsing and warning logic in ConfigFile.ts to suppress warnings for non-ObjectExpression CSF-factory call and member-expression chains.
  • storybookjs/storybook#32891: Both PRs modify default/export parsing paths in ConfigFile.ts by resolving exported declarations to determine whether the resolved value is an Identifier or related expression.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/csf-tools/ConfigFile.test.ts`:
- Around line 1-5: The test currently calls logger.warn.mockImplementation
inline inside a test; instead, add a module-level
vi.mock('storybook/internal/node-logger', { spy: true }) and use
vi.mocked(logger) to obtain the mocked module, then move the warn mock setup
into a beforeEach (e.g., const mockedLogger = vi.mocked(logger); beforeEach(()
=> mockedLogger.warn.mockImplementation(() => {}))); also add afterEach(() =>
vi.restoreAllMocks()) to reset spies and remove any inline mockImplementation in
individual it(...) blocks; update references to logger.warn in tests to use the
mockedLogger where needed.

In `@code/core/src/csf-tools/ConfigFile.ts`:
- Around line 312-317: Tests currently only cover default-export paths; add
regression tests ensuring no warning is emitted for module.exports when assigned
an identifier, a call expression, or a member expression. In the test suite for
ConfigFile handling (tests that exercise the exportObject guard around
exportObject and checks using
t.isIdentifier/t.isCallExpression/t.isMemberExpression), add three assertions
verifying that "module.exports = config", "module.exports = createConfig(...)",
and "module.exports = obj.value" are treated the same as the default-export
cases (i.e., no warning produced). Target the code path that evaluates
exportObject in ConfigFile (the guard using exportObject and the t.is* helpers)
so the tests fail if the guard regresses for module.exports assignments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7ee03543-81b8-4847-bf4d-a52141f67d40

📥 Commits

Reviewing files that changed from the base of the PR and between d6ce689 and cbaef0b.

📒 Files selected for processing (2)
  • code/core/src/csf-tools/ConfigFile.test.ts
  • code/core/src/csf-tools/ConfigFile.ts

Comment on lines +1 to 5
import { describe, expect, it, vi } from 'vitest';

import { babelPrint } from 'storybook/internal/babel';
import { babelPrint, types as t } from 'storybook/internal/babel';
import { logger } from 'storybook/internal/node-logger';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the relevant sections of the test file.
FILE="code/core/src/csf-tools/ConfigFile.test.ts"
echo "=== File: $FILE ==="
wc -l "$FILE"
rg -n "warn|spyOn\\(logger|warning" "$FILE" || true
# Show imports
sed -n '1,80p' "$FILE"
# Show around the referenced lines (~257-279)
sed -n '220,340p' "$FILE"

# Read the spy mocking rule file for exact constraints.
RULES_FILE=".cursor/rules/spy-mocking.mdc"
echo "=== Rules: $RULES_FILE ==="
ls -la "$RULES_FILE" || true
sed -n '1,200p' "$RULES_FILE" || true

Repository: storybookjs/storybook

Length of output: 7744


Align ConfigFile.test.ts logger warning suppression with Vitest spy-mocking rules.

In code/core/src/csf-tools/ConfigFile.test.ts (around lines 257-279), logger.warn is spied and mockImplementation is set inline inside the test body, which violates .cursor/rules/spy-mocking.mdc (mock implementations must be in beforeEach, and inline mock implementations in test cases should be avoided). It also doesn’t use the vi.mock(..., { spy: true }) + vi.mocked() pattern for module mocking.

Suggested update
-import { describe, expect, it, vi } from 'vitest';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
 
 import { babelPrint, types as t } from 'storybook/internal/babel';
 import { logger } from 'storybook/internal/node-logger';
 
 import { dedent } from 'ts-dedent';
 
 import { loadConfig, printConfig } from './ConfigFile.ts';
+
+vi.mock('storybook/internal/node-logger', { spy: true });
+
+const mockedWarn = vi.mocked(logger.warn);
+
+beforeEach(() => {
+  mockedWarn.mockImplementation(() => {});
+});
@@
-      it('should not log warning when default export is Identifier, CallExpression, or MemberExpression', () => {
-        const warnSpy = vi.spyOn(logger, 'warn').mockImplementation(() => {});
+      it('should not log warning when default export is Identifier, CallExpression, or MemberExpression', () => {
@@
-        expect(warnSpy).not.toHaveBeenCalled();
-        warnSpy.mockRestore();
+        expect(mockedWarn).not.toHaveBeenCalled();
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/csf-tools/ConfigFile.test.ts` around lines 1 - 5, The test
currently calls logger.warn.mockImplementation inline inside a test; instead,
add a module-level vi.mock('storybook/internal/node-logger', { spy: true }) and
use vi.mocked(logger) to obtain the mocked module, then move the warn mock setup
into a beforeEach (e.g., const mockedLogger = vi.mocked(logger); beforeEach(()
=> mockedLogger.warn.mockImplementation(() => {}))); also add afterEach(() =>
vi.restoreAllMocks()) to reset spies and remove any inline mockImplementation in
individual it(...) blocks; update references to logger.warn in tests to use the
mockedLogger where needed.

Comment on lines +312 to +317
} else if (
exportObject &&
!t.isIdentifier(exportObject) &&
!t.isCallExpression(exportObject) &&
!t.isMemberExpression(exportObject)
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add regression coverage for module.exports unresolved expressions.

This guard changed behavior for module.exports too, but the new tests only assert the default-export path. Please add a counterpart test ensuring no warning for module.exports = config, module.exports = createConfig(...), and module.exports = obj.value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/csf-tools/ConfigFile.ts` around lines 312 - 317, Tests
currently only cover default-export paths; add regression tests ensuring no
warning is emitted for module.exports when assigned an identifier, a call
expression, or a member expression. In the test suite for ConfigFile handling
(tests that exercise the exportObject guard around exportObject and checks using
t.isIdentifier/t.isCallExpression/t.isMemberExpression), add three assertions
verifying that "module.exports = config", "module.exports = createConfig(...)",
and "module.exports = obj.value" are treated the same as the default-export
cases (i.e., no warning produced). Target the code path that evaluates
exportObject in ConfigFile (the guard using exportObject and the t.is* helpers)
so the tests fail if the guard regresses for module.exports assignments.

@valentinpalkovic

Copy link
Copy Markdown
Contributor

Hi @surendravarikallu

Thank you for your contribution.

I've seen that you have opened a lot of PRs. A comment on the style and format:

  1. Please always reference the issue you want to close.
  2. Please use our GitHub PR template to create the PRs.
  3. Make sure all fixes are tested manually

Thank you!

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

Projects

Status: Human verification

Development

Successfully merging this pull request may close these issues.

2 participants