DEV Community

Cover image for I built a Rust-based codebase analyzer that finds dead code in JS/TS projects in milliseconds
Bart Waardenburg
Bart Waardenburg

Posted on

I built a Rust-based codebase analyzer that finds dead code in JS/TS projects in milliseconds

TL;DR: fallow is a Rust-native static analyzer for JS/TS codebases. It finds unused files, unused exports, unused dependencies, circular dependencies, and duplicated code. Zero config, 84 framework plugins auto-detected, sub-second on most projects. npx fallow check to try it now.


The problem that kept bugging me

Every codebase I've worked on has dead code. Exports that nothing imports. Dependencies in package.json that no file uses. Files that were "temporarily" left in after a refactor six months ago. Entire utility modules where 3 out of 12 functions are actually called.

That dead code isn't free. It bloats your bundle, slows your builds, and confuses anyone new to the project because they can't tell what's actually used and what's left over from two refactors ago. And the longer nobody touches it, the scarier it gets to delete.

This was annoying but manageable when humans wrote all the code. Now AI coding agents generate code at a pace where nobody can keep track of what's still needed. They create new files, refactor existing ones, but never go back to clean up what they left behind. They can't, really. Determining whether an export is unused requires building a complete module graph across every file in the project, which is a graph traversal problem, not a "read the file and figure it out" problem.

Existing tools work, but they were slow enough that I'd only run them occasionally. So I built fallow.

What it does

Fallow builds the full import/export graph of your project and reports:

  • Unused files, exports, types, enum members, class members
  • Unused dependencies and devDependencies
  • Unresolved imports and unlisted dependencies
  • Duplicate exports (same symbol from multiple modules)
  • Circular dependencies
  • Code duplication (4 detection modes)

12 issue types total. Here's what it looks like:

$ fallow check

● Unused files (16)
  src/server/jobs/worker.ts
  src/server/jobs/cron.ts
  src/features/savings/hooks/usePotGroups.ts
  ... and 13 more

● Unused exports (20)
  src/components/Card/index.ts
    :1 CardFooter
  src/providers/trpc-provider/index.tsx
    :12 TRPCProvider

● Unused dependencies (1)
  @trpc/react-query

● Duplicate exports (50)
  ... across 23 files

Found 401 issues in 0.16s
Enter fullscreen mode Exit fullscreen mode

That last line is the part I'm most proud of. 0.16 seconds for 401 issues across a real-world codebase.

Why Rust

I didn't pick Rust to be trendy. The bottleneck in dead code analysis is parsing every file in the project to build the module graph. Fallow uses the Oxc parser natively in Rust (not through NAPI bindings) and rayon for parallel file processing across all cores. If you're already looking at oxlint or oxfmt, fallow is the same family. Oxlint replaces ESLint, oxfmt replaces Prettier, fallow finds what you're not using anymore.

Here's how that plays out against knip, the most popular JS/TS dead code analyzer:

Project Files fallow knip v5 knip v6 vs v5 vs v6
zod 174 19ms 639ms 334ms 34x 18x
preact 244 20ms 819ms 41x
fastify 286 24ms 1.13s 289ms 46x 12x
vue/core 522 63ms 702ms 299ms 11x 5x
TanStack/query 901 148ms 2.75s 1.41s 19x 10x
svelte 3,337 325ms 1.93s 860ms 6x 3x
next.js 20,416 1.48s —* —*

Knip v6 adopted the Oxc parser through NAPI bindings and got a lot faster. But it's still single-threaded and still runs in Node.js. The gap narrows on bigger projects, but fallow stays sub-second even at 3,000+ files. On the Next.js monorepo (20,416 files), the existing tooling just crashes without producing results. I keep going back to that repo to make sure fallow actually works on real projects, not just benchmarks that fit neatly in memory. *knip errors out on next.js without valid results.

Memory usage is 3-15x lower too, which matters if you're running this in CI.

Duplication detection built in

Most projects I've worked on also have a copy-paste problem. fallow has a dupes command that finds duplicated code blocks using a suffix array algorithm (no quadratic pairwise comparison):

$ fallow dupes

Clone group 1 (42 lines, 3 instances)
├─ src/features/forecasting/server/procedures/analytics.ts:141-181
├─ src/features/forecasting/server/procedures/cashflow.ts:153-194
└─ src/features/forecasting/server/procedures/income.ts:590-631

Duplication: 19.4% (27,255 duplicated lines across 398 files)
Found 1,184 clone groups, 2,959 instances (0.23s)
Enter fullscreen mode Exit fullscreen mode

Four detection modes: strict (exact), mild (default), weak (different string literals), and semantic (catches renamed variables too). Clone families group related duplicates and suggest whether to extract a function or a module.

vs jscpd:

Project fallow jscpd Speedup
zod 46ms 909ms 20x
preact 44ms 1.33s 30x
fastify 84ms 2.83s 34x
vue/core 120ms 3.13s 26x
svelte 400ms 3.63s 9x
next.js 3.16s 24.64s 8x

Zero config, 84 plugins

Fallow reads your package.json and auto-detects your framework stack. Next.js, Vite, Vitest, Playwright, Storybook, ESLint, Tailwind, Prisma, Drizzle, Turborepo, Nx... 84 plugins total, 31 of which do deep config file parsing (reading your next.config.js, vite.config.ts, etc. to understand entry points and aliases).

No .fallowrc.json required unless you want to customize. Just run it.

Auto-fix

Fallow can remove unused exports, unused dependencies, and unused enum members automatically:

# Preview what would change
fallow fix --dry-run

# Apply fixes
fallow fix

# Non-interactive for CI/agents
fallow fix --yes
Enter fullscreen mode Exit fullscreen mode

It only touches things it's confident about. Unused files aren't auto-deleted because that's a bigger decision you should make yourself.

For the "I already use knip" crowd

Fair question. Knip is a solid tool with 141 plugins and a large community. If it's working for you and speed isn't a pain point, keep using it.

Reasons you might want fallow instead:

  • Speed is a pain point (especially in CI or watch mode)
  • You want dead code + duplication detection in one tool (replacing both knip and jscpd)
  • You need SARIF output for GitHub Code Scanning
  • You want --changed-since to only check files modified in a PR
  • You want inline suppression comments (// fallow-ignore-next-line)
  • You want baseline comparison for incremental adoption in existing projects
  • You use AI coding agents and want structured JSON output or MCP integration

If you do switch, fallow migrate converts your knip config automatically.

AI agent integration

This is the use case I care about most. Agents generate code but have no way to know what's unused across the full project. Fallow gives them that:

# Agent checks its own work after generating code
fallow check --changed-since main --format json

# Agent auto-fixes what it can
fallow fix --yes --format json
Enter fullscreen mode Exit fullscreen mode

There's also an MCP server (fallow-mcp) that exposes 6 tools for typed tool calling: analyze, check_changed, find_dupes, fix_preview, fix_apply, and project_info. Works with Claude Code, Cursor, and any MCP-compatible agent.

CI integration

# GitHub Actions
- uses: fallow-rs/fallow@v1
  with:
    format: sarif
Enter fullscreen mode Exit fullscreen mode

SARIF output uploads to GitHub Code Scanning for inline PR annotations. --changed-since scopes analysis to PR changes only. --save-baseline and --baseline let you adopt incrementally without fixing every existing issue first.

Try it

npx fallow check
Enter fullscreen mode Exit fullscreen mode

No install, no config, no signup. Takes about a second on most projects.

If you want duplication analysis too:

npx fallow dupes
Enter fullscreen mode Exit fullscreen mode

I'd love feedback. If fallow finds something useful (or misses something it shouldn't), open an issue or drop a comment here.

Top comments (0)