Warning.UnusedImport
Flags re-export passthroughs (export { Bar } from './bar') when no other file in the project imports Bar from the re-exporter. The classic case single-file linters miss because the file's own re-export counts as "using" the symbol locally.
Why
ts
// barrel.ts
export { Bar } from './bar'; // tsc: "Bar is used (in the export)"
// reality: nobody imports Bar from barrel.tstsc --noUnusedLocals and ESLint no-unused-vars treat the re-export as a use site, so this kind of dead barrel entry survives indefinitely. Cofferdam sees the whole project graph and can confirm whether anyone downstream actually imports the re-exported name.
What gets flagged
export { Bar } from './m'when no other file hasimport { Bar } from './barrel'(orimport Bar from './barrel'for default re-exports).- Default re-exports (
export { default } from './m',export { default as foo } from './m') when nobody imports the default (or the renamed name) from the re-exporter.
What's not flagged
- Plain unused imports inside a single file — tsc/ESLint already do this well, and we deliberately don't duplicate.
- Star re-exports (
export * from './m') — they have no specific name to track. If the re-exporter file itself is unused, that'sDesign.OrphanExport's job. - Type-only re-exports — same false-positive surface as
Refactor.DeadExport. Will be enabled with type-aware analysis. - Re-exports from a file reached by
import * as nssomewhere — the namespace import opaquely consumes everything. - Side-effect imports (
import './polyfill') — they have no name bindings.
Limitations
- Re-export chain depth: we look one hop at a time. If
a.tsre-exports fromb.tsre-exports fromc.tsand onlyc.ts's origin export is consumed (skipping the barrel layers), each re-exporter shows up separately. Walking through chains for attribution is a planned enhancement. - Dynamic imports of the re-exporter module DO count as namespace consumption (matching real runtime semantics), so a single
import('./barrel').then(m => m.Bar)keeps every re-export off the list.