[ts-morph] List exports that have no references outside their declaring file across the project. Read-only.
When to use
- Hunting for dead code candidates after a refactor or migration.
- Auditing a module's surface area: which exports does nobody actually consume?
- Pre-deletion safety check before manually removing exports — combine with
find_references_by_tsmorph to double-confirm.
When NOT to use
- You want a single symbol's references — use
find_references_by_tsmorph. - Single-file unused locals —
tsc --noUnusedLocals is faster.
Detection scope
Reports:
export function/class/const/let/var/enum/interface/type ... (inline export keyword)export default function/class ... and export default <Identifier>export = <Identifier> (CommonJS)
Detection algorithm
For each candidate identifier, findReferencesAsNodes() is run and the following references are excluded before deciding "unused":
- References inside the SAME file as the declaration (internal use does not count).
- References inside any
ExportDeclaration (pure re-export sites like export { x } from "./y" or export *). This means a symbol re-exported only via a barrel — with nothing actually consuming the barrel — IS reported as unused. - References in
node_modules.
If 0 references remain, the export is reported.
Known limitations (this tool returns CANDIDATES, not verdicts)
Static analysis cannot see:
- Dynamic
require() / import() resolved from runtime strings. - File-system / convention based routing (Next.js
page.tsx, Remix routes, etc.). Pass these as entryPoints. - Symbols looked up via reflection or string keys.
- Pure local re-exports (
export { x } without from) where x is declared by a separate const x = ... in the same file — this form is not enumerated. - Mixed function + namespace declarations may be partially missed.
- Workspace packages that publish built output: in a monorepo, when a scanned package's
package.json entry points (exports / main / module / types) resolve outside the scanned sources (e.g. "exports": { ".": "./dist/index.js" }), imports from OTHER workspace packages resolve to the built files (or node_modules) instead of the scanned sources. Every export of such a package is then reported unused even when it IS consumed — a systematic false positive. The tool detects this shape and prepends a ⚠ package-level warning to the result; treat all candidates from a warned package as low confidence. Workaround: point that package's exports at source files for analysis, or verify each candidate with find_references_by_tsmorph / textHits.
Default exports are high false-positive
export default <Identifier> / export = <Identifier> (shown with the [default] tag) are prone to FALSE POSITIVES: findReferencesAsNodes runs on the local identifier and often fails to connect to import Foo from "./mod" default-import sites. A default export reported here with textHits well above 0 is almost certainly actually used. Treat [default] candidates as low confidence and always confirm with find_references_by_tsmorph.
Always verify a candidate with find_references_by_tsmorph before deletion.
Options
tsconfigPath: absolute path to tsconfig.json.entryPoints: list of absolute file paths whose exports should be skipped (treat as public API). Reference sites IN these files still count as "used" automatically.excludeFilePatterns: substrings; any file whose absolute path includes() a pattern is not scanned. Use this for test files (e.g. ".test."), generated dirs, etc.maxResults: cap on number of reported entries. Default 100. When reached, scanning stops and truncated becomes true — narrow scope with the filters above and retry.
Output modes (responseFormat)
"list" (default): one line per candidate (format below)."summary": aggregate counts for the WHOLE project — total, delete-safety split (deletable vs unexport-only), default-export count, and breakdowns by kind and by directory. On large repos the per-line list easily blows past the response size limit, so start with "summary" to see where dead code clusters, then narrow with entryPoints / excludeFilePatterns and switch to "list" for exact locations. (summary scans the whole project regardless of maxResults.)
Result format (list mode)
A bullet list of candidates with file:line:column, symbol name, declaration kind, a [default] tag for default exports, textHits=N, and sameFileRefs=N.
sameFileRefs — decides delete vs. unexport (read this first)
Every reported export is, by definition, unreferenced OUTSIDE its declaring file. sameFileRefs tells you whether it is still used INSIDE that file (declaration itself and re-export sites excluded), which determines the safe action:
sameFileRefs=0: not used anywhere, including its own file → truly dead, safe to delete the whole declaration (combine with textHits=0 for highest confidence).sameFileRefs=1+: used within its own file → only the export keyword is unnecessary. Remove export, but KEEP the declaration — deleting it breaks the in-file references.
Deleting every reported declaration blindly will break the build: the majority are often sameFileRefs=1+ (over-exported but internally used).
textHits — text-occurrence triage hint
textHits is the number of word-boundary occurrences of the export's name in OTHER source files (declaring file excluded — so it says nothing about same-file usage; use sameFileRefs for that):
textHits=0: no OTHER file mentions the name. Does NOT by itself mean deletable — still check sameFileRefs.textHits=1+: the name appears as a string literal, JSX tag, dynamic import().then(m => m.X), or comment. Verify with find_references_by_tsmorph before deleting. Short names (e.g. a, id) match incidentally — discount accordingly.
⚠ Package-level warnings
When a package that produced candidates publishes built output (see Known limitations), a ⚠ warnings block is prepended to the result (both list and summary modes) naming the package, its out-of-scan entry points, and how many candidates are affected. Those candidates are likely false positives.
Trailing line reports Scanned files: N and Truncated: bool.