Languages / JavaScript

JavaScript static analysis: dead code, complexity and call graphs

To find dead code in JavaScript, use Knip for files, exports and dependencies, or ESLint no-unused-vars inside files. repowise builds one import and call graph across CommonJS and ES modules, knows Express, Koa, Fastify and Hono route handlers, and ranks each unused file or export by confidence, so dynamic require patterns do not become false deletions.

Last reviewed: November 2026

Full tier

What repowise detects in JavaScript

Checked against the engine source. Every row runs locally with zero LLM calls.

repowise capabilities for JavaScript
Files parsed.js, .jsx, .mjs, .cjs
Import resolutionFull. ES modules and CommonJS require, CommonJS re-exports, jsconfig paths, workspaces, package.json exports and # subpath imports
Call graphResolved calls with confidence per edge
Dead codeUnreachable files, unused exports, zombie packages; deletion-ready flag on unused exports
Health markersComplexity, class metrics, Extract Method plans, assertion-free tests and mock saturation
Performance markersI/O in loops (fetch, axios, node:fs, child_process), serial await, JSON.parse in loops, spread in reduce
Coverage reportslcov (Jest, c8, nyc), Cobertura, Clover
Frameworks recognisedExpress app.use and routes, Koa, Fastify, hapi, Hono, Remix, test-runner setup files

Specific to this language

How JavaScript code is read

CommonJS and ESM in one graph

Older Node code mixes require and import, and module.exports assignments re-export whole objects. repowise resolves both forms and CommonJS re-exports into the same graph, so a helper used through require in one file and import in another is one node with two importers.

Route handlers count as used

An Express handler passed to app.get or mounted with app.use(router) is reached through the framework, not imported by a caller. Express, Koa, Fastify, hapi and Hono route registration becomes edges, so handler files are not reported as unreachable.

Dynamic require is a signal

require() with a computed path, require.resolve and require.context cap the confidence of nearby files at the review tier. A plugins folder loaded by a loop over readdir is held for review, not marked safe to delete.

Honest comparison

Native JavaScript tools, and when they are enough

Native JavaScript analysis tools
ToolWhat it covers
KnipUnused files, exports and dependencies for JavaScript and TypeScript, with framework plugins and auto-fix.
ESLintno-unused-vars within a file, the complexity rule, and import plugins for unresolved paths.
madge, dependency-cruiserModule graphs, circular dependencies and dependency rules.
c8, nyc (Istanbul)Coverage. Both write lcov, which repowise reads.

Where repowise adds value

  • Confidence tiers from git history and runtime-load signals, so old untouched files rank above files someone edited last week.
  • One pass over JavaScript and every other language in the repository.
  • Ownership, hotspots and co-change on the same files, so you know who to ask before deleting.
  • Agent context: get_dead_code and get_risk over MCP for Claude Code, Cursor and Codex.

How to run it

Analyse a JavaScript repository

From the repository root. Agents get the same results through the get_dead_code, get_health and get_context MCP tools.

uv tool install repowise        # or: pipx install repowise
repowise init --yes --no-prose     # index the repo, no API key
repowise dead-code --min-confidence 0.7 --safe-only
repowise health --refactoring-targets
npx c8 --reporter=lcov npm test
repowise coverage add coverage/lcov.info
repowise serve                     # graph, health and dead code in a local dashboard

Questions

What people ask before they try it.

How do I find unused JavaScript files in a Node.js project?

Run repowise dead-code --kind unreachable_file. A file is unreachable when nothing imports it and it is not an entry point: files with a shebang, test files, build and config files and framework routes are roots. Each result carries a confidence and the reason it was reported.

Does it work on CommonJS code?

Yes. require, module.exports, exports.x assignments and CommonJS re-exports are resolved, alongside ES module imports, in the same graph.

How is this different from ESLint no-unused-vars?

ESLint looks inside one file at a time. It cannot tell that an exported function is never imported anywhere. repowise works across the whole repository graph, so it reports unused exports and whole unreachable files.

Can I measure cyclomatic complexity in JavaScript with it?

Yes. Complexity is one input to the 1.0 to 10.0 file health score, next to class size, test smells, performance markers, coverage and churn. repowise health --refactoring-targets lists the functions that drive the score with an Extract Method plan.

Does repowise send my JavaScript code to an LLM?

No. Parsing, the call graph, dead code, health scores, performance markers and git history all run locally with zero LLM calls. repowise init --yes --no-prose needs no API key, and the analysis itself makes no network calls. Written documentation prose is the one opt-in step that uses a model, and it can run on a local Ollama model.

Index a JavaScript repository.

Public repositories are free on repowise.dev. The CLI is open source and runs on your machine with no API key.