Documentation
¶
Overview ¶
Package deadcode exposes the whole-program dead-code engine as a golangci-lint linter.
The analysis itself lives in the engine subpackage. This package is the adapter that makes a whole-program result fit go/analysis's per-package contract, and it is where the cost of that mismatch is paid.
How a whole-program result reaches per-package diagnostics ¶
golangci-lint gives a plugin one analyzer, run once per package, whose only output channel is analysis.Pass.Report. The adapter therefore runs the engine once behind a sync.Once, indexes the findings by the file that declares them, and each pass reports the findings belonging to the files it holds.
Three consequences follow, and none is avoidable within the plugin API:
- The engine loads the program itself, in addition to the load golangci-lint already performed, and reads more than golangci-lint does: it needs dependency syntax and type information where golangci-lint can often settle for export data. The cost scales with the whole dependency graph, and the reachability analysis after the load costs more again. It is work the run would not otherwise do, and it does not shrink when golangci-lint is asked for a narrow package pattern, because correct roots need the whole program.
- It is work every run does. golangci-lint caches issues per package, keyed on that package and its imports, while dead-code liveness flows the other way — from importers. A cached answer would go stale on an edit that never touches the package it is wrong about, so this linter opts out of that cache entirely and pays the full analysis on every invocation, including one where nothing changed.
- The engine's token.FileSet is not the pass's, because a second packages.Load builds its own. Findings are indexed by the file name the engine's loader opened, unadjusted by any //line directive, because that is the name a pass's token.File carries; the byte offset is the same in both, so a position is remapped by offsetting from the file's base. A finding in a file no pass holds is dropped, which is what scopes reporting to the packages the user asked golangci-lint to lint.
What the plugin cannot do ¶
golangci-lint runs within one module, so a repository of sibling modules is analyzed a module at a time and a reference crossing a module boundary is not seen as a use. Covering it would mean loading the modules as one program, which the plugin has no way to ask golangci-lint for.
That limit is not only one of coverage. As the engine documents, the set of loaded packages is an input to interface-participation verdicts: an interface declared in a sibling module is a dependency's rather than one of the analyzed packages', and a dependency's interface credits its implementations unconditionally. A per-module run therefore reports strictly fewer method findings than one spanning the repository, rather than a subset of the same answer.
Findings in a cgo package do not reach a report. The engine analyzes such a package, but cgo rewrites its files and the position a finding carries names the rewritten file in the build cache rather than the source the user wrote. No pass holds that name, so the finding is dropped. Pairing the source file name with the rewritten file's offset is not a fix: the offset is meaningless in the other file, so a wrong position would replace a missing one.
Nothing bounds how long the analysis takes. go/analysis carries no context, golangci-lint offers a plugin none of its own, and its run.timeout is a deadline the analysis runner discards and only consults afterwards to set an exit code.
Settings ¶
Because the plugin cannot read golangci-lint's own run configuration, build tags and test inclusion are settings on this linter rather than inherited from run.build-tags and run.tests.
An absent key and an empty list mean different things. Omitting patterns analyzes the whole module; writing patterns: [] names no packages at all and is rejected rather than quietly read as the default.
Suppression ¶
golangci-lint applies the nolint directive centrally, matched on the linter name, so a "nolint:deadcode" comment suppresses a finding and the engine does not interpret such directives itself. The directive is spelled out rather than written here: golangci-lint reads one out of any comment line, trimming leading slashes and spaces first, so writing it would suppress findings on whatever followed.
Index ¶
Constants ¶
const Name = "deadcode"
Name is what golangci-lint knows this linter by: the key under linters.settings.custom, the entry in linters.enable, and the token a //nolint:deadcode directive must carry.
Variables ¶
This section is empty.
Functions ¶
func New ¶
func New(settings any) (register.LinterPlugin, error)
New builds the plugin from the raw settings golangci-lint decoded out of the configuration file. It satisfies register.NewPlugin.
It decodes and validates, and touches nothing else. golangci-lint constructs every configured plugin whether or not the run enables it, and a failure here aborts configuration loading — so anything that can fail on an unrelated command, listing linters or formatting among them, has to wait until the analysis actually runs.
Types ¶
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin adapts the whole-program engine to golangci-lint's per-package linter contract. It holds the single analysis the whole run shares.
func (*Plugin) BuildAnalyzers ¶
BuildAnalyzers returns the analyzer this linter runs, alongside the one that keeps its results from being cached.
func (*Plugin) GetLoadMode ¶
GetLoadMode asks for syntax only. The analyzer reads nothing but file names and positions from the pass — the engine does its own typed load — so making golangci-lint type check on this linter's behalf would be wasted work.
type Settings ¶
type Settings struct {
// Patterns are the package patterns to analyze, resolved against the module
// root rather than the working directory. Omit the key to analyze ./..., which
// is the whole module; an explicitly empty list is rejected rather than read
// as the default, because a list written out means the packages in it.
//
// Narrowing this does not narrow what is reported — reporting is scoped
// to what golangci-lint was asked to lint — it narrows what the analysis can
// see, and so risks calling a live declaration dead. Set it only when one
// module holds several independent programs.
Patterns []string `json:"patterns"`
// BuildTags are extra build tags. golangci-lint's run.build-tags cannot
// be read from a plugin, so it has to be repeated here.
BuildTags []string `json:"build-tags"`
// Tests includes test code: test binaries become call-graph roots
// and test-only references count as uses. Defaults to true, matching
// golangci-lint's run.tests, which this cannot read.
Tests *bool `json:"tests"`
// API are package patterns whose exported surface consumers reach.
API []string `json:"api"`
// APIExempt is the declaration kinds an API package exempts: func, method,
// type, const, var, or interface-method. Defaults to method when API is set.
// Requires API.
APIExempt []string `json:"api-exempt"`
}
Settings is the decoded linters.settings.custom.deadcode.settings block. Decoding rejects keys it does not know, so an unrecognized key fails the run.