diff options
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.go | 159 |
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 +} |