Skip to content

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.ts

tsc --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 has import { Bar } from './barrel' (or import 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's Design.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 ns somewhere — 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.ts re-exports from b.ts re-exports from c.ts and only c.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.

MIT License