UnusedSymbolAnalyzer 1.0.4
dotnet tool install --global UnusedSymbolAnalyzer --version 1.0.4
dotnet new tool-manifest
dotnet tool install --local UnusedSymbolAnalyzer --version 1.0.4
#tool dotnet:?package=UnusedSymbolAnalyzer&version=1.0.4
nuke :add-package UnusedSymbolAnalyzer --version 1.0.4
UnusedSymbolAnalyzer
A .NET/Roslyn CLI that scans a solution and finds public classes, methods, and
const/static readonly fields that are never referenced anywhere in the solution —
candidates for narrowing down to internal. Goes beyond CA1515's type-level check by
working at the method/const level with real solution-wide reference counting via
SymbolFinder.FindReferencesAsync.
Requirements
- .NET SDK 10.0 or later must be installed on any machine that runs this tool
(not just the runtime). The tool uses
Microsoft.Build.Locatorto findMSBuild.dllinside an installed SDK in order to open solutions/projects — a bare runtime install won't have that. - The analyzed solution can target any TFM (e.g.
net8.0,net48) — Roslyn's syntax/semantic analysis is TFM-agnostic and the .NET 10 SDK can restore/evaluate older-TFM projects without needing that older SDK installed. Exception: if the analyzed solution has aglobal.jsonpinning an exact SDK version with"rollForward": "disable", that specific SDK must be present.
Install
Published on nuget.org:
# global
dotnet tool install --global UnusedSymbolAnalyzer
# local (pinned per-repo via a tool manifest, good for sharing with teammates)
dotnet new tool-manifest
dotnet tool install --local UnusedSymbolAnalyzer
Or build and pack locally from source:
dotnet pack src/UnusedSymbolAnalyzer.Cli -c Release -o ./nupkg
dotnet tool install --global --add-source ./nupkg UnusedSymbolAnalyzer
Usage
unused-symbols --solution MySolution.sln [--format console|json|markdown] [--config analyzer.config.json]
(--solution also accepts the newer .slnx format.)
Progress/diagnostic output (project count, MSBuild workspace-open failures) goes to
stderr; the report itself goes to stdout, so --format json output is safe to pipe.
--dump-symbols prints every collected public symbol (used and unused, with reference
counts) rather than the filtered unused-only report — useful for sanity-checking why a
symbol was or wasn't flagged.
JSON report schema
[
{
"DisplayName": "MyLib.Widget.NeverCalledMethod()",
"Kind": "Class | Method | Field",
"ProjectName": "MyLib",
"FilePath": "C:\\...\\Widget.cs",
"Line": 12,
"Category": "Unused"
}
]
Only genuinely unused symbols (Category: "Unused") appear in the default report;
whitelisted symbols are excluded entirely (use --dump-symbols to see them with their
whitelist category instead).
Whitelist configuration (analyzer.config.json)
Reflection-based frameworks (ASP.NET Core action invocation, DI container resolution, Quartz.NET job scheduling, ORM entity mapping, JSON/XML serialization) reference members in ways Roslyn's static reference search can't see, which would otherwise produce a pile of false positives. The following are auto-whitelisted and excluded from the unused report, tagged with the matching category instead:
| Category | Rule |
|---|---|
TestProject |
Symbol's project name contains one of TestProjectNamePatterns |
InterfaceOrOverride |
Symbol is an override, or implements an interface member (explicit or implicit) |
AttributeWhitelist |
Symbol (or its containing type) carries an attribute named in AttributeNames |
BaseTypeWhitelist |
Symbol's containing type derives from / implements a type named in BaseTypeNames |
NamespaceWhitelist |
Symbol's namespace starts with a pattern in NamespacePatterns |
Default config (used when no analyzer.config.json is found):
{
"BaseTypeNames": ["Controller", "ControllerBase", "IJob"],
"AttributeNames": ["Obsolete", "UsedImplicitly"],
"NamespacePatterns": [],
"TestProjectNamePatterns": ["Tests", "Test"]
}
Place a project-specific analyzer.config.json next to where you run the tool (or pass
--config <path>) to add your own base classes (e.g. an NHibernate entity base class),
attributes, or namespace patterns.
Note: BaseTypeNames matches by simple type name only (not fully-qualified), so it's
namespace-agnostic — convenient for matching e.g. any Controller regardless of which
Controller base you're actually using, but it means an unrelated type that happens to
share the name would also get whitelisted.
Known limitations
- Static analysis only: reflection-based access that doesn't match any whitelist rule can still produce false positives.
- DI-container-registered interface implementations aren't specifically whitelisted beyond the general interface-implementation rule.
- Mapping-config-driven ORM usage (e.g. NHibernate XML/Fluent mappings) isn't parsed — whitelist by base class/attribute instead.
- Source-generated code is included in the compilation (so it doesn't break analysis) but isn't filtered out of the report by generated-file path patterns yet.
Versioning and distribution
Package version is derived automatically from git tags via
MinVer (e.g. tag v1.2.0 → package version
1.2.0; without a tag you get a 0.0.0-alpha.0.<height> pre-release version).
.github/workflows/publish.yml builds, tests, and packs on every v* tag push and
uploads the .nupkg as a workflow artifact. From there, pick whichever distribution
channel fits your network:
- Local folder / network share: download the artifact and
--add-sourceit, as shown in Install above. - Private feed (Azure Artifacts, private NuGet server):
dotnet nuget pushthe package to your feed. - GitHub Packages: push there and add the feed to
nuget.configon the consuming machine. - nuget.org: works if the target network allows outbound access to it.
For teams, prefer the local tool manifest approach (dotnet new tool-manifest +
dotnet tool install --local) and commit .config/dotnet-tools.json, so the tool
version is pinned per-repo and dotnet tool restore gets everyone onto the same version.
Development
dotnet build
dotnet test
samples/SampleSolution is a small fixture solution with deliberately unused/used/
whitelisted members, exercised by tests/UnusedSymbolAnalyzer.Tests.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.