go/analysis/passes/buildtag: report invalid go versions in build tags.
Report invalid Go versions within build tags. Additionally reports
likely misspellings of Go version tags.
Fixes golang/go#64127
Change-Id: I9b698c94c7da29aafbe45b4c90d58c0bfe8efa1d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/597576
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/analysis/passes/buildtag/buildtag.go b/go/analysis/passes/buildtag/buildtag.go
index 5b4cf9d..b5a2d27 100644
--- a/go/analysis/passes/buildtag/buildtag.go
+++ b/go/analysis/passes/buildtag/buildtag.go
@@ -15,6 +15,7 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
+ "golang.org/x/tools/internal/versions"
)
const Doc = "check //go:build and // +build directives"
@@ -264,6 +265,8 @@
return
}
+ check.tags(pos, x)
+
if check.goBuild == nil {
check.goBuild = x
}
@@ -323,6 +326,8 @@
check.crossCheck = false
return
}
+ check.tags(pos, y)
+
if check.plusBuild == nil {
check.plusBuild = y
} else {
@@ -363,3 +368,51 @@
return
}
}
+
+// tags reports issues in go versions in tags within the expression e.
+func (check *checker) tags(pos token.Pos, e constraint.Expr) {
+ // Check that constraint.GoVersion is meaningful (>= go1.21).
+ if versions.ConstraintGoVersion == nil {
+ return
+ }
+
+ // Use Eval to visit each tag.
+ _ = e.Eval(func(tag string) bool {
+ if malformedGoTag(tag) {
+ check.pass.Reportf(pos, "invalid go version %q in build constraint", tag)
+ }
+ return false // result is immaterial as Eval does not short-circuit
+ })
+}
+
+// malformedGoTag returns true if a tag is likely to be a malformed
+// go version constraint.
+func malformedGoTag(tag string) bool {
+ // Not a go version?
+ if !strings.HasPrefix(tag, "go1") {
+ // Check for close misspellings of the "go1." prefix.
+ for _, pre := range []string{"go.", "g1.", "go"} {
+ suffix := strings.TrimPrefix(tag, pre)
+ if suffix != tag {
+ if valid, ok := validTag("go1." + suffix); ok && valid {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ // The tag starts with "go1" so it is almost certainly a GoVersion.
+ // Report it if it is not a valid build constraint.
+ valid, ok := validTag(tag)
+ return ok && !valid
+}
+
+// validTag returns (valid, ok) where valid reports when a tag is valid,
+// and ok reports determining if the tag is valid succeeded.
+func validTag(tag string) (valid bool, ok bool) {
+ if versions.ConstraintGoVersion != nil {
+ return versions.ConstraintGoVersion(&constraint.TagExpr{Tag: tag}) != "", true
+ }
+ return false, false
+}
diff --git a/go/analysis/passes/buildtag/buildtag_test.go b/go/analysis/passes/buildtag/buildtag_test.go
index 110343c..6109cba 100644
--- a/go/analysis/passes/buildtag/buildtag_test.go
+++ b/go/analysis/passes/buildtag/buildtag_test.go
@@ -10,6 +10,7 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/buildtag"
+ "golang.org/x/tools/internal/versions"
)
func Test(t *testing.T) {
@@ -30,5 +31,9 @@
return buildtag.Analyzer.Run(pass)
}
- analysistest.Run(t, analysistest.TestData(), &analyzer, "a")
+ patterns := []string{"a"}
+ if versions.ConstraintGoVersion != nil {
+ patterns = append(patterns, "b")
+ }
+ analysistest.Run(t, analysistest.TestData(), &analyzer, patterns...)
}
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers.go b/go/analysis/passes/buildtag/testdata/src/b/vers.go
new file mode 100644
index 0000000..71cf71d
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/b/vers.go
@@ -0,0 +1,10 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// want +3 `invalid go version \"go1.20.1\" in build constraint`
+// want +1 `invalid go version \"go1.20.1\" in build constraint`
+//go:build go1.20.1
+// +build go1.20.1
+
+package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers1.go b/go/analysis/passes/buildtag/testdata/src/b/vers1.go
new file mode 100644
index 0000000..37f9182
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/b/vers1.go
@@ -0,0 +1,7 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file is intentionally so its build tags always match.
+
+package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers2.go b/go/analysis/passes/buildtag/testdata/src/b/vers2.go
new file mode 100644
index 0000000..c91941f
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/b/vers2.go
@@ -0,0 +1,8 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// want +1 `invalid go version \"go120\" in build constraint`
+//go:build go120
+
+package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers3.go b/go/analysis/passes/buildtag/testdata/src/b/vers3.go
new file mode 100644
index 0000000..e26ac75
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/b/vers3.go
@@ -0,0 +1,8 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// want +1 `invalid go version \"go1..20\" in build constraint`
+//go:build go1..20
+
+package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers4.go b/go/analysis/passes/buildtag/testdata/src/b/vers4.go
new file mode 100644
index 0000000..2ddbe18
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/b/vers4.go
@@ -0,0 +1,8 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// want +1 `invalid go version \"go.20\" in build constraint`
+//go:build go.20
+
+package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers5.go b/go/analysis/passes/buildtag/testdata/src/b/vers5.go
new file mode 100644
index 0000000..83964f8
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/b/vers5.go
@@ -0,0 +1,8 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// want +1 `invalid go version \"g1.20\" in build constraint`
+//go:build g1.20
+
+package b
diff --git a/go/analysis/passes/buildtag/testdata/src/b/vers6.go b/go/analysis/passes/buildtag/testdata/src/b/vers6.go
new file mode 100644
index 0000000..219e2db
--- /dev/null
+++ b/go/analysis/passes/buildtag/testdata/src/b/vers6.go
@@ -0,0 +1,8 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// want +1 `invalid go version \"go20\" in build constraint`
+//go:build go20
+
+package b
diff --git a/internal/versions/constraint.go b/internal/versions/constraint.go
new file mode 100644
index 0000000..179063d
--- /dev/null
+++ b/internal/versions/constraint.go
@@ -0,0 +1,13 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package versions
+
+import "go/build/constraint"
+
+// ConstraintGoVersion is constraint.GoVersion (if built with go1.21+).
+// Otherwise nil.
+//
+// Deprecate once x/tools is after go1.21.
+var ConstraintGoVersion func(x constraint.Expr) string
diff --git a/internal/versions/constraint_go121.go b/internal/versions/constraint_go121.go
new file mode 100644
index 0000000..3801140
--- /dev/null
+++ b/internal/versions/constraint_go121.go
@@ -0,0 +1,14 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.21
+// +build go1.21
+
+package versions
+
+import "go/build/constraint"
+
+func init() {
+ ConstraintGoVersion = constraint.GoVersion
+}