antislop

package module
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 15 Imported by: 0

README

anti-slop-go

Opinionated go/analysis rules that reject low-evidence Go patterns.

This project is a Go companion to dmmulroy/anti-slop. The upstream project targets TypeScript and JavaScript through Oxlint. This project applies the same philosophy to Go.

The idea in one paragraph

Code generators produce code that compiles but carries no evidence. A type assertion with no stated invariant, an any parameter, or a map[string]any field moves a proof obligation from the author to the reader. These rules reject such patterns. The author must decode input at its I/O boundary, keep concrete types inside the program, and write a justification comment where an assertion is the correct tool.

The rules

Eight rules run by default.

ID Rule Reports
G01 safetyassert A panicking type assertion without a justification comment above it.
G02 nountypedmap A map with an any value type in a signature, a struct field, or a package variable.
G04 noanyreturn An any result.
G05 nolaundering A value that passes through any and comes back through an assertion.
G06 noadhoctypeswitch A type switch on an any value outside a package that decodes input.
G07 noreflect An import of reflect outside an allowed package. A test file that only calls reflect.DeepEqual stays clean.
G08 nomonkeypatch A test that rewires production code: an assignment to a package-level variable, an import of a runtime patching library, or a //go:linkname directive.
G10 noerrorassert A type assertion or a type switch on an error value, where errors.As answers the question.

Six rules are opt-in. The enable setting of the golangci-lint plugin turns one on.

ID Rule Reports
G03 noanyparam An any parameter outside the exemptions that the specification states.
G09 nointerfacereturn An interface result where every return statement builds the same concrete type.
G11 justifypanic A panic, an os.Exit, or a log.Fatal call outside main, init, and test files, with no justification comment above it.
G12 fullstructcomp A test that asserts a value field by field instead of one cmp.Diff.
G13 errsemantics A test that reads the text of an error instead of its identity.
G14 separategotwant A test that calls a helper taking the testing value inside an assertion argument, instead of binding got and want first.

The IDs come from the specification, where the rules stand in the order of their writing. Each table therefore skips the IDs of the other.

The specification carries the full contract of each rule, with examples and the measurements behind every decision:

  1. Overview: philosophy, goals, and scope.
  2. Rules: the rule catalogue.
  3. Implementation: architecture, distribution, and configuration.

A first run

The standalone binary needs no setup:

go run github.com/JacobJNilsson/anti-slop-go/cmd/antislop@latest ./...

This path reads no configuration file, so it runs every rule, the opt-in ones included. -justifypanic=false turns that one rule off. -errsemantics alone runs that one rule and no other. The same command satisfies the go vet -vettool contract: install it with go install github.com/JacobJNilsson/anti-slop-go/cmd/antislop@latest, then give -vettool the path of the installed binary.

Use with golangci-lint

golangci-lint loads these rules as a module plugin. A module plugin is Go code, so you build a golangci-lint binary that contains it. Put a .custom-gcl.yml in the root of your project:

version: v2.10.1
name: custom-gcl
destination: .
plugins:
  - module: github.com/JacobJNilsson/anti-slop-go
    import: github.com/JacobJNilsson/anti-slop-go/plugin
    version: v1.1.0

The import line is necessary. The registration lives in the plugin subpackage, not in the module root. The version line takes a tag of this repository. Take the newest one from the tag list.

Run golangci-lint custom in that directory. The command clones golangci-lint, adds this module, and writes a custom-gcl binary. It needs network access, git, and a Go toolchain that satisfies the go directive of this module (see go.mod; Go 1.26 today).

Then configure the linter in .golangci.yml:

version: "2"
linters:
  enable:
    - antislop
  settings:
    custom:
      antislop:
        type: module
        description: Rejects low-evidence Go patterns.
        original-url: github.com/JacobJNilsson/anti-slop-go
        settings:
          boundary-packages:
            - example.com/app/internal/ingest
          reflect-allow:
            - example.com/app/internal/codec
          fullstructcomp-min: 3
          fullstructcomp-maxignore: 5
          errsemantics-equality: true
          test-packages:
            - example.com/app/internal/suite
          enable:
            - noanyparam
            - nointerfacereturn
            - justifypanic
            - fullstructcomp
            - errsemantics
            - separategotwant
          disable:
            - nountypedmap

Twelve points about this file:

  • type: module is necessary. Without it, golangci-lint looks for a shared object file.
  • antislop joins the standard group of linters, so a configuration that keeps the default linters.default: standard runs it without the linters.enable entry. The entry becomes necessary when the configuration sets linters.default: none. The example keeps it, because it states the intention.
  • All rules arrive as one linter named antislop. You select the individual rules with the plugin's own enable and disable settings, not with linters.enable. An unknown rule name in either plugin setting stops the run.
  • disable drops a rule from the default set, which holds the eight rules of the first table above. A configuration that disables every rule is legal, and the linter then reports nothing. enable turns on an opt-in rule from the second table. A name that is on by default stops the run, because enable would do nothing for it.
  • The standalone binary and go vet -vettool read none of this file. The section "A first run" states what they read instead.
  • Three settings name packages by path pattern. A pattern matches the whole import path: * holds inside one segment, ... crosses a slash, and a pattern that ends in /... names the package above it as well. The standalone binary takes the same patterns in a flag, as a comma-separated list or a repeated flag. An unknown settings key stops the run, so this file names only the keys that a rule reads today.
  • boundary-packages names the packages that decode input, which rule noadhoctypeswitch (G06) reads. A type switch on an any value is the work of such a package, so the rule accepts every one of them there. The standalone flag is -noadhoctypeswitch.boundary.
  • reflect-allow names the packages that may import reflect, which rule noreflect (G07) reads. The standalone flag is -noreflect.allow.
  • fullstructcomp-min names the number of distinct fields of one value that a report of rule fullstructcomp (G12) needs. The default is 2. A project that meets the mid-flow checkpoint shape, where each step of a scenario asserts the one field it changed, raises the number. The standalone flag is -fullstructcomp.min.
  • fullstructcomp-maxignore sets the number of cmpopts.IgnoreFields names that a comparison of rule fullstructcomp (G12) may need. The rule counts those names. It reports no group above the setting, because such a fix states more than the assertions it replaces. The default is 5. A project that wants every checklist reported sets a high number. The standalone flag is -fullstructcomp.maxignore.
  • errsemantics-equality is a boolean, and rule errsemantics (G13) reads it. It adds a report for a comparison of an error message against a string, such as err.Error() == "..." and the EqualError assertion of testify. The default is false, because a package that tests its own message text writes that form. The standalone flag is -errsemantics.equality.
  • test-packages names the packages that serve tests and hold no file whose name ends in _test.go. A shared suite that a TestMain function starts is such a package. Six rules must decide whether a file is a test file, and this one key answers for all six. In a named package, noreflect (G07) gives the reflect.DeepEqual allowance of a test file. nomonkeypatch (G08) reads the assignments as test code, and justifypanic (G11) asks for no justification comment. fullstructcomp (G12), errsemantics (G13), and separategotwant (G14) read no such package today, so an entry adds findings for those three. The standalone flags are -noreflect.testpackages, -nomonkeypatch.testpackages, -justifypanic.testpackages, -fullstructcomp.testpackages, -errsemantics.testpackages, and -separategotwant.testpackages.

Run the new binary with ./custom-gcl run ./....

Supported golangci-lint versions: the plugin is verified against v2.10.1. The v2.9 line shares the same plugin register API and the same golang.org/x/tools requirement, so it works too. Earlier v2 releases are untested.

Development

Run make setup once per clone; it installs the tracked git hooks. make check is the definition of green: tidy check, vet, lint, the coverage-gate self-test, race-enabled tests behind a statement coverage gate (COVERAGE_MIN, default 90%), and the build. Read AGENTS.md before your first commit and REVIEW.md before your first pull request.

License

MIT. See LICENSE.

Documentation

Overview

Package antislop provides go/analysis analyzers that reject low-evidence Go patterns. The rule catalogue lives in docs/spec.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Analyzers

func Analyzers() []*analysis.Analyzer

Analyzers returns every analyzer this module provides, the opt-in rules included. Consumers register the full list; rule toggles happen in the consumer's configuration, not here.

One registry serves the three consumption paths of 003. The golangci-lint plugin reads a configuration file, so it applies the opt-in severity of 002 and drops such a rule until enable names it. cmd/antislop and go vet -vettool read no configuration file, so they run every rule. The reader turns one off with the -NAME=false flag that multichecker gives each analyzer.

Types

This section is empty.

Directories

Path Synopsis
analyzers
errsemantics
Package errsemantics implements rule G13 of the anti-slop rule set.
Package errsemantics implements rule G13 of the anti-slop rule set.
fullstructcomp
Package fullstructcomp implements rule G12 of the anti-slop rule set.
Package fullstructcomp implements rule G12 of the anti-slop rule set.
justifypanic
Package justifypanic implements rule G11 of the anti-slop rule set.
Package justifypanic implements rule G11 of the anti-slop rule set.
noadhoctypeswitch
Package noadhoctypeswitch implements rule G06 of the anti-slop rule set.
Package noadhoctypeswitch implements rule G06 of the anti-slop rule set.
noanyparam
Package noanyparam implements rule G03 of the anti-slop rule set.
Package noanyparam implements rule G03 of the anti-slop rule set.
noanyreturn
Package noanyreturn implements rule G04 of the anti-slop rule set.
Package noanyreturn implements rule G04 of the anti-slop rule set.
noerrorassert
Package noerrorassert implements rule G10 of the anti-slop rule set.
Package noerrorassert implements rule G10 of the anti-slop rule set.
nointerfacereturn
Package nointerfacereturn implements rule G09 of the anti-slop rule set.
Package nointerfacereturn implements rule G09 of the anti-slop rule set.
nolaundering
Package nolaundering implements rule G05 of the anti-slop rule set.
Package nolaundering implements rule G05 of the anti-slop rule set.
nomonkeypatch
Package nomonkeypatch implements rule G08 of the anti-slop rule set.
Package nomonkeypatch implements rule G08 of the anti-slop rule set.
noreflect
Package noreflect implements rule G07 of the anti-slop rule set.
Package noreflect implements rule G07 of the anti-slop rule set.
nountypedmap
Package nountypedmap implements rule G02 of the anti-slop rule set.
Package nountypedmap implements rule G02 of the anti-slop rule set.
safetyassert
Package safetyassert implements rule G01 of the anti-slop rule set: a single-result type assertion must carry a comment that states the invariant which makes the panic unreachable.
Package safetyassert implements rule G01 of the anti-slop rule set: a single-result type assertion must carry a comment that states the invariant which makes the panic unreachable.
separategotwant
Package separategotwant implements rule G14 of the anti-slop rule set.
Package separategotwant implements rule G14 of the anti-slop rule set.
cmd
antislop command
Command antislop runs every anti-slop analyzer as a standalone multichecker.
Command antislop runs every anti-slop analyzer as a standalone multichecker.
internal
pathmatch
Package pathmatch matches the import path of a package against the path patterns of a configuration setting.
Package pathmatch matches the import path of a package against the path patterns of a configuration setting.
signature
Package signature holds the machinery that more than one rule needs.
Package signature holds the machinery that more than one rule needs.
Package antislopplugin registers the anti-slop analyzers as a golangci-lint module plugin.
Package antislopplugin registers the anti-slop analyzers as a golangci-lint module plugin.

Jump to

Keyboard shortcuts

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