Skip to content

Add a NuGet dependency analyzer (redundant & conflicting package references) — CLI + build #421

Description

@ChrisonSimtian

Summary

Add a NuGet dependency analyzer to Fallout that flags redundant and conflicting package references across a solution — available both as a CLI command (dotnet fallout :analyze packages) and as a build-consumable check with configurable severity.

This is conceptually a "snitch" (spectresystems/snitch) reimagined on top of machinery Fallout already owns — and positioned to do strictly more than snitch did.

Motivation

Large solutions accumulate <PackageReference> entries that are no longer necessary:

  • the package is already provided by a referenced project, or
  • the package is already pulled in as a transitive dependency of another package you reference, or
  • the same package resolves to different versions across projects/paths (NuGet silently unifies to the highest, which hides real drift).

Cleaning these up reduces csproj noise, makes the real dependency surface legible, and catches version drift early. Snitch solved part of this for the .NET ecosystem but is unmaintained and, notably, only ever detected project-reference redundancy despite its README claiming package-transitive detection — it never walked the NuGet package graph.

Fallout already has the pieces to do the full job (see "Why Fallout is well-positioned").

Proposed scope (first iteration)

Three detections:

  1. Project-reference redundancy — a direct PackageReference already provided by a referenced project (snitch's actual behavior).
  2. True package-transitive redundancy — a direct PackageReference that is already a transitive dependency of another package you reference (the thing snitch only claimed to do).
  3. Version conflict / divergence — the same package resolved at different versions across projects or transitive paths.

Out of scope for v1 (candidate follow-ups): pre-release flagging, dependency-graph export (dot/json), auto-fix/csproj rewriting.

CLI surface

dotnet fallout :analyze packages [<path>] [--tfm <moniker>] [--severity <level>] [--exclude <id>...]
  • <path> — a .slnx/.sln/.csproj; defaults to the solution/project in the working directory.
  • --tfm — restrict to one target framework (multi-targeted projects analyzed per-TFM otherwise).
  • --severitynone|trace|normal|warning|error — controls the log level findings are emitted at and whether the command returns a non-zero exit code (see below).
  • --exclude — package IDs to ignore (e.g. analyzers, intentionally-pinned transitives).

:analyze is introduced as a command namespace so future analyzers (:analyze deadcode, etc.) slot in alongside.

Build integration & configurable severity

The same analyzer is consumable from a build so a target can enforce hygiene in CI. Severity must be configurable as a log level, not just warn-vs-error:

  • emit findings at a chosen LogLevel (Trace/Normal/Warning/Error), and
  • optionally fail the build (treat Error as build-failing; Warning as advisory).

This maps onto the existing Fallout.Build LogLevel enum + Host.Warning/Host.Error and the build's success determination.

Why Fallout is well-positioned (reuse, not greenfield)

Need Existing asset
Resolved transitive package graph (per TFM) Fallout.Common.Tooling.NuGetPackageResolver — already reads project.assets.json and walks transitive deps via NuspecReader.GetDependencyGroups
Declared direct refs + PrivateAssets/AutoReferenced intent Fallout.ProjectModel (MSBuild evaluation)
Project→project edges Fallout.Persistence.Solution (SolutionProjectModel.Dependencies)
Graph + cycle/topo utilities Fallout.Core/Planning/Graph (Vertex<T>, Tarjan SCC, TopoSort)
Severity/logging Fallout.Build LogLevel + Host
Shared-lib-consumed-by-CLI-and-build precedent Fallout.Migrate + Fallout.Migrate.Analyzers

Data sources (both)

  • project.assets.json (post-restore) → authoritative resolved transitive graph + unified versions. Required for detections 2 & 3. Implies the target must have been restored; the command will say so if assets are missing.
  • MSBuild evaluation → declared direct refs + PrivateAssets/AutoReferenced so we don't recommend removing things that can't be removed or don't flow transitively.

Correctness guardrails (learned from snitch's edges)

  • Exclude PrivateAssets-suppressed refs from the "provider" set (they don't flow to consumers).
  • Exclude AutoReferenced / SuppressParent=All (SDK implicit refs) — can't be removed from csproj.
  • Analyze per-TFM; only recommend removal if redundant across all target frameworks.
  • Central Package Management aware (Directory.Packages.props, no inline versions) — versions come from the resolved graph.
  • Distinguish "safe to remove" from "removable but changes resolved version" (snitch's can- vs might-be-removed) — removing a higher pin silently downgrades.

Proposed layout

  • src/Fallout.NuGet.Analysis/ — core analyzer (project graph build + three detections + result model). No console/build coupling.
  • src/Fallout.Cli/Program.Analyze.cs:analyze packages command surface.
  • build target wiring (follow-up) — consume the core lib with a configurable LogLevel.
  • tests/Fallout.NuGet.Analysis.Tests/ — fixtures mirroring the snitch fixture style (chained project refs + transitive-providing packages).

Acceptance criteria (v1)

  • Core analyzer library with the three detections, unit-tested against fixtures.
  • dotnet fallout :analyze packages runs against a .slnx/.csproj, prints a categorized report, returns a severity-driven exit code.
  • Guardrails above implemented (PrivateAssets / AutoReferenced / per-TFM / CPM).
  • Clear messaging when restore is missing.
  • Build-integration hook designed (target wiring may land in a follow-up).

Implementation is being prototyped on a fork first and will be refined before landing here.

Metadata

Metadata

Labels

enhancementNew feature or requesttarget/2026Targets the 2026 calendar-version line (current). See ADR-0004.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions