standardgo

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 8 Imported by: 0

README

standardgo

CI Go Version


One ruleset. No config files. No arguments. standardgo is an unconfigurable golangci-lint ruleset for amberpixels Go projects, shipped as a binary.

Every project that copies a .golangci.yml eventually has a different one. Placeholders go unreplaced, a linter gets commented out during a bad afternoon, and a year later no two repos agree on what "clean" means.

standardgo removes the file. The ruleset - 46 linters and 4 formatters - is compiled into this binary along with the golangci-lint engine itself, so a project pins both with one line in go.mod and carries no lint config of its own.

It follows standardrb and standard: the config is not something you extend, it is something you install.

Install

go get -tool github.com/amberpixels/standardgo/cmd/standardgo

That writes a tool directive to your go.mod, which survives go mod tidy and pins the ruleset like any other dependency. Nothing needs to be installed on the machine - not even golangci-lint, which ships inside the binary.

Delete your .golangci.yml after adopting. It will not be read.

Quick Start

# lint the current module
go tool standardgo

# fix everything fixable, formatting included
go tool standardgo ./... --fix

# formatting only - a faster subset of --fix, good for editor-on-save
go tool standardgo fmt ./...

# what is actually enabled
go tool standardgo linters

run reports formatting problems as ordinary issues, and --fix applies formatter and linter fixes alike - including cleaning up an import a fixer just orphaned. So --fix is a superset of fmt, and there is no ordering to remember. fmt exists because it skips the linters entirely and costs about half as much.

Upgrading the rules everywhere is one command per repo:

go get -tool github.com/amberpixels/standardgo/cmd/standardgo@latest

Configuration

There isn't any, and that is the point. --config is refused:

standardgo: --config is not accepted; the ruleset ships with this binary.
Project-local additions go in .standardgo.yml

A project may add to the ruleset via .standardgo.yml, never weaken it:

# enable linters the shared ruleset does not
linters:
  enable:
    - dupl

  # configure linters the shared ruleset leaves unconfigured
  settings:
    depguard:
      rules:
        layering:
          files: ["**/internal/business/**"]
          deny:
            - pkg: github.com/amberpixels/myapp/internal/application
              desc: "business must not import application"

# the only way to get softer, and only under a path
ignore:
  - path: "internal/legacy/"
    linters: [gosec, funcorder]

Anything the shared ruleset already configures is off limits in both directions - making a locked linter stricter is refused just as firmly as making it softer:

standardgo: .standardgo.yml may not override settings for "gocritic",
which the shared ruleset already configures

That sounds arbitrary until you try to define "stricter" for an arbitrary setting. Is a different gocritic check set stricter? Nobody can say. "Already configured" is decidable, so that is the line.

Custom Linters

Alongside the upstream linters, standardgo ships amberpixels' own:

  • lostfield - validates Go type-converter functions so no field is silently dropped on the way from one struct to another.

These are golangci-lint module plugins, linked into this binary via blank imports. Go links statically, so a project cannot add a plugin to an already-built standardgo - the plugin has to live here. In exchange you never touch .custom-gcl.yml or run golangci-lint custom: standardgo already is the custom binary that mechanism exists to produce.

Configuring a bundled plugin

Some plugins only make sense once a project tells them where to look. lostfield is one: what counts as a converter, and which fields a persistence layer manages, are per-project facts no shared default can guess. So a project may supply settings for a plugin the shared ruleset declares but does not tune:

# .standardgo.yml
linters:
  settings:
    custom:
      lostfield:
        settings:
          only-converters: ["To*", "From*"]
          exclude-fields: ["^ID$", CreatedAt, UpdatedAt, DeletedAt]
          ignore-tags: ['lostfield:"ignore"']

Note that these are YAML lists. The comma-separated form (exclude-fields: 'A,B') is lostfield's CLI-flag syntax and is not valid here. Unknown keys and out-of-range values are rejected at startup rather than ignored, so a typo fails loudly.

type, path and description stay locked. Settings are an addition; those three decide what code backs the plugin, and the plugin is compiled into this binary. The same already-configured rule applies as everywhere else: once the shared ruleset tunes a plugin, that plugin is closed.

How It Works

.golangci.yml is embedded with go:embed. On each run standardgo merges .standardgo.yml if present, writes the result to a temp file named after its content hash, and hands it to golangci-lint's commands.Execute, which is linked into this binary rather than shelled out to.

Bundling the engine is what makes results reproducible: a project cannot lint differently on a laptop and in CI, because there is no second copy of golangci-lint to disagree with. The cost is that go get -tool pulls every linter's dependencies into your go.mod as indirect requirements. They never enter your build graph. If that noise bothers you, put the tool directive in a nested tools/go.mod, build the binary from there, and run it from the repo root.

Engine upgrades arrive as ordinary Dependabot PRs. CI on this repo fails such a PR if the new engine deprecates a linter the ruleset enables, and reports any linter the engine has gained that the ruleset has not yet taken a position on.

Requirements

Go 1.26 or newer.

Feedback

standardgo is a solo, opinionated project - but if you stumbled upon it and have ideas, questions, or bug reports, an issue is always welcome :)

License

MIT © amberpixels

Documentation

Overview

Package standardgo is an unconfigurable golangci-lint configuration for amberpixels Go projects.

The ruleset lives in exactly one place — the .golangci.yml embedded below — and is delivered as a binary rather than a file to copy. Projects that adopt standardgo carry no .golangci.yml of their own, so there is nothing to drift.

golangci-lint has no config inheritance (no extends, no remote configs), so a shared ruleset cannot be referenced; it has to be distributed. That is the same design standard (JS) and standardrb (Ruby) chose deliberately, even though their engines do support inheritance.

Index

Constants

View Source
const OverlayFile = ".standardgo.yml"

OverlayFile is the project-local overlay read from the working directory.

Variables

View Source
var Config []byte

Config is the locked golangci-lint configuration.

Functions

func Merge

func Merge(locked, overlay []byte) ([]byte, error)

Merge applies overlay onto the locked config and returns the result. It returns an error rather than silently dropping anything the overlay is not permitted to do, so a rejected override is visible instead of confusing.

Types

type IgnoreRule

type IgnoreRule struct {
	Path    string   `yaml:"path"`
	Linters []string `yaml:"linters"`
}

IgnoreRule silences specific linters under a path glob.

type Overlay

type Overlay struct {
	Linters struct {
		Enable   []string       `yaml:"enable"`
		Disable  []string       `yaml:"disable"`
		Settings map[string]any `yaml:"settings"`
	} `yaml:"linters"`

	Ignore []IgnoreRule `yaml:"ignore"`
}

Overlay is the optional project-local .standardgo.yml.

It deliberately cannot change a rule the shared config already sets — in either direction. Making a locked linter stricter is refused just as firmly as making it softer, because "stricter" is not decidable in general (is a different gocritic check set stricter?) while "already configured" is. This mirrors standardrb's extend_config, which drops any cop Standard has already configured.

The one sanctioned way to get softer is Ignore, and it is path-scoped: a linter may be silenced under a glob, never globally.

Directories

Path Synopsis
cmd
standardgo command
Command standardgo lints a Go project with the locked amberpixels ruleset.
Command standardgo lints a Go project with the locked amberpixels ruleset.

Jump to

Keyboard shortcuts

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