aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go')
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go159
1 files changed, 159 insertions, 0 deletions
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go
new file mode 100644
index 0000000000..75d8697759
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go
@@ -0,0 +1,159 @@
+// 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 stdversion reports uses of standard library symbols that are
+// "too new" for the Go version in force in the referring file.
+package stdversion
+
+import (
+ "go/ast"
+ "go/build"
+ "go/types"
+ "regexp"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/versions"
+)
+
+const Doc = `report uses of too-new standard library symbols
+
+The stdversion analyzer reports references to symbols in the standard
+library that were introduced by a Go release higher than the one in
+force in the referring file. (Recall that the file's Go version is
+defined by the 'go' directive its module's go.mod file, or by a
+"//go:build go1.X" build tag at the top of the file.)
+
+The analyzer does not report a diagnostic for a reference to a "too
+new" field or method of a type that is itself "too new", as this may
+have false positives, for example if fields or methods are accessed
+through a type alias that is guarded by a Go version constraint.
+`
+
+var Analyzer = &analysis.Analyzer{
+ Name: "stdversion",
+ Doc: Doc,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion",
+ RunDespiteErrors: true,
+ Run: run,
+}
+
+func run(pass *analysis.Pass) (any, error) {
+ // Prior to go1.22, versions.FileVersion returns only the
+ // toolchain version, which is of no use to us, so
+ // disable this analyzer on earlier versions.
+ if !slicesContains(build.Default.ReleaseTags, "go1.22") {
+ return nil, nil
+ }
+
+ // Don't report diagnostics for modules marked before go1.21,
+ // since at that time the go directive wasn't clearly
+ // specified as a toolchain requirement.
+ //
+ // TODO(adonovan): after go1.21, call GoVersion directly.
+ pkgVersion := any(pass.Pkg).(interface{ GoVersion() string }).GoVersion()
+ if !versions.AtLeast(pkgVersion, "go1.21") {
+ return nil, nil
+ }
+
+ // disallowedSymbols returns the set of standard library symbols
+ // in a given package that are disallowed at the specified Go version.
+ type key struct {
+ pkg *types.Package
+ version string
+ }
+ memo := make(map[key]map[types.Object]string) // records symbol's minimum Go version
+ disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]string {
+ k := key{pkg, version}
+ disallowed, ok := memo[k]
+ if !ok {
+ disallowed = typesinternal.TooNewStdSymbols(pkg, version)
+ memo[k] = disallowed
+ }
+ return disallowed
+ }
+
+ // Scan the syntax looking for references to symbols
+ // that are disallowed by the version of the file.
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+ nodeFilter := []ast.Node{
+ (*ast.File)(nil),
+ (*ast.Ident)(nil),
+ }
+ var fileVersion string // "" => no check
+ inspect.Preorder(nodeFilter, func(n ast.Node) {
+ switch n := n.(type) {
+ case *ast.File:
+ if isGenerated(n) {
+ // Suppress diagnostics in generated files (such as cgo).
+ fileVersion = ""
+ } else {
+ fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
+ // (may be "" if unknown)
+ }
+
+ case *ast.Ident:
+ if fileVersion != "" {
+ if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
+ disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
+ if minVersion, ok := disallowed[origin(obj)]; ok {
+ noun := "module"
+ if fileVersion != pkgVersion {
+ noun = "file"
+ }
+ pass.ReportRangef(n, "%s.%s requires %v or later (%s is %s)",
+ obj.Pkg().Name(), obj.Name(), minVersion, noun, fileVersion)
+ }
+ }
+ }
+ }
+ })
+ return nil, nil
+}
+
+// Reduced from x/tools/gopls/internal/golang/util.go. Good enough for now.
+// TODO(adonovan): use ast.IsGenerated in go1.21.
+func isGenerated(f *ast.File) bool {
+ for _, group := range f.Comments {
+ for _, comment := range group.List {
+ if matched := generatedRx.MatchString(comment.Text); matched {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// Matches cgo generated comment as well as the proposed standard:
+//
+// https://golang.org/s/generatedcode
+var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
+
+// origin returns the original uninstantiated symbol for obj.
+func origin(obj types.Object) types.Object {
+ switch obj := obj.(type) {
+ case *types.Var:
+ return obj.Origin()
+ case *types.Func:
+ return obj.Origin()
+ case *types.TypeName:
+ if named, ok := obj.Type().(*types.Named); ok { // (don't unalias)
+ return named.Origin().Obj()
+ }
+ }
+ return obj
+}
+
+// TODO(adonovan): use go1.21 slices.Contains.
+func slicesContains[S ~[]E, E comparable](slice S, x E) bool {
+ for _, elem := range slice {
+ if elem == x {
+ return true
+ }
+ }
+ return false
+}