goago

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: Apache-2.0, MIT Imports: 19 Imported by: 0

README

goago

CI Go Reference Go Report Card License

One way to write Go, no matter who writes it.

Pronounce goago as go ago. The name keeps three meanings:

  • agent Go: the Go that coding agents may write.
  • a Go: one selected way to write Go for every human developer and coding agent in a project.
  • Go ago: an earlier, smaller Go. It recalls the simpler language that inspired the project, but it does not copy one past Go release.

Go's original design called for one way to write a piece of code. Rob Pike later made the same point:

Go code looks and works the same regardless of who's writing it.

Rob Pike, What We Got Right, What We Got Wrong

gofmt gives Go one format. goago enforces a project's selected way to write Go. Human developers, coding agents, and CI use the same policy.

goago rejects selected legal Go constructs. It does not add syntax, rewrite code, or change semantics. Code that passes goago is ordinary Go that builds with the stock toolchain.

$ go tool goago ./...
internal/store/index.go:42:2: naked return in indexAll; name the values you are returning (no-naked-return)
internal/store/index.go:88:9: new() takes a type, not an expression (no-new-expr)
2 violations

Read the design case for the project boundary and evidence.

Previously named ago. See migration to update an existing installation.

Adopt goago in a Go repository

goago requires Go 1.25 or later and a Go module.

  1. Add goago as a module tool dependency.

    go get -tool github.com/agentstation/goago/cmd/goago@latest
    

    This command pins the goago version in go.mod. It records module checksums in go.sum.

  2. Check the module.

    go tool goago ./...
    

    A clean run prints nothing and exits with status 0.

The Go module now owns the goago version. Developers, coding agents, and CI can run go tool goago without a global installation or a PATH change.

goago does not require a config file. The pinned goago version supplies the default rule policy. Add .goago.yml only when the project needs a different policy. Run the same go get -tool command later to upgrade goago deliberately.

Other installation methods

Install a global command when one pinned repository does not own the use:

go install github.com/agentstation/goago/cmd/goago@latest

On macOS or Linux with Homebrew:

brew trust --cask agentstation/tap/goago
brew install --cask agentstation/tap/goago

Release archives, checksums, and software bills of materials are available on the release page.

Make the policy automatic

Use these repository files to give each contributor the same command and policy:

File Purpose When needed
go.mod and go.sum Pin the goago command and its module graph. Always
.goago.yml Change or record the built-in rule policy. Only for a custom policy
AGENTS.md Tell coding agents when and how to run goago. Repositories that use coding agents
CI workflow Reject a change that violates the policy. Repositories that enforce goago

Add this instruction to the adopting repository's AGENTS.md:

Run `go tool goago -stale-ignores -format json ./...` after each Go change.
Fix findings in source. Do not add or change `.goago.yml` only to make the run
pass. Do not add a suppression only to make the run pass. Exit status 2 means
the check was incomplete.

Use the same pinned tool in GitHub Actions:

- uses: actions/setup-go@v7
  with:
    go-version: stable
- run: go tool goago -format github ./...

CI remains the policy boundary. Agent instructions and the optional goago Agent Skill improve the local repair loop.

Run goago

go tool goago ./...                    # default rule set, current module
go tool goago -list                    # show every rule and which are on
go tool goago -explain no-goto         # print one complete rationale
go tool goago -all ./...               # run every rule
go tool goago -tests ./...             # include _test.go files
go tool goago -stale-ignores ./...     # report unused suppressions

Package arguments are go/packages patterns. With no arguments, goago checks ./....

goago always skips vendor/ and testdata/. Third-party code is not yours to restrict.

Exit status Meaning
0 The run completed with no findings or stale ignores.
1 The run found a rule violation or stale ignore.
2 goago could not complete a meaningful run.

Configure the rule policy

Configuration is optional. With no config file, goago runs the default rules from the version pinned in go.mod.

Create a minimal policy only when the project needs one:

go tool goago -init

The command writes .goago.yml at the nearest go.mod or go.work root. It refuses to create a second policy when a parent policy already applies.

version: 1
enable:
  - default
  - no-init-func

disable:
  - no-goto

tests: false
exclude:
  - "*.pb.go"
  - third_party/*

enable accepts rule names and the meta-names default and all. disable wins over enable. Command flags -rules and -all override the file.

Choose the policy form that matches the project:

Form Upgrade behavior
No .goago.yml Use the defaults in the pinned goago version.
enable: [default] Record a policy file and use the defaults in the pinned version.
Explicit rule names Keep the named rule set until the project edits the file.

goago matches each exclude pattern against three path shapes:

  • the complete slash-separated path.
  • each path element.
  • each leading path prefix.

Thus, *.pb.go matches a file name, generated matches that directory at any depth, and third_party/* matches that subtree.

Unknown keys and unknown rule names stop the run. A policy typo cannot disable a rule silently. Use -config path to name a file. Use -no-config to ignore all policy files. The JSON Schema supplies editor validation. goago also accepts unversioned files created by v0.1.

Fix or suppress a finding

Fix source code when the selected policy applies. Each finding includes the rule name. Run go tool goago -explain <rule> for the full rationale and rule boundary.

Use a suppression only when the local construct is a justified exception:

//goago:ignore no-goto -- hand-written state machine, see docs/parser.md
goto retry

The directive applies to the next line. A top-level //goago:ignore-file directive applies to its file. Both forms accept a comma-separated rule list or *.

Every suppression must name a known rule and include a -- reason. An invalid directive suppresses nothing, and no-invalid-ignore reports it. Run with -stale-ignores to find a suppression that no longer covers a finding.

Machine contract for coding agents

goago exposes policy and results as stable data. A coding agent does not need to parse this README.

Discover the active rules:

go tool goago -list -format json

The document includes a schema version and the resolved policy source. It also reports the config path, test setting, and exclude patterns. Each rule entry includes its name, analyzer ident, summary, rationale, default and active state, Go release boundary, severity, and documentation URL.

policy.ruleSource is built-in, config, or flags. configDisabled is true when the command used -no-config.

Read findings and incomplete-run errors:

go tool goago -stale-ignores -format json ./...
{
  "schemaVersion": 1,
  "version": "v0.1.1",
  "rules": ["no-dot-import", "no-goto", "no-naked-return"],
  "findings": [
    {
      "rule": "no-naked-return",
      "severity": "error",
      "message": "naked return in indexAll; name the values you are returning",
      "file": "internal/store/index.go",
      "line": 42,
      "column": 2,
      "endLine": 42,
      "endColumn": 8,
      "docURL": "https://github.com/agentstation/goago/blob/main/docs/rules.md#no-naked-return"
    }
  ],
  "staleIgnores": [],
  "errors": []
}

goago sorts and deduplicates findings. The same version, policy, and source tree produce the same JSON document. Existing JSON fields keep their names and meanings. Later versions can add fields.

A package load or parse failure appears in errors. goago continues with each package that it can analyze. Exit status 2 means that no usable result was available, so an empty finding list is not a clean result.

Optional Agent Skill

The optional skill teaches compatible coding agents the discovery, repair, suppression, and verification loop:

gh skill install agentstation/skills goago --agent codex --scope project

Change --agent for another supported host. The skill guides the local repair loop. The pinned Go tool, optional policy, agent instruction, and CI workflow remain authoritative.

Rules

Seven rules are on by default. Their constructs have direct replacements. Six rules are off by default because they encode a project-specific choice.

Rule Default Restriction
no-self-referential-constraints on A generic type cannot name itself in its own type parameter list.
no-new-expr on new accepts a type, not an expression value.
no-generic-methods on Methods cannot declare type parameters.
no-naked-return on A return in a function with named results must name its values.
no-dot-import on Imports must keep a package qualifier.
no-goto on goto is forbidden.
no-invalid-ignore on Each suppression must name known rules and give a reason.
no-f-bounded-constraints off A type parameter cannot appear inside its own constraint.
no-generic-decls off Functions and types cannot declare type parameters.
no-redundant-short-decl off Use var for a short declaration in plain statement position.
no-embedded-field off Struct fields must have names.
no-init-func off Packages cannot declare func init().
no-blank-import-outside-main off Only package main can use a blank import.

The rule reference gives the rationale, replacement, evidence, and non-findings for each rule. go tool goago -list -format json carries the same rule catalogue in machine-readable form.

Integrations

GitHub output and SARIF

Use workflow-command output for inline pull request annotations:

go tool goago -format github ./...

Use SARIF 2.1.0 for GitHub code scanning or another SARIF consumer:

- run: go tool goago -format sarif ./... > goago.sarif
  continue-on-error: true
- uses: github/codeql-action/upload-sarif@v4
  with:
    sarif_file: goago.sarif
Go analysis drivers

Every rule is a *analysis.Analyzer. A custom analysis command can compose them with multichecker:

package main

import (
	"github.com/agentstation/goago"
	"golang.org/x/tools/go/analysis/multichecker"
)

func main() {
	multichecker.Main(goago.Analyzers()...)
}

A binary built with multichecker supports go vet -vettool. The shipped cmd/goago command uses its own policy, JSON, suppression, and exit contracts. It is not a vet tool.

Library callers can inspect rules or run the checker directly:

rules := goago.Rules()
rule, ok := goago.Lookup("no-goto")
report, err := goago.Check(goago.Options{Patterns: []string{"./..."}})

See the package documentation for the complete API.

golangci-lint

goago ships as a golangci-lint module plugin. Add it to .custom-gcl.yml:

version: v2.12.2
plugins:
  - module: github.com/agentstation/goago
    import: github.com/agentstation/goago/plugin/golangci
    version: latest

Run golangci-lint custom, then enable the goago custom linter in .golangci.yml. Pin the plugin version before you commit the configuration.

Project

License

goago is available under either license, at your option:

The dual-license grant and contribution terms apply to the repository.

Documentation

Overview

Package goago enforces one way to write Go across a codebase.

A project selects the Go constructs that it accepts. Developers and coding agents use the same rule policy, and CI enforces it.

goago only ever rejects language constructs. It never adds syntax, never rewrites code, and never changes semantics. Code that passes goago is ordinary Go that builds with the stock toolchain.

Every rule is a golang.org/x/tools/go/analysis.Analyzer. Run them through the goago command, compose them in an analysis driver, or load the golangci-lint module plugin.

See github.com/agentstation/goago/cmd/goago for the command.

Index

Constants

View Source
const (
	ConfigName    = ".goago.yml"
	ConfigNameAlt = ".goago.yaml"
)

ConfigName is the file goago looks for when the command line names no configuration. ConfigNameAlt is an equivalent spelling.

View Source
const ReportSchemaVersion = 1

ReportSchemaVersion is the current JSON report schema version.

Variables

View Source
var RuleNoBlankImportOutsideMain = register(Rule{
	Name:     "no-blank-import-outside-main",
	Summary:  `import _ "pkg" only allowed in package main`,
	Default:  false,
	Severity: Error,
	Rationale: `A blank import registers a side effect through the import graph, which makes
program behaviour depend on which packages the linker includes. Confining
blank imports to package main keeps that dependency explicit and puts it
where a reader looks for wiring.

Off by default because some driver-registration patterns legitimately need a
blank import from a library package.

Files with a _test.go suffix are exempt. A test that blank-imports a driver
registers it for that binary only.`,
	Analyzer: newAnalyzer("no-blank-import-outside-main",
		`reject import _ "pkg" outside package main`, checkBlankImport),
})

RuleNoBlankImportOutsideMain confines side-effect imports to package main.

View Source
var RuleNoDotImport = register(Rule{
	Name:     "no-dot-import",
	Summary:  `import . "pkg" is forbidden`,
	Default:  true,
	Severity: Error,
	Rationale: `A dot import makes every identifier in the file ambiguous as to origin, which
defeats both the reader and grep.

The standard library uses dot imports in production code in exactly one
pattern. The two type checkers dot-import a package that holds nothing but
error-code constants, referenced densely enough that qualifying every use
would be noise. That is the narrow case where this rule is wrong. Outside of
it, only tests and assembly generators excluded from the build use dot
imports in the standard library.`,
	Analyzer: newAnalyzer("no-dot-import", `reject import . "pkg"`, checkDotImport),
})

RuleNoDotImport forbids dot imports.

View Source
var RuleNoEmbeddedField = register(Rule{
	Name:     "no-embedded-field",
	Summary:  "struct embedding is forbidden. name your fields",
	Default:  false,
	Severity: Error,
	Rationale: `Embedding promotes methods and fields implicitly, so a type's method set stops
being visible at its declaration. Naming the field costs one selector per
use and makes the delegation explicit.

Off by default because embedding is thoroughly idiomatic Go. Turning this on
is a real break with house Go, not a tidy-up.

Interface embedding is not reported. Composing interfaces from smaller ones
is the language's intended mechanism and has no field to name.`,
	Analyzer: newAnalyzer("no-embedded-field",
		"reject embedded struct fields. Name them",
		checkEmbeddedField),
})

RuleNoEmbeddedField forbids struct embedding.

View Source
var RuleNoFBoundedConstraints = register(Rule{
	Name:     "no-f-bounded-constraints",
	Summary:  "a type parameter may not appear in its own constraint",
	Default:  false,
	Severity: Error,
	Rationale: `Reports F-bounded polymorphism. A type parameter appears inside its own
constraint, as in type Node[T Cloneable[T]] or func algo[A Adder[A]](x A) A.

This is not a version revert. Generics in Go 1.18 made this construct legal,
and the standard library uses it. It is the most abstraction-dense shape the
type system allows. An ordinary interface plus a concrete type usually
reaches the same shape, so some codebases choose to ban it. That is a
house-style decision. That is why the rule is off by default.

A constraint that refers to a sibling type parameter, such as
type Graph[N any, E Edge[N]], is not reported. The parameter does not appear
in its own constraint.`,
	Analyzer: newAnalyzer("no-f-bounded-constraints",
		"reject a type parameter that appears in its own constraint",
		checkFBoundedConstraints),
})

RuleNoFBoundedConstraints forbids F-bounded polymorphism.

View Source
var RuleNoGenericDecls = register(Rule{
	Name:     "no-generic-decls",
	Summary:  "no type parameters on any func or type declaration",
	Reverts:  "1.18",
	Default:  false,
	Severity: Error,
	Rationale: `Reverts generics entirely. This is the strictest rule goago offers. It is the
least likely to be right for a given codebase: it forbids type parameters on
any func or type declaration.

Reasonable for a codebase with no container libraries of its own. Hostile
otherwise. It does not stop you calling generic standard library functions
such as slices.Sort. Recognising a generic call requires resolving the
callee to its declaration in another package.`,
	Analyzer: newAnalyzer("no-generic-decls",
		"reject type parameters on any func or type declaration (reverts Go 1.18)",
		checkGenericDecls),
})

RuleNoGenericDecls forbids type parameters anywhere.

View Source
var RuleNoGenericMethods = register(Rule{
	Name:     "no-generic-methods",
	Summary:  "methods may not declare their own type parameters",
	Reverts:  "1.27",
	Default:  true,
	Severity: Error,
	Rationale: `Go 1.27 lets a method declare type parameters of its own. Rewrite any such
method as a package-level function that takes the receiver as its first
argument. The feature buys call-site chaining and costs a second place to
look for a type's operations.

A method whose receiver carries type parameters, such as
func (b *Box[T]) Get() T, is not affected. The rule reports only a method that
introduces new type parameters.

On a toolchain older than Go 1.27 this rule is dormant. The parser rejects the
construct before any analyzer runs.`,
	Analyzer: newAnalyzer("no-generic-methods",
		"reject methods that declare their own type parameters (reverts Go 1.27)",
		checkGenericMethods),
})

RuleNoGenericMethods forbids the Go 1.27 generic-method feature.

View Source
var RuleNoGoto = register(Rule{
	Name:     "no-goto",
	Summary:  "goto is forbidden",
	Default:  true,
	Severity: Error,
	Rationale: `Labelled break and continue cover the loop cases. A goto that jumps anywhere
else is control flow the reader has to simulate by hand.

This is a rule for application code, not a claim that goto is vestigial. The
standard library's goto statements concentrate in the type checkers, the
compiler, and the syscall exec paths. That code is either performance
critical or a hand-written state machine. If you write that kind of code,
turn this rule off rather than working around it.`,
	Analyzer: newAnalyzer("no-goto", "reject goto statements", checkGoto),
})

RuleNoGoto forbids goto.

View Source
var RuleNoInitFunc = register(Rule{
	Name:     "no-init-func",
	Summary:  "func init() is forbidden. initialize explicitly",
	Default:  false,
	Severity: Error,
	Rationale: `init runs before main in an order determined by the import graph, which makes
an initialisation failure hard to localise and hard to test. Explicit wiring
from main puts the order in one readable place.

Off by default because avoiding init entirely requires a wiring convention
that a linter cannot supply.`,
	Analyzer: newAnalyzer("no-init-func",
		"reject func init(). Prefer explicit initialization",
		checkInitFunc),
})

RuleNoInitFunc forbids package initialisation functions.

View Source
var RuleNoInvalidIgnore = register(Rule{
	Name:     "no-invalid-ignore",
	Summary:  "every //goago:ignore must name a known rule and give a reason",
	Default:  true,
	Severity: Error,
	Rationale: `A suppression that names no rule silences everything on the line, and a
suppression that names a misspelled rule silences nothing at all. Both fail
quietly, which is how a lint configuration rots.

This rule requires the full form:

	//goago:ignore no-goto -- hand-written state machine, see docs/parser.md

The reason is not decoration. It is the only record of why goago granted the
exception. A reviewer or a coding agent reads it before deciding whether the
exception still applies.

Turning this rule off is possible but self-defeating. It is the rule that
makes every other rule's escape hatch auditable.`,
	Analyzer: newAnalyzer("no-invalid-ignore",
		"reject //goago:ignore directives that name no known rule or give no reason",
		checkInvalidIgnore),
})

RuleNoInvalidIgnore keeps suppression directives honest.

View Source
var RuleNoNakedReturn = register(Rule{
	Name:     "no-naked-return",
	Summary:  "return statements must be explicit even with named results",
	Default:  true,
	Severity: Error,
	Rationale: `A bare return in a function with named results forces the reader to scroll up
to learn the returned values. It also silently returns whatever the result
variables hold at that point. Naming the values costs nothing and survives
later edits to the function body.

The rule checks function literals against their own result list, not the
enclosing function's. It reports a naked return inside a closure when the
closure itself declares named results.`,
	Analyzer: newAnalyzer("no-naked-return",
		"reject bare return statements in a signature with named results",
		checkNakedReturn),
})

RuleNoNakedReturn forbids bare returns from a signature with named results.

View Source
var RuleNoNewExpr = register(Rule{
	Name:     "no-new-expr",
	Summary:  "new() takes a type, not an expression",
	Reverts:  "1.26",
	Default:  true,
	Severity: Error,
	Rationale: `Go 1.26 lets the built-in new take an expression that supplies the initial
value, so new(yearsSince(born)) allocates and initialises in one step.

The form collapses declaration, initialisation, and address-taking into a
single expression. The two-line version with a named variable says the same
thing and reads left to right:

	age := yearsSince(born)
	p := &age

When type information is available the rule decides exactly whether the
argument is a type. Without it the rule falls back to syntax. It reports only
unambiguous expressions such as new(f(x)). A shadowed new or a variable that
looks like a type name is not reported.`,
	Analyzer: newAnalyzer("no-new-expr",
		"reject new() applied to an expression rather than a type (reverts Go 1.26)",
		checkNewExpr),
})

RuleNoNewExpr forbids the Go 1.26 new(expression) form.

View Source
var RuleNoRedundantShortDecl = register(Rule{
	Name:     "no-redundant-short-decl",
	Summary:  "use var except where := is syntactically required",
	Default:  false,
	Severity: Error,
	Rationale: `One way to introduce a variable instead of several.

The rule does not report := where var is a syntax error:

	switch t := x.(type) { ... }   // var is not allowed in a switch guard
	for i, v := range xs { ... }   // no var form exists
	select { case v := <-ch: ... } // var is not allowed in a receive clause

It reports := only in plain statement position, where var is a drop-in
replacement. Moving an if, for, or switch header declaration to a preceding var
is legal but widens the variable's scope. The rule does not report those
declarations. A blanket ban would force adding syntax to the language. The tool
will not do that.`,
	Analyzer: newAnalyzer("no-redundant-short-decl",
		"reject := in plain statement position, where var is a drop-in replacement",
		checkRedundantShortDecl),
})

RuleNoRedundantShortDecl forbids := where var is a drop-in replacement.

View Source
var RuleNoSelfReferentialConstraints = register(Rule{
	Name:     "no-self-referential-constraints",
	Summary:  "a generic type may not name itself in its own type parameter list",
	Reverts:  "1.26",
	Default:  true,
	Severity: Error,
	Rationale: `Go 1.26 lifted the restriction that a generic type may not refer to itself in
its type parameter list. The type Adder[A Adder[A]] interface{ Add(A) A } now
compiles. Before Go 1.26 the compiler rejected it as "invalid recursive
type".

This rule reports exactly that construct: the declared type's own name
appearing inside its own type parameter list. It does not report F-bounded
constraints written through a separate interface, such as
type Node[T Cloneable[T]]. Generics in Go 1.18 made that shape legal. It is
not what Go 1.26 changed. Use no-f-bounded-constraints for those.

Detection is syntactic and catches direct self-reference. Mutual recursion
across two declarations is not reported.`,
	Analyzer: newAnalyzer("no-self-referential-constraints",
		"reject a generic type that names itself in its own type parameter list (reverts Go 1.26)",
		checkSelfReferentialConstraints),
})

RuleNoSelfReferentialConstraints forbids the Go 1.26 self-reference.

View Source
var Version = "dev"

Version is the module version. Release builds can set it with a linker value. Commands built through go install or go tool read it from Go build information. A local source build without a version reports "dev".

Functions

func Analyzers

func Analyzers() []*analysis.Analyzer

Analyzers returns the analyzer for every rule, for use with multichecker, unitchecker, or a golangci-lint module plugin. Callers that want only the default set should filter with Rule.Default.

func DefaultNames

func DefaultNames() []string

DefaultNames returns the canonical names of the rules that are on when no configuration selects a rule set, sorted.

func ExampleConfig

func ExampleConfig() string

ExampleConfig returns the minimal policy that "goago -init" writes. The "default" meta-name follows the defaults in the pinned goago version.

func Names

func Names() []string

Names returns the canonical names of every rule, sorted.

Types

type Config

type Config struct {
	// Version names the config schema. Zero accepts an unversioned config from
	// ago v0.1. Version 1 is the current schema.
	Version int `yaml:"version"`
	// Enable lists rules to turn on. The special value "default" expands to
	// the default rule set and "all" expands to every rule. An empty list
	// means the default set.
	Enable []string `yaml:"enable"`
	// Disable lists rules to turn off after goago applies Enable.
	Disable []string `yaml:"disable"`
	// Tests reports whether goago checks _test.go files.
	Tests bool `yaml:"tests"`
	// Exclude lists path patterns. goago matches them with [path/filepath.Match]
	// against each path element and skips those packages.
	Exclude []string `yaml:"exclude"`
	// contains filtered or unexported fields
}

A Config is the on-disk rule policy for a repository. A committed policy means every developer, every CI job, and every coding agent enforces the same subset without extra instruction.

func LoadConfig

func LoadConfig(dir, path string) (*Config, error)

LoadConfig reads a config file. A path of "" searches dir and each parent directory for .goago.yml or .goago.yaml, including the legacy .ago names. Multiple policy files in one directory are an error. When the search finds no file it returns the default config and a nil error.

func (*Config) Enabled

func (c *Config) Enabled(overrides []string) []Rule

Enabled resolves the config into the set of rules to run, sorted in registration order. overrides, when non-empty, replaces Config.Enable and comes from the command line.

func (*Config) Path

func (c *Config) Path() string

Path returns the file goago loaded the config from, or "" for a default config.

func (*Config) Skip

func (c *Config) Skip(path string) bool

Skip reports whether a file path matches any exclude pattern.

goago matches a pattern three ways, because each way catches a different surprise. It matches the whole slash-separated path, so "*.pb.go" works. It matches each path element, so "generated" excludes any directory with that name at any depth. It matches each leading path prefix, so "third_party/*" excludes the whole subtree rather than only the files directly inside it.

func (*Config) Validate

func (c *Config) Validate() error

Validate reports whether every name in Enable and Disable refers to a rule this build knows about. The meta-names "default" and "all" are valid in Enable only. Errors carry no path prefix. A caller that read the config from a file should prefix them with its path.

type Finding

type Finding struct {
	// Rule is the canonical kebab-case rule name.
	Rule string `json:"rule"`
	// Severity is the rule's severity.
	Severity Severity `json:"severity"`
	// Message states what is wrong and what to write instead.
	Message string `json:"message"`
	// File is the path as the caller passed it, slash separated.
	File string `json:"file"`
	// Line and Column are 1-based, matching go vet and gofmt.
	Line   int `json:"line"`
	Column int `json:"column"`
	// EndLine and EndColumn bound the offending syntax. They equal Line and
	// Column when the rule reports a point rather than a range.
	EndLine   int `json:"endLine"`
	EndColumn int `json:"endColumn"`
	// DocURL points at the rule's section in the README.
	DocURL string `json:"docURL"`
}

A Finding is one reported violation, in the form the report writers consume. It is deliberately a flat value rather than an analysis.Diagnostic so that the JSON schema is stable across x/tools upgrades.

func (Finding) Position

func (f Finding) Position() string

Position renders the finding in the file:line:col form that editors and terminals link on.

type Format

type Format string

Format names an output encoding.

const (
	// FormatText is the vet-style file:line:col form that editors link on.
	FormatText Format = "text"
	// FormatJSON is a stable machine-readable document. It is the format to
	// use from a coding agent or any other program.
	FormatJSON Format = "json"
	// FormatSARIF is SARIF 2.1.0, which GitHub code scanning ingests.
	FormatSARIF Format = "sarif"
	// FormatGitHub is the GitHub Actions workflow-command form, which turns
	// findings into inline annotations on a pull request.
	FormatGitHub Format = "github"
)

Supported output formats.

func Formats

func Formats() []Format

Formats lists every supported output format.

func ParseFormat

func ParseFormat(s string) (Format, error)

ParseFormat validates a format name.

type Options

type Options struct {
	// Dir is the directory goago resolves patterns against. An empty Dir means
	// the process working directory.
	Dir string
	// Patterns are go/packages patterns such as "./..." or a list of .go
	// files. An empty Patterns means "./...".
	Patterns []string
	// Rules are the rules to run. An empty Rules means the default set.
	Rules []Rule
	// Tests reports whether goago analyzes _test.go files.
	Tests bool
	// Config supplies exclude patterns. It may be nil.
	Config *Config
	// ReportStaleIgnores reports //goago:ignore directives that suppressed
	// nothing.
	ReportStaleIgnores bool
}

Options control one Check run.

type Report

type Report struct {
	// SchemaVersion is the JSON report schema version.
	SchemaVersion int `json:"schemaVersion"`
	// Version is the goago version that produced the report.
	Version string `json:"version"`
	// Rules lists the canonical names of the rules that ran, sorted.
	Rules []string `json:"rules"`
	// Findings holds every violation, ordered by file, line, and column.
	Findings []Finding `json:"findings"`
	// StaleIgnores lists //goago:ignore directives that suppressed nothing.
	StaleIgnores []StaleIgnore `json:"staleIgnores"`
	// Errors holds load or parse failures. A non-empty Errors means the run
	// did not finish and Findings may omit violations.
	Errors []string `json:"errors"`
}

A Report is everything one goago run produced. The JSON encoding of this type is goago's machine-readable contract. Fields grow over time but existing fields keep their name and meaning.

func Check

func Check(opts Options) (*Report, error)

Check loads the named packages and runs the selected rules over them.

A load or type error does not abort the run. Check collects errors into Report.Errors and analysis continues over whatever parsed. A single unbuildable file cannot hide every finding in the repository.

func (*Report) Write

func (r *Report) Write(w io.Writer, format Format) error

Write encodes the report in the requested format.

type Rule

type Rule struct {
	// Name is the canonical kebab-case rule name, such as "no-goto".
	Name string
	// Summary is a single line shown by "goago -list".
	Summary string
	// Rationale explains why the rule exists and when to turn it off. It is
	// the analyzer's Doc body and the text an agent reads to decide whether a
	// violation is worth fixing or worth ignoring.
	Rationale string
	// Default reports whether goago enables the rule when no configuration
	// selects a rule set explicitly.
	Default bool
	// Reverts names the Go release that introduced the construct this rule
	// forbids, or "" when the construct is not tied to one release.
	Reverts string
	// Severity is the level reported for violations of this rule.
	Severity Severity
	// Analyzer enforces the rule.
	Analyzer *analysis.Analyzer
}

A Rule is one restriction, paired with the analyzer that enforces it.

Name is the canonical kebab-case name used by the goago command, by .goago.yml, and by //goago:ignore directives. The Analyzer.Name field is the same name with the hyphens removed, because go/analysis requires analyzer names to be valid Go identifiers. The goago command accepts either spelling.

func Lookup

func Lookup(name string) (Rule, bool)

Lookup finds a rule by its canonical kebab-case name or by its analyzer identifier spelling.

func Rules

func Rules() []Rule

Rules returns every rule goago knows about, in registration order.

func (Rule) DocURL

func (r Rule) DocURL() string

DocURL returns the rule-reference anchor that documents the rule.

type Severity

type Severity string

Severity classifies how strongly goago objects to a construct. Every rule currently reports at Error. The field exists so that report formats with a severity axis, such as SARIF, carry an honest value rather than a hardcoded one.

const (
	// Error marks a construct the rule forbids outright.
	Error Severity = "error"
	// Warning marks a construct the rule discourages.
	Warning Severity = "warning"
)

Severity levels.

type StaleIgnore

type StaleIgnore struct {
	// Rules lists the rule names the directive claimed to suppress.
	Rules []string `json:"rules"`
	// Reason is the text the author gave for the exception.
	Reason string `json:"reason"`
	// File, Line, and Column locate the directive.
	File   string `json:"file"`
	Line   int    `json:"line"`
	Column int    `json:"column"`
}

A StaleIgnore is a suppression directive that matched no finding. The command reports it separately from Findings because it is a maintenance signal rather than a Go-subset violation.

func (StaleIgnore) Position

func (s StaleIgnore) Position() string

Position renders the directive location in file:line:col form.

Directories

Path Synopsis
cmd
goago command
Command goago enforces one way to write Go across a codebase.
Command goago enforces one way to write Go across a codebase.
plugin
golangci
Package golangci registers goago as a golangci-lint module plugin.
Package golangci registers goago as a golangci-lint module plugin.

Jump to

Keyboard shortcuts

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