deadset-go

Deterministic whole-program dead-code analysis for a Go module and the consumers it declares.
⚠️ Pre-release software
deadset-go implements version 0.1.0 of the deadset contract and has not shipped its first analysis. Today the binary answers version and refuses a --fix flag; the other verbs below are the contract's and arrive with the analysis. The report shape, the exit codes and the configuration keys can change until 1.0.
What it does
deadset-go loads a Go module with its tests, type-checks it and reports the declarations nothing uses: a function nobody calls, a struct field nothing reads, an exported symbol no consumer imports, an interface no value is converted to. Every verdict comes from type information and the reference graph, never from a text search, so two runs over one tree produce one report.
Three properties separate it from a per-package linter:
- Consumers are part of the program. A library's exported API is dead only when no declared consumer uses it. Name the repositories that import the module and the analysis loads them into one whole-program graph, so an export a downstream repository calls is never reported.
- Exemptions are computed, not configured. A method that satisfies an interface a value of its type is converted to, a
main, an init, a test function, a symbol a go:linkname directive names: each is held live by a documented exemption class, and print-retained lists every symbol an exemption held back and the class that held it.
- A cascade is one finding. When a dead function is the only caller of three more, the report names the root and counts what falls with it, so the deletion total is known before the edit.
It also reports over-visibility: an exported symbol only its own package uses, which can be unexported without a behavior change.
The output is a report, never an edit: a JSON document, one path:line:col text line per finding, or SARIF 2.1.0 for code-scanning upload. Every verb refuses a --fix flag with exit 2.
Quick start
go install github.com/cplieger/deadset-go/cmd/deadset-go@latest
deadset-go version
Installing needs Go 1.27 or later, and running needs the go toolchain on PATH, which loads the packages. deadset-go runs no language server and builds with cgo disabled, so a host with no C toolchain runs it.
Commands
| Verb |
What it does |
analyze |
Loads the module and its declared consumers, writes the report, exits with the verdict |
explain |
Says why a symbol is dead, why it is live, or why it was not reported |
print-config |
Prints the resolved configuration with the source of every setting |
print-roots |
Prints the resolved root set |
print-retained |
Prints every symbol an exemption held back, with the classes that held it |
describe |
Prints the analyzer's name, version, contract version and conformance record as JSON |
version |
Prints the analyzer version and the contract version |
Exit codes follow the contract: 0 clean, 1 findings, 2 a usage error or a requested source edit, 3 a load or type-check failure, 4 a finding whose cross-language reference is unresolved.
How it relates to deadcode
golang.org/x/tools/cmd/deadcode answers a different question, and running both is worth it. deadcode builds a call graph from each main with rapid type analysis, which resolves calls through interfaces by tracking the types that reach run time, and reports the functions no call path reaches. deadset-go covers the symbol kinds a call graph has no node for (types, fields, constants, variables, interface methods, type parameters), treats a library's declared consumers as part of the program, and records on each finding which relation produced it: a reference count of zero, or unreachability from the roots. Run deadcode for the functions and deadset-go for everything else.
Security
deadset-go reads source and writes a report. It edits no file, and every verb refuses a --fix flag, or any flag whose name contains fix, with exit 2. It spawns no analysis process other than the Go toolchain's own go list, which resolves the module's dependencies as go build does, and it opens no network connection of its own: a module download happens only where go list would download, so a run against a populated module cache stays offline.
- deadset-spec: the contract and the conformance corpus deadset-go implements and passes before each release.
- deadset-ts: the same analysis for TypeScript and JavaScript. Neither analyzer can analyze the other's language: the TypeScript 7 compiler module
github.com/microsoft/TypeScript/tsc keeps every analysis package under internal/, its programmatic surface is a TypeScript client over a private protocol, and no port of Go's type checker to JavaScript exists.
- deadset: runs both analyzers over a repository holding both languages, resolves the references that cross the language boundary and merges the reports into one.
Dependencies
deadset-go allows itself two dependencies: the Go standard library and golang.org/x/tools, which supplies the package loader and the type information no reimplementation can. go version -m on a released binary lists nothing else. Renovate keeps the pins in go.mod current.
Contributing
Issues and pull requests are welcome. The general guidelines live in cplieger/.github.
Disclaimer
This project is built with care and follows security best practices, but it is intended for personal / self-hosted use. No guarantees of fitness for production environments. Use at your own risk.
This project was built with AI-assisted tooling using Claude, GPT, and Kiro. The human maintainer defines architecture, supervises implementation, and makes all final decisions.
License
GPL-3.0-or-later. See LICENSE.