Skip to content

Latest commit

 

History

History
97 lines (71 loc) · 3.78 KB

File metadata and controls

97 lines (71 loc) · 3.78 KB

Design and scope

goago enforces one way to write Go across a codebase. A project selects the Go constructs that it accepts. goago enforces that choice.

Motivation

Go's original design put simplicity, safety, and readability first. It also 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. A project selects one way to write Go, and goago enforces it. Developers and coding agents follow the same rules. CI checks them. Go at Google explains why uniform code helps teams work together.

This need remains current. The 2024 Go Developer Survey found that consistent coding standards were the most common high-value team problem. The 2025 survey also found broad AI coding-tool use and continued concerns about generated-code quality.

Recent releases added more ways to write Go. Go 1.26 let a generic type refer to itself in its type parameter list. It also let new accept an expression instead of a type. Go 1.27 let methods declare type parameters.

goago does not claim that these features are bad. Each project selects one way to write Go. The linter enforces that choice before review.

Each goago rule removes an alternative. No rule adds syntax, rewrites code, or changes semantics.

Policy boundary

The rules encode one project's taste. They do not claim to encode Rob Pike's preferences. His retrospective does not list variable declaration syntax as a mistake. Its generics argument is close to the opposite position. Defining generic containers in the language without programmer access to that genericity was arguably an error.

Treat goago as a house policy, not an appeal to authority.

Why a linter instead of a Go fork

The parser change for one language restriction can be small. For example, Go parsers rejected generic methods through Go 1.26. Go 1.27 removed those checks. A fork can restore them in two parser locations.

In src/cmd/compile/internal/syntax/parser.go, restore the receiver check in funcType:

 var tparamList []*Field
 if p.got(_Lbrack) {
+    if context == "method" {
+        p.syntaxErrorAt(typ.pos, "method must have no type parameters")
+    }
     if p.tok == _Rbrack {

In src/go/parser/parser.go, restore the check in parseFuncDecl:

+if recv != nil && tparams != nil {
+    p.error(tparams.Opening, "method must have no type parameters")
+}

Build the fork with ./make.bash.

The scope boundary is harder. The stock standard library uses constructs that a project can reject. It uses := in 2,522 of 3,916 non-test files. It declares generic functions or types in 115 non-test files. A compiler that rejects those constructs cannot build its own standard library.

A useful compiler fork must restrict first-party packages while it exempts GOROOT and the module cache. goago gets that scope from package loading.

A fork also needs its own distribution and version pin through GOTOOLCHAIN=local. It needs a rebase every six months. A stock gopls can disagree with that compiler unless the project forks the language server too.

Use a compiler fork when a dependency must not contain one or two constructs. Use goago for source policy in the code that the project owns.

Measured evidence

The standard library survey records the population, commands, and current counts behind each rule rationale. A rule proposal must state its non-findings and measure the construct before it selects a default.