From 13743a4ceea0de3a1e2c33c27805200ce0866962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 27 Dec 2023 23:10:47 +0100 Subject: [PATCH] format: support Go versions from newer go.mod files The 'go' directive in go.mod files can now contain strings such as "go1.22rc1" or "go1.21.1", and our old semver logic didn't support some of those. Upstream is providing new API via go/version in Go 1.22, but until that is out, we implement our own workaround to avoid panics. Fixes #280. --- format/format.go | 31 ++++++++++++++++++---------- testdata/script/diagnose.txtar | 4 ++-- testdata/script/gomod.txtar | 25 ++++++++++++++++++++++ testdata/script/octal-literals.txtar | 2 +- 4 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 testdata/script/gomod.txtar diff --git a/format/format.go b/format/format.go index 9771d17..aaae194 100644 --- a/format/format.go +++ b/format/format.go @@ -31,17 +31,20 @@ import ( // Options is the set of formatting options which affect gofumpt. type Options struct { - // LangVersion corresponds to the Go language 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 be: - // - // go mod edit -json | jq -r '.Go' + // 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: + // + // go mod edit -json | jq -r '.Go' LangVersion string // ModulePath corresponds to the Go module path which contains the source @@ -82,20 +85,26 @@ 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" - } else if opts.LangVersion[0] != 'v' { - opts.LangVersion = "v" + opts.LangVersion + opts.LangVersion = "v1.0" } - if !semver.IsValid(opts.LangVersion) { - panic(fmt.Sprintf("invalid semver string: %q", opts.LangVersion)) + m := rxGoVersionMajorMinor.FindStringSubmatch(opts.LangVersion) + if m == nil { + panic(fmt.Sprintf("invalid Go version: %q", opts.LangVersion)) } + opts.LangVersion = "v" + m[2] + "." + m[3] f := &fumpter{ File: fset.File(file.Pos()), fset: fset, diff --git a/testdata/script/diagnose.txtar b/testdata/script/diagnose.txtar index 9528743..6bbcb8f 100644 --- a/testdata/script/diagnose.txtar +++ b/testdata/script/diagnose.txtar @@ -14,7 +14,7 @@ cmp stdout foo.go.golden exec gofumpt -extra foo.go cmp stdout foo.go.golden-extra -exec gofumpt -lang=v1 foo.go +exec gofumpt -lang=1.0 foo.go cmp stdout foo.go.golden-lang exec gofumpt -d nochange.go @@ -91,7 +91,7 @@ package p -- foo.go.golden-lang -- package p -//gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=v1 -modpath=test +//gofumpt:diagnose version: v0.0.0-20220727155840-8dda8068d9f3 (go1.18.29) flags: -lang=v1.0 -modpath=test -- foo.go.golden-released -- package p diff --git a/testdata/script/gomod.txtar b/testdata/script/gomod.txtar new file mode 100644 index 0000000..90f13a8 --- /dev/null +++ b/testdata/script/gomod.txtar @@ -0,0 +1,25 @@ +# Test various edge cases with go.mod files. + +exec gofumpt toolchain-stable/a.go +stdout '//gofumpt:diagnose.* -lang=v1.21' + +exec gofumpt toolchain-unstable/a.go +stdout '//gofumpt:diagnose.* -lang=v1.21' + +-- toolchain-stable/go.mod -- +module a + +go 1.21.2 +-- toolchain-stable/a.go -- +package a + +//gofumpt:diagnose + +-- toolchain-unstable/go.mod -- +module a + +go 1.21rc3 +-- toolchain-unstable/a.go -- +package a + +//gofumpt:diagnose diff --git a/testdata/script/octal-literals.txtar b/testdata/script/octal-literals.txtar index da87881..0408dda 100644 --- a/testdata/script/octal-literals.txtar +++ b/testdata/script/octal-literals.txtar @@ -20,7 +20,7 @@ exec gofumpt -d foo.go.golden ! stdout . # We can give an explicitly older version, too -exec gofumpt -lang=v1 -l . +exec gofumpt -lang=1.0 -l . ! stdout . -- go.mod --