analysis

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: AGPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package analysis finds functions that carry no direct unit test.

`go test -cover` measures statement coverage, which cannot distinguish a function that was tested from one that was merely executed on the way to somebody else's assertion. This package asks the stricter question: for each function declared in a package, does a TestXxx body actually mention it?

The mechanism is deliberately one question, asked once. For every identifier in a test body it consults go/types and keeps the ones that resolve to a function declared in the package under test. It never asks "is this a call?", which is an unbounded syntactic question — and so method values, method expressions, functions passed as arguments, deferred closures, range-over-func iterators, and generic instantiations all fall out for free rather than each needing a case of its own.

Index

Constants

View Source
const IgnoreDirective = "//tarp:ignore"

IgnoreDirective exempts the declaration it precedes. A reason is required — the escape hatch is what decides whether the tool is adoptable on a real codebase, and one sentence of justification is what keeps it from becoming a way to make the score go up.

//tarp:ignore -- talks to a live payment processor; covered by the e2e suite

Variables

View Source
var ErrNoGoFiles = platformerrors.New("no Go files found")

ErrNoGoFiles is returned when the requested patterns match no Go source at all — an empty directory reads as a perfect score otherwise, which would be a lie.

View Source
var ErrNotInModule = platformerrors.New(
	"no go.mod in that directory or any parent, and packages load in module mode: run `go mod init` there first",
)

ErrNotInModule is returned when the analyzed directory sits outside every Go module. The go command's own words for this are "directory prefix . does not contain main module or its selected dependencies", which names the symptom and leaves the cause to be guessed at.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Dir is the directory the patterns are resolved against. Empty means the
	// current working directory.
	Dir string
	// Patterns are go/packages patterns such as "./..." or a package path.
	// Empty means "./...".
	Patterns []string
	// Strictness is how close a reference must be to count. The zero value is
	// the strictest setting, which is the one worth defaulting to.
	Strictness Strictness
}

Config selects what to analyze and how strictly to grade it.

type DiagnosticError

type DiagnosticError struct {
	Diagnostics []string
}

DiagnosticError reports that the analyzed source could not be loaded and type-checked. The old implementation parsed with parser.AllErrors and happily analyzed broken source; go/types refuses, which is an improvement, so long as the refusal is legible.

func (*DiagnosticError) Error

func (e *DiagnosticError) Error() string

Error implements the error interface.

type Function

type Function struct {
	Package string `json:"package"`
	File    string `json:"file"`
	Name    string `json:"name"`
	// PackagePath is the import path Package is the clause name of. It is what
	// actually identifies a package — a module of any size holds several named
	// config — and so it is what per-package grading groups on. Left out of the
	// JSON with the rest of the renderer-only fields below.
	PackagePath string `json:"-"`
	// Path, EndLine, and Tested are carried on the Go value but left out of the
	// JSON: the wire shape lists only what is missing, and it names files the
	// way a person reads them. A caller that renders source instead — the
	// coverage view — needs to find the file on disk, know how far the
	// declaration extends, and have the verdict on every function rather than
	// only the failures.
	Path    string `json:"-"`
	Line    int    `json:"line"`
	EndLine int    `json:"-"`
	Tested  bool   `json:"-"`
}

Function identifies a single function declaration and says whether a TestXxx body references it directly. That is a weaker claim than the one the tool is named for: a reference is evidence that a test was written for the function, not that the test asserts anything. File is relative to the analyzed directory when it sits underneath it, so reports are stable across machines and check-outs.

type Package

type Package struct {
	// Path is the import path, which is the identity. Name is the package
	// clause, which is what a person reads.
	Path     string
	Name     string
	Declared int
	Tested   int
}

Package is one package's grade, for reports that span more than one.

func (Package) Score

func (p Package) Score() int

Score grades a single package on the same arithmetic as the whole report.

type Report

type Report struct {
	// Root is the module root the analyzed packages live under, absolute, or
	// empty when the target sits in no module. Like Function.Path it is carried
	// on the Go value and left out of the JSON: a renderer that has to state
	// where a file sits — SARIF, whose URIs are relative to a declared base —
	// needs a root to make paths relative to, and the analyzed directory is not
	// it. The wire shape is pinned verbatim in analysis_test.go.
	Root      string
	Functions []Function
	// Sources is every file the load behind this report read, keyed by the
	// import path of the package that named it and sorted within each package.
	// The paths are absolute, as go/packages reports them. Carried on the Go
	// value and left out of the JSON for the same reason as Root: it exists for
	// a renderer that has to open the source rather than describe it.
	//
	// An analysis is ~99.9% package loading, so the one thing worth handing a
	// caller is the loading it would otherwise repeat. The coverage view has to
	// turn the package-relative names in a cover profile into files it can read,
	// which is a question this list already answers. Keyed rather than a slice
	// of pairs so the field costs one word: Report is passed by value to its own
	// methods, and gocritic holds it to a size.
	Sources    map[string][]string
	Warnings   []string
	Strictness Strictness
}

Report is the outcome of an analysis run.

Functions is sorted by file and then by declaration line, so two runs over unchanged source produce byte-identical output.

func Analyze

func Analyze(ctx context.Context, cfg Config) (*Report, error)

Analyze loads the requested packages and reports which of their functions have no direct unit test.

func (Report) Declared

func (r Report) Declared() int

Declared is the number of functions the report holds to the standard.

func (Report) MarshalJSON

func (r Report) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler, so that anything holding a Report can hand it to a standard encoder. The bytes themselves come from platform-go's encoding package, which keeps the content type one decision made in one place; EncodeJSON returns exactly what json.Marshal would, with no trailing newline.

The directive below is tarp's answer to the one shape it cannot see: TestReportMarshalJSON asserts this method thoroughly and never writes its name, because json.Marshal reaches it by reflection. Every Stringer, driver.Valuer, and interface satisfied for a framework's benefit reads the same way, and the honest fix is a reason naming the test, not a looser rule.

func (Report) Packages

func (r Report) Packages() []Package

Packages groups the report by the package each function was declared in, sorted by import path so two runs over unchanged source agree.

Only packages that declared something appear: a package with nothing to grade would score a meaningless 100 and pad the table it is read from.

func (Report) Score

func (r Report) Score() int

Score is the percentage of declared functions carrying a direct test, truncated rather than rounded: two of three is 66%, and only a genuinely complete package reaches 100. A package that declares nothing scores 100.

func (Report) Tested

func (r Report) Tested() int

Tested is the number of declared functions carrying a direct test.

func (Report) Untested

func (r Report) Untested() []Function

Untested lists the functions with no direct test, in report order.

type Strictness

type Strictness uint8

Strictness selects how close a reference has to be to a declaration before it counts as a direct test. The dial only ever weakens: StrictnessFile is the default and the strongest claim the tool can make.

const (
	// StrictnessFile requires the reference to live in the declaring file's test
	// slot: Bar declared in foo.go must be referenced from foo_test.go or
	// foo_internal_test.go.
	StrictnessFile Strictness = iota
	// StrictnessPackage accepts a reference from any _test.go in the package.
	StrictnessPackage
	// StrictnessAny accepts a reference anywhere in any _test.go, test helpers
	// included.
	StrictnessAny
)

func ParseStrictness

func ParseStrictness(raw string) (Strictness, error)

ParseStrictness converts a flag value into a Strictness.

func (Strictness) String

func (s Strictness) String() string

String implements fmt.Stringer, returning the flag spelling of the level.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL