Skip to content

dead_code false-negatives on any component with a Storybook .stories.tsx file #902

Description

@aberkeme

code-review-graph version

2.3.6

Operating system

Windows

Python version

3.14.3

AI platform

Claude Code

Output of code-review-graph status

Nodes: 7913
Edges: 67514
Files: 691
Languages: tsx, powershell, typescript, javascript
Last updated: 2026-08-22T00:22:16
Built on branch: main
Built at commit: cd8e3c0c4020

Steps to reproduce

  1. In a TypeScript/React repo that uses Storybook, add a component with zero real callers — e.g. components/Foo.tsx exporting Foo, imported by nothing else in the application.
  2. Add components/Foo.stories.tsx that imports and renders Foo (a completely ordinary Storybook story — no special naming beyond the standard .stories.tsx suffix).
  3. Run refactor_tool in dead_code mode (or the CLI equivalent) scoped to the component directory.

Expected vs actual behavior

Expected: Foo is reported as dead code — nothing in the application imports it, only its own story file does.

Actual: Foo is never reported, because the story's import edge counts as a live caller. refactor.py's _TEST_FILE_RE (used by find_dead_code) and parser.py's separate _TEST_FILE_PATTERNS (used to set is_test on every node) both match common test-file conventions (.test., .spec., __tests__/, test_*.py, e2e[-_]?tests?/, test[-_]utils?/) but neither list includes .stories. — the Storybook convention (Component Story Format) used by essentially every React/Vue/Svelte project with Storybook.

Because .stories.tsx files get is_test: false, they're treated as production source. Any component with a story — which in a maintained Storybook setup is most components — can never be flagged as dead no matter how disconnected it is from the actual application, since its story always supplies one "real" importer.

We hit this concretely: three components in our app (ArchiveSection, GroupCardDense, StickyRightRail) sat unreferenced by any page or route for 3–4 weeks. dead_code reported 0 results the whole time because each had a .stories.tsx. We only found them via a manual grep restricted to source directories, explicitly excluding each component's own story/test file.

Suggested fix — add a stories pattern to both lists:

  • parser.py _TEST_FILE_PATTERNS: re.compile(r".*\.stories\.[jt]sx?$")
  • refactor.py _TEST_FILE_RE: add |\.stories\.[jt]sx?$ to the alternation

(Whether story files should be classified as is_test: true outright, or dead_code should just special-case excluding them from the "has a caller" check via a separate flag, is worth a design decision either way — happy to open a PR against whichever approach you'd prefer.)

Additional context

This doesn't affect dead_code's Python/backend detection — only apparent for JS/TS ecosystems using Storybook (Component Story Format), which is the de facto standard for component libraries and design systems. Given how common Storybook is, I'd guess this false-negative affects most JS/TS repos that have adopted code-review-graph.

Also noticed a smaller duplication while tracing this: is_test classification lives in two independent places — parser.py's _TEST_FILE_PATTERNS and refactor.py's own _TEST_FILE_RE — with slightly different pattern sets (e.g. refactor.py matches e2e[-_]?tests?/ and test[-_]utils?/, which parser.py doesn't). Not sure if that's intentional, but it means a fix to one doesn't propagate to the other, and this issue is itself an example of that.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions