1
2
3
4
5
6
7 package stdversion
8
9 import (
10 "go/ast"
11 "go/build"
12 "go/types"
13 "regexp"
14 "slices"
15
16 "golang.org/x/tools/go/analysis"
17 "golang.org/x/tools/go/analysis/passes/inspect"
18 "golang.org/x/tools/go/ast/inspector"
19 "golang.org/x/tools/internal/typesinternal"
20 "golang.org/x/tools/internal/versions"
21 )
22
23 const Doc = `report uses of too-new standard library symbols
24
25 The stdversion analyzer reports references to symbols in the standard
26 library that were introduced by a Go release higher than the one in
27 force in the referring file. (Recall that the file's Go version is
28 defined by the 'go' directive its module's go.mod file, or by a
29 "//go:build go1.X" build tag at the top of the file.)
30
31 The analyzer does not report a diagnostic for a reference to a "too
32 new" field or method of a type that is itself "too new", as this may
33 have false positives, for example if fields or methods are accessed
34 through a type alias that is guarded by a Go version constraint.
35 `
36
37 var Analyzer = &analysis.Analyzer{
38 Name: "stdversion",
39 Doc: Doc,
40 Requires: []*analysis.Analyzer{inspect.Analyzer},
41 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion",
42 RunDespiteErrors: true,
43 Run: run,
44 }
45
46 func run(pass *analysis.Pass) (any, error) {
47
48
49
50 if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
51 return nil, nil
52 }
53
54
55
56
57 pkgVersion := pass.Pkg.GoVersion()
58 if !versions.AtLeast(pkgVersion, "go1.21") {
59 return nil, nil
60 }
61
62
63
64 type key struct {
65 pkg *types.Package
66 version string
67 }
68 memo := make(map[key]map[types.Object]string)
69 disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]string {
70 k := key{pkg, version}
71 disallowed, ok := memo[k]
72 if !ok {
73 disallowed = typesinternal.TooNewStdSymbols(pkg, version)
74 memo[k] = disallowed
75 }
76 return disallowed
77 }
78
79
80
81 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
82 nodeFilter := []ast.Node{
83 (*ast.File)(nil),
84 (*ast.Ident)(nil),
85 }
86 var fileVersion string
87 inspect.Preorder(nodeFilter, func(n ast.Node) {
88 switch n := n.(type) {
89 case *ast.File:
90 if ast.IsGenerated(n) {
91
92 fileVersion = ""
93 } else {
94 fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
95
96 }
97
98 case *ast.Ident:
99 if fileVersion != "" {
100 if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
101 disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
102 if minVersion, ok := disallowed[origin(obj)]; ok {
103 noun := "module"
104 if fileVersion != pkgVersion {
105 noun = "file"
106 }
107 pass.ReportRangef(n, "%s.%s requires %v or later (%s is %s)",
108 obj.Pkg().Name(), obj.Name(), minVersion, noun, fileVersion)
109 }
110 }
111 }
112 }
113 })
114 return nil, nil
115 }
116
117
118
119
120 var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
121
122
123 func origin(obj types.Object) types.Object {
124 switch obj := obj.(type) {
125 case *types.Var:
126 return obj.Origin()
127 case *types.Func:
128 return obj.Origin()
129 case *types.TypeName:
130 if named, ok := obj.Type().(*types.Named); ok {
131 return named.Origin().Obj()
132 }
133 }
134 return obj
135 }
136
View as plain text