1
2
3
4
5 package checker_test
6
7 import (
8 "fmt"
9 "go/ast"
10 "os"
11 "path/filepath"
12 "reflect"
13 "strings"
14 "testing"
15
16 "golang.org/x/tools/go/analysis"
17 "golang.org/x/tools/go/analysis/analysistest"
18 "golang.org/x/tools/go/analysis/internal/checker"
19 "golang.org/x/tools/go/analysis/passes/inspect"
20 "golang.org/x/tools/go/ast/inspector"
21 "golang.org/x/tools/internal/testenv"
22 "golang.org/x/tools/internal/testfiles"
23 "golang.org/x/tools/txtar"
24 )
25
26 func TestApplyFixes(t *testing.T) {
27 testenv.NeedsGoPackages(t)
28
29 files := map[string]string{
30 "rename/test.go": `package rename
31
32 func Foo() {
33 bar := 12
34 _ = bar
35 }
36
37 // the end
38 `}
39 want := `package rename
40
41 func Foo() {
42 baz := 12
43 _ = baz
44 }
45
46 // the end
47 `
48
49 testdata, cleanup, err := analysistest.WriteFiles(files)
50 if err != nil {
51 t.Fatal(err)
52 }
53 path := filepath.Join(testdata, "src/rename/test.go")
54 checker.Fix = true
55 checker.Run([]string{"file=" + path}, []*analysis.Analyzer{renameAnalyzer})
56
57 contents, err := os.ReadFile(path)
58 if err != nil {
59 t.Fatal(err)
60 }
61
62 got := string(contents)
63 if got != want {
64 t.Errorf("contents of rewritten file\ngot: %s\nwant: %s", got, want)
65 }
66
67 defer cleanup()
68 }
69
70 var renameAnalyzer = &analysis.Analyzer{
71 Name: "rename",
72 Requires: []*analysis.Analyzer{inspect.Analyzer},
73 Run: run,
74 Doc: "renames symbols named bar to baz",
75 }
76
77 var otherAnalyzer = &analysis.Analyzer{
78 Name: "other",
79 Requires: []*analysis.Analyzer{inspect.Analyzer},
80 Run: run,
81 Doc: "renames symbols named bar to baz only in package 'other'",
82 }
83
84 func run(pass *analysis.Pass) (interface{}, error) {
85 const (
86 from = "bar"
87 to = "baz"
88 conflict = "conflict"
89 duplicate = "duplicate"
90 other = "other"
91 )
92
93 if pass.Analyzer.Name == other {
94 if pass.Pkg.Name() != other {
95 return nil, nil
96 }
97 }
98
99 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
100 nodeFilter := []ast.Node{(*ast.Ident)(nil)}
101 inspect.Preorder(nodeFilter, func(n ast.Node) {
102 ident := n.(*ast.Ident)
103 if ident.Name == from {
104 msg := fmt.Sprintf("renaming %q to %q", from, to)
105 edits := []analysis.TextEdit{
106 {Pos: ident.Pos(), End: ident.End(), NewText: []byte(to)},
107 }
108 switch pass.Pkg.Name() {
109 case conflict:
110 edits = append(edits, []analysis.TextEdit{
111 {Pos: ident.Pos() - 1, End: ident.End(), NewText: []byte(to)},
112 {Pos: ident.Pos(), End: ident.End() - 1, NewText: []byte(to)},
113 {Pos: ident.Pos(), End: ident.End(), NewText: []byte("lorem ipsum")},
114 }...)
115 case duplicate:
116 edits = append(edits, edits...)
117 case other:
118 if pass.Analyzer.Name == other {
119 edits[0].Pos = edits[0].Pos + 1
120 }
121 }
122 pass.Report(analysis.Diagnostic{
123 Pos: ident.Pos(),
124 End: ident.End(),
125 Message: msg,
126 SuggestedFixes: []analysis.SuggestedFix{{Message: msg, TextEdits: edits}}})
127 }
128 })
129
130 return nil, nil
131 }
132
133 func TestRunDespiteErrors(t *testing.T) {
134 testenv.NeedsGoPackages(t)
135
136 files := map[string]string{
137 "rderr/test.go": `package rderr
138
139 // Foo deliberately has a type error
140 func Foo(s string) int {
141 return s + 1
142 }
143 `}
144
145 testdata, cleanup, err := analysistest.WriteFiles(files)
146 if err != nil {
147 t.Fatal(err)
148 }
149 path := filepath.Join(testdata, "src/rderr/test.go")
150
151
152
153 noop := &analysis.Analyzer{
154 Name: "noop",
155 Requires: []*analysis.Analyzer{inspect.Analyzer},
156 Run: func(pass *analysis.Pass) (interface{}, error) {
157 return nil, nil
158 },
159 RunDespiteErrors: true,
160 }
161
162
163
164 noopWithFact := &analysis.Analyzer{
165 Name: "noopfact",
166 Requires: []*analysis.Analyzer{inspect.Analyzer},
167 Run: func(pass *analysis.Pass) (interface{}, error) {
168 return nil, nil
169 },
170 RunDespiteErrors: true,
171 FactTypes: []analysis.Fact{&EmptyFact{}},
172 }
173
174 for _, test := range []struct {
175 name string
176 pattern []string
177 analyzers []*analysis.Analyzer
178 code int
179 }{
180
181 {name: "skip-error", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: 1},
182
183
184
185
186
187
188 {name: "despite-error", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{noop}, code: 1},
189
190
191
192 {name: "despite-error-fact", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{noopWithFact}, code: 0},
193
194 {name: "despite-error-and-no-error", pattern: []string{"file=" + path, "sort"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: 1},
195
196 {name: "no-package", pattern: []string{"xyz"}, analyzers: []*analysis.Analyzer{renameAnalyzer}, code: 1},
197 {name: "no-package-despite-error", pattern: []string{"abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1},
198 {name: "no-multi-package-despite-error", pattern: []string{"xyz", "abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1},
199
200 {name: "different-errors", pattern: []string{"file=" + path, "xyz"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: 1},
201
202 {name: "no-match-dir", pattern: []string{"file=non/existing/dir"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: 1},
203
204 {name: "no-errors", pattern: []string{"sort"}, analyzers: []*analysis.Analyzer{renameAnalyzer, noop}, code: 0},
205 } {
206 if test.name == "despite-error" && testenv.Go1Point() < 20 {
207
208 continue
209 }
210 if got := checker.Run(test.pattern, test.analyzers); got != test.code {
211 t.Errorf("got incorrect exit code %d for test %s; want %d", got, test.name, test.code)
212 }
213 }
214
215 defer cleanup()
216 }
217
218 type EmptyFact struct{}
219
220 func (f *EmptyFact) AFact() {}
221
222 func TestURL(t *testing.T) {
223
224 testenv.NeedsGoPackages(t)
225
226 files := map[string]string{
227 "p/test.go": `package p // want "package name is p"`,
228 }
229 pkgname := &analysis.Analyzer{
230 Name: "pkgname",
231 Doc: "trivial analyzer that reports package names",
232 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/internal/checker",
233 Run: func(p *analysis.Pass) (interface{}, error) {
234 for _, f := range p.Files {
235 p.ReportRangef(f.Name, "package name is %s", f.Name.Name)
236 }
237 return nil, nil
238 },
239 }
240
241 testdata, cleanup, err := analysistest.WriteFiles(files)
242 if err != nil {
243 t.Fatal(err)
244 }
245 defer cleanup()
246 path := filepath.Join(testdata, "src/p/test.go")
247 results := analysistest.Run(t, testdata, pkgname, "file="+path)
248
249 var urls []string
250 for _, r := range results {
251 for _, d := range r.Diagnostics {
252 urls = append(urls, d.URL)
253 }
254 }
255 want := []string{"https://pkg.go.dev/golang.org/x/tools/go/analysis/internal/checker"}
256 if !reflect.DeepEqual(urls, want) {
257 t.Errorf("Expected Diagnostics.URLs %v. got %v", want, urls)
258 }
259 }
260
261
262 func TestPassReadFile(t *testing.T) {
263 cwd, _ := os.Getwd()
264
265 const src = `
266 -- go.mod --
267 module example.com
268
269 -- p/file.go --
270 package p
271
272 -- p/ignored.go --
273 //go:build darwin && mips64
274
275 package p
276
277 hello from ignored
278
279 -- p/other.s --
280 hello from other
281 `
282
283
284 tmpdir := t.TempDir()
285 if err := testfiles.ExtractTxtar(tmpdir, txtar.Parse([]byte(src))); err != nil {
286 t.Fatal(err)
287 }
288
289 ran := false
290 a := &analysis.Analyzer{
291 Name: "a",
292 Requires: []*analysis.Analyzer{inspect.Analyzer},
293 Doc: "doc",
294 Run: func(pass *analysis.Pass) (any, error) {
295 if len(pass.OtherFiles)+len(pass.IgnoredFiles) == 0 {
296 t.Errorf("OtherFiles and IgnoredFiles are empty")
297 return nil, nil
298 }
299
300 for _, test := range []struct {
301 filename string
302 want string
303 }{
304 {
305 pass.OtherFiles[0],
306 "hello from other",
307 },
308 {
309 pass.IgnoredFiles[0],
310 "hello from ignored",
311 },
312 {
313 "nonesuch",
314 "nonesuch is not among OtherFiles, ",
315 },
316 {
317 filepath.Join(cwd, "checker_test.go"),
318 "checker_test.go is not among OtherFiles, ",
319 },
320 } {
321 content, err := pass.ReadFile(test.filename)
322 var got string
323 if err != nil {
324 got = err.Error()
325 } else {
326 got = string(content)
327 if len(got) > 100 {
328 got = got[:100] + "..."
329 }
330 }
331 if !strings.Contains(got, test.want) {
332 t.Errorf("Pass.ReadFile(%q) did not contain %q; got:\n%s",
333 test.filename, test.want, got)
334 }
335 }
336 ran = true
337 return nil, nil
338 },
339 }
340
341 analysistest.Run(t, tmpdir, a, "example.com/p")
342
343 if !ran {
344 t.Error("analyzer did not run")
345 }
346 }
347
View as plain text