Skip to main content
roe dead-code reports code that’s unreachable from your solution’s entry points. Entry points are never flagged themselves, and reachability is traced outward from them.

What counts as an entry point

  • static Main and top-level-statement files
  • ASP.NET controllers (*Controller / ControllerBase) and their actions, Startup conventions
  • Test methods and fixtures ([Fact], [Theory], [Test], [TestMethod], …)
  • Any declaration carrying a non-inert attribute ([HttpGet], [JsonProperty], [UsedImplicitly], custom markers — everything except [Obsolete]-class metadata)
  • Reflection-scan contracts: implementations of a type used as a lone generic argument in a call (GetExports<IImageProvider>(), AddHostedService<W>())
  • Public API surface of NuGet-packable projects, of every non-test project when the workspace has no executable (“library mode”), and of any project named with --library/libraryProjects (a project consumed outside the workspace, e.g. a Unity project referencing the built DLL, regardless of what executables live alongside it)
  • Declarations in generated files (*.g.cs, *.Designer.cs, <auto-generated> headers, obj/) — reference-only, never flagged

Adding your own roots

When code is only reached in ways roe can’t see — string-based reflection, an external plugin host — declare it as a root with the --root flag or the roots config field, using the symbol’s fully-qualified name:
roe.json
When a whole file is consumed in ways roe can’t see — a plugin host loading it by path, an external tool reading it directly — name the file with the entryPoints config field instead of listing every symbol in it. Patterns are globs resolved relative to the config file’s directory (a trailing / matches the whole directory); every declaration in a matching file becomes a root, so the file and everything it references count as used:
roe.json
A pattern that matches no file at all is reported as a note, so a stale path can’t silently drop the protection it promised. See Configuration for the full rules.

How detection works

Under the hood, roe dead-code runs a five-step pipeline:
  1. Discover — parses .sln and .csproj files and walks the source tree, gitignore-aware, harvesting obj/ generated sources as reference-only inputs.
  2. Extract — parses every .cs file in parallel with tree-sitter, collecting declarations and references.
  3. Resolve — merges partial types and overloads into one symbol table; references resolve by namespace-aware, conservative name matching.
  4. Mark and sweep — walks the reference graph outward from the entry points. A member only counts as used when its name is referenced from reachable code and its containing type is reachable.
  5. Report — unreachable symbols become findings; a file whose every declaration is dead is reported once as a dead file.
The whole tool is tuned so that when in doubt, code is marked used — a missed finding is cheap, a wrong one is a bug. See Known limitations for the cases that name-based, static analysis can’t see.