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.
| Files parsed | .js, .jsx, .mjs, .cjs |
|---|---|
| Import resolution | Full. ES modules and CommonJS require, CommonJS re-exports, jsconfig paths, workspaces, package.json exports and # subpath imports |
| Call graph | Resolved calls with confidence per edge |
| Dead code | Unreachable files, unused exports, zombie packages; deletion-ready flag on unused exports |
| Health markers | Complexity, class metrics, Extract Method plans, assertion-free tests and mock saturation |
| Performance markers | I/O in loops (fetch, axios, node:fs, child_process), serial await, JSON.parse in loops, spread in reduce |
| Coverage reports | lcov (Jest, c8, nyc), Cobertura, Clover |
| Frameworks recognised | Express 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
| Tool | What it covers |
|---|---|
| Knip | Unused files, exports and dependencies for JavaScript and TypeScript, with framework plugins and auto-fix. |
| ESLint | no-unused-vars within a file, the complexity rule, and import plugins for unresolved paths. |
| madge, dependency-cruiser | Module 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 dashboardQuestions
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.
Related reading
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.