1
2
3
4
5 package shadow
6
7 import (
8 _ "embed"
9 "go/ast"
10 "go/token"
11 "go/types"
12
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/inspect"
15 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
16 "golang.org/x/tools/go/ast/inspector"
17 )
18
19
20
21
22 var doc string
23
24 var Analyzer = &analysis.Analyzer{
25 Name: "shadow",
26 Doc: analysisutil.MustExtractDoc(doc, "shadow"),
27 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow",
28 Requires: []*analysis.Analyzer{inspect.Analyzer},
29 Run: run,
30 }
31
32
33 var strict = false
34
35 func init() {
36 Analyzer.Flags.BoolVar(&strict, "strict", strict, "whether to be strict about shadowing; can be noisy")
37 }
38
39 func run(pass *analysis.Pass) (interface{}, error) {
40 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
41
42 spans := make(map[types.Object]span)
43 for id, obj := range pass.TypesInfo.Defs {
44
45
46
47 if obj != nil {
48 growSpan(spans, obj, id.Pos(), id.End())
49 }
50 }
51 for id, obj := range pass.TypesInfo.Uses {
52 growSpan(spans, obj, id.Pos(), id.End())
53 }
54 for node, obj := range pass.TypesInfo.Implicits {
55
56
57
58
59
60
61
62
63 if cc, ok := node.(*ast.CaseClause); ok {
64 growSpan(spans, obj, cc.Colon, cc.Colon)
65 }
66 }
67
68 nodeFilter := []ast.Node{
69 (*ast.AssignStmt)(nil),
70 (*ast.GenDecl)(nil),
71 }
72 inspect.Preorder(nodeFilter, func(n ast.Node) {
73 switch n := n.(type) {
74 case *ast.AssignStmt:
75 checkShadowAssignment(pass, spans, n)
76 case *ast.GenDecl:
77 checkShadowDecl(pass, spans, n)
78 }
79 })
80 return nil, nil
81 }
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 type span struct {
103 min token.Pos
104 max token.Pos
105 }
106
107
108 func (s span) contains(pos token.Pos) bool {
109 return s.min <= pos && pos < s.max
110 }
111
112
113 func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos) {
114 if strict {
115 return
116 }
117 s, ok := spans[obj]
118 if ok {
119 if s.min > pos {
120 s.min = pos
121 }
122 if s.max < end {
123 s.max = end
124 }
125 } else {
126 s = span{pos, end}
127 }
128 spans[obj] = s
129 }
130
131
132 func checkShadowAssignment(pass *analysis.Pass, spans map[types.Object]span, a *ast.AssignStmt) {
133 if a.Tok != token.DEFINE {
134 return
135 }
136 if idiomaticShortRedecl(pass, a) {
137 return
138 }
139 for _, expr := range a.Lhs {
140 ident, ok := expr.(*ast.Ident)
141 if !ok {
142 pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier")
143 return
144 }
145 checkShadowing(pass, spans, ident)
146 }
147 }
148
149
150
151 func idiomaticShortRedecl(pass *analysis.Pass, a *ast.AssignStmt) bool {
152
153
154
155
156
157 if len(a.Rhs) != len(a.Lhs) {
158 return false
159 }
160
161 for i, expr := range a.Lhs {
162 lhs, ok := expr.(*ast.Ident)
163 if !ok {
164 pass.ReportRangef(expr, "invalid AST: short variable declaration of non-identifier")
165 return true
166 }
167 switch rhs := a.Rhs[i].(type) {
168 case *ast.Ident:
169 if lhs.Name != rhs.Name {
170 return false
171 }
172 case *ast.TypeAssertExpr:
173 if id, ok := rhs.X.(*ast.Ident); ok {
174 if lhs.Name != id.Name {
175 return false
176 }
177 }
178 default:
179 return false
180 }
181 }
182 return true
183 }
184
185
186
187 func idiomaticRedecl(d *ast.ValueSpec) bool {
188
189
190
191
192 if len(d.Names) != len(d.Values) {
193 return false
194 }
195 for i, lhs := range d.Names {
196 rhs, ok := d.Values[i].(*ast.Ident)
197 if !ok || lhs.Name != rhs.Name {
198 return false
199 }
200 }
201 return true
202 }
203
204
205 func checkShadowDecl(pass *analysis.Pass, spans map[types.Object]span, d *ast.GenDecl) {
206 if d.Tok != token.VAR {
207 return
208 }
209 for _, spec := range d.Specs {
210 valueSpec, ok := spec.(*ast.ValueSpec)
211 if !ok {
212 pass.ReportRangef(spec, "invalid AST: var GenDecl not ValueSpec")
213 return
214 }
215
216
217 if idiomaticRedecl(valueSpec) {
218 return
219 }
220 for _, ident := range valueSpec.Names {
221 checkShadowing(pass, spans, ident)
222 }
223 }
224 }
225
226
227 func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast.Ident) {
228 if ident.Name == "_" {
229
230 return
231 }
232 obj := pass.TypesInfo.Defs[ident]
233 if obj == nil {
234 return
235 }
236
237
238 _, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos())
239 if shadowed == nil {
240 return
241 }
242
243 if shadowed.Parent() == types.Universe {
244 return
245 }
246 if strict {
247
248 if shadowed.Pos() > ident.Pos() {
249 return
250 }
251 } else {
252
253
254 span, ok := spans[shadowed]
255 if !ok {
256 pass.ReportRangef(ident, "internal error: no range for %q", ident.Name)
257 return
258 }
259 if !span.contains(ident.Pos()) {
260 return
261 }
262 }
263
264 if types.Identical(obj.Type(), shadowed.Type()) {
265 line := pass.Fset.Position(shadowed.Pos()).Line
266 pass.ReportRangef(ident, "declaration of %q shadows declaration at line %d", obj.Name(), line)
267 }
268 }
269
View as plain text