Skip to content

Commit

Permalink
require Go 1.22 and use go/version for Options.LangVersion
Browse files Browse the repository at this point in the history
This is a Go API breaking change; where existing users could use
strings such as "1.16" or "v1.16", they should now use "go1.16".
Note that the "go" prefix is mandatory, following go/version.

I considered a softer landing to manipulate the older string formats
to conform to the new format, but given how go/version is strict
and Go versions are now clearly defined differently from SemVer,
it seems best to draw a line and follow go/version's notion of validity.

Supporting "go1.16" alongside "v1.16" could also cause confusion in the
long run, as Go versions are subtly different from SemVer in terms
of validity and order. Requiring the "go" prefix avoids footguns.

Most gofumpt CLI users shouldn't be using the -lang CLI flag directly,
as by default it grabs its value from the go directive in go.mod.
Hence, most gofumpt CLI users should not be affected by this breakage.
Go API users, such as tool or IDE integration authors, are more likely
to get broken as the API falls back to assuming a conservative Go 1.0.
Fixing the breakage is rather simple, as shown by the changes below.

While here, suggest `go list -m` rather than `go mod edit -json`,
as the latter requires using `jq` to obtain just one field.
We still mention the latter command as an alternative.
  • Loading branch information
mvdan committed Jul 17, 2024
1 parent 41a9946 commit 88a300b
Show file tree
Hide file tree
Showing 8 changed files with 40 additions and 46 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ package p
$ gofumpt f.go
package p
//gofumpt:diagnose v0.1.1-0.20211103104632-bdfa3b02e50a -lang=v1.16
//gofumpt:diagnose v0.1.1-0.20211103104632-bdfa3b02e50a -lang=go1.16
```

### License
Expand Down
48 changes: 21 additions & 27 deletions format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"go/ast"
"go/parser"
"go/token"
goversion "go/version"
"os"
"reflect"
"regexp"
Expand All @@ -23,7 +24,6 @@ import (
"unicode/utf8"

"github.com/google/go-cmp/cmp"
"golang.org/x/mod/semver"
"golang.org/x/tools/go/ast/astutil"

"mvdan.cc/gofumpt/internal/govendor/go/format"
Expand All @@ -32,26 +32,25 @@ import (

// Options is the set of formatting options which affect gofumpt.
type Options struct {
// TODO: link to the go/version docs once Go 1.22 is out.
// The old semver docs said:
//
// LangVersion is treated as a semantic version, which may start with a "v"
// prefix. Like Go versions, it may also be incomplete; "1.14" is equivalent
// to "1.14.0". When empty, it is equivalent to "v1", to not use language
// features which could break programs.

// LangVersion is the Go version a piece of code is written in.
// The version is used to decide whether to apply formatting
// rules which require new language features.
// When inside a Go module, LangVersion should typically be:
// When empty, a default of go1 is assumed.
// Otherwise, the version must satisfy [go/version.IsValid].
//
// When formatting a Go module, LangVersion should typically be
//
// go mod edit -json | jq -r '.Go'
// go list -m -f {{.GoVersion}}
//
// with a "go" prefix, or the equivalent from `go mod edit -json`.
LangVersion string

// ModulePath corresponds to the Go module path which contains the source
// code being formatted. When inside a Go module, ModulePath should be:
// code being formatted. When formatting a Go module, ModulePath should be
//
// go list -m -f {{.Path}}
//
// go mod edit -json | jq -r '.Module.Path'
// or the equivalent from `go mod edit -json`.
//
// ModulePath is used for formatting decisions like what import paths are
// considered to be not part of the standard library. When empty, the source
Expand Down Expand Up @@ -86,26 +85,21 @@ func Source(src []byte, opts Options) ([]byte, error) {
return buf.Bytes(), nil
}

var rxGoVersionMajorMinor = regexp.MustCompile(`^(v|go)?([1-9]+)\.([0-9]+)`)

// File modifies a file and fset in place to follow gofumpt's format. The
// changes might include manipulating adding or removing newlines in fset,
// modifying the position of nodes, or modifying literal values.
func File(fset *token.FileSet, file *ast.File, opts Options) {
simplify(file)

// TODO: replace this hacky mess with go/version once we can rely on Go 1.22,
// as well as replacing our uses of the semver package.
// In particular, we likely want to allow any of 1.21, 1.21.2, or go1.21rc3,
// but we can rely on go/version.Lang to validate and normalize.
if opts.LangVersion == "" {
opts.LangVersion = "v1.0"
}
m := rxGoVersionMajorMinor.FindStringSubmatch(opts.LangVersion)
if m == nil {
panic(fmt.Sprintf("invalid Go version: %q", opts.LangVersion))
opts.LangVersion = "go1"
} else {
lang := goversion.Lang(opts.LangVersion)
if lang == "" {
panic(fmt.Sprintf("invalid Go version: %q", opts.LangVersion))
}
opts.LangVersion = lang
}
opts.LangVersion = "v" + m[2] + "." + m[3]
f := &fumpter{
file: fset.File(file.Pos()),
fset: fset,
Expand Down Expand Up @@ -680,8 +674,8 @@ func (f *fumpter) applyPre(c *astutil.Cursor) {
}

case *ast.BasicLit:
// Octal number literals were introduced in 1.13.
if semver.Compare(f.LangVersion, "v1.13") >= 0 {
// Octal number literals were introduced in Go 1.13.
if goversion.Compare(f.LangVersion, "go1.13") >= 0 {
if node.Kind == token.INT && rxOctalInteger.MatchString(node.Value) {
node.Value = "0o" + node.Value[1:]
c.Replace(node)
Expand Down
8 changes: 4 additions & 4 deletions format/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ func FuzzFormat(f *testing.F) {
for _, file := range archive.Files {
f.Logf("adding %s from %s", file.Name, path)
if strings.HasSuffix(file.Name, ".go") || strings.Contains(file.Name, ".go.") {
f.Add(string(file.Data), int8(18), false) // -lang=1.18
f.Add(string(file.Data), int8(1), false) // -lang=1.1
f.Add(string(file.Data), int8(18), true) // -lang=1.18 -extra
f.Add(string(file.Data), int8(18), false) // -lang=go1.18
f.Add(string(file.Data), int8(1), false) // -lang=go1.1
f.Add(string(file.Data), int8(18), true) // -lang=go1.18 -extra
}
}
}
Expand All @@ -40,7 +40,7 @@ func FuzzFormat(f *testing.F) {
// TODO: also fuzz Options.ModulePath
opts := Options{ExtraRules: extraRules}
if majorVersion >= 0 {
opts.LangVersion = fmt.Sprintf("1.%d", majorVersion)
opts.LangVersion = fmt.Sprintf("go1.%d", majorVersion)
}

orig := []byte(src)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module mvdan.cc/gofumpt

go 1.21
go 1.22

require (
github.com/go-quicktest/qt v1.101.0
Expand Down
4 changes: 2 additions & 2 deletions gofmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func usage() {
-w write result to (source) file instead of stdout
-extra enable extra rules which should be vetted by a human
-lang str target Go version in the form "1.X" (default from go.mod)
-lang str target Go version in the form "go1.X" (default from go.mod)
-modpath str Go module path containing the source file (default from go.mod)
`)
}
Expand Down Expand Up @@ -298,7 +298,7 @@ func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter, e
if ok && mod != nil {
mod := mod.(*module)
if lang == "" {
lang = mod.Go
lang = "go" + mod.Go
}
if modpath == "" {
modpath = mod.Module.Path
Expand Down
14 changes: 7 additions & 7 deletions testdata/script/diagnose.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ cmp stdout foo.go.golden
exec gofumpt -extra foo.go
cmp stdout foo.go.golden-extra

exec gofumpt -lang=1.0 foo.go
exec gofumpt -lang=go1 foo.go
cmp stdout foo.go.golden-lang

exec gofumpt -d nochange.go
Expand Down Expand Up @@ -79,24 +79,24 @@ package p
-- foo.go.golden --
package p

//gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=v1.16 -modpath=test
//gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=go1.16 -modpath=test
-- foo.go.golden-devel --
package p

//gofumpt:diagnose version: (devel) (go1.18.29) flags: -lang=v1.16 -modpath=test
//gofumpt:diagnose version: (devel) (go1.18.29) flags: -lang=go1.16 -modpath=test
-- foo.go.golden-extra --
package p

//gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=v1.16 -modpath=test -extra
//gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=go1.16 -modpath=test -extra
-- foo.go.golden-lang --
package p

//gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=v1.0 -modpath=test
//gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=go1 -modpath=test
-- foo.go.golden-released --
package p

//gofumpt:diagnose v0.3.2-0.20220627183521-8dda8068d9f3 -lang=v1.16 -modpath=test
//gofumpt:diagnose v0.3.2-0.20220627183521-8dda8068d9f3 -lang=go1.16 -modpath=test
-- foo.go.golden-external --
package p

//gofumpt:diagnose (devel) -lang=v1.16 -modpath=
//gofumpt:diagnose (devel) -lang=go1.16 -modpath=
4 changes: 2 additions & 2 deletions testdata/script/gomod.txtar
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Test various edge cases with go.mod files.

exec gofumpt toolchain-stable/a.go
stdout '//gofumpt:diagnose.* -lang=v1.21'
stdout '//gofumpt:diagnose.* -lang=go1.21'

exec gofumpt toolchain-unstable/a.go
stdout '//gofumpt:diagnose.* -lang=v1.21'
stdout '//gofumpt:diagnose.* -lang=go1.21'

-- toolchain-stable/go.mod --
module a
Expand Down
4 changes: 2 additions & 2 deletions testdata/script/octal-literals.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ exec gofumpt -l .
! stdout .

# We can give an explicitly newer version.
exec gofumpt -lang=1.13 -l .
exec gofumpt -lang=go1.13 -l .
stdout -count=1 'foo\.go'
stdout -count=1 'nested[/\\]nested\.go'

Expand All @@ -20,7 +20,7 @@ exec gofumpt -d foo.go.golden
! stdout .

# We can give an explicitly older version, too
exec gofumpt -lang=1.0 -l .
exec gofumpt -lang=go1.0 -l .
! stdout .

-- go.mod --
Expand Down

0 comments on commit 88a300b

Please sign in to comment.