1 | // Copyright 2019 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package checker_test |
6 | |
7 | import ( |
8 | "fmt" |
9 | "go/ast" |
10 | "io/ioutil" |
11 | "path/filepath" |
12 | "testing" |
13 | |
14 | "golang.org/x/tools/go/analysis" |
15 | "golang.org/x/tools/go/analysis/analysistest" |
16 | "golang.org/x/tools/go/analysis/internal/checker" |
17 | "golang.org/x/tools/go/analysis/passes/inspect" |
18 | "golang.org/x/tools/go/ast/inspector" |
19 | "golang.org/x/tools/internal/testenv" |
20 | ) |
21 | |
22 | func TestApplyFixes(t *testing.T) { |
23 | testenv.NeedsGoPackages(t) |
24 | |
25 | files := map[string]string{ |
26 | "rename/test.go": `package rename |
27 | |
28 | func Foo() { |
29 | bar := 12 |
30 | _ = bar |
31 | } |
32 | |
33 | // the end |
34 | `} |
35 | want := `package rename |
36 | |
37 | func Foo() { |
38 | baz := 12 |
39 | _ = baz |
40 | } |
41 | |
42 | // the end |
43 | ` |
44 | |
45 | testdata, cleanup, err := analysistest.WriteFiles(files) |
46 | if err != nil { |
47 | t.Fatal(err) |
48 | } |
49 | path := filepath.Join(testdata, "src/rename/test.go") |
50 | checker.Fix = true |
51 | checker.Run([]string{"file=" + path}, []*analysis.Analyzer{analyzer}) |
52 | |
53 | contents, err := ioutil.ReadFile(path) |
54 | if err != nil { |
55 | t.Fatal(err) |
56 | } |
57 | |
58 | got := string(contents) |
59 | if got != want { |
60 | t.Errorf("contents of rewritten file\ngot: %s\nwant: %s", got, want) |
61 | } |
62 | |
63 | defer cleanup() |
64 | } |
65 | |
66 | var analyzer = &analysis.Analyzer{ |
67 | Name: "rename", |
68 | Requires: []*analysis.Analyzer{inspect.Analyzer}, |
69 | Run: run, |
70 | } |
71 | |
72 | func run(pass *analysis.Pass) (interface{}, error) { |
73 | const ( |
74 | from = "bar" |
75 | to = "baz" |
76 | ) |
77 | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
78 | nodeFilter := []ast.Node{(*ast.Ident)(nil)} |
79 | inspect.Preorder(nodeFilter, func(n ast.Node) { |
80 | ident := n.(*ast.Ident) |
81 | if ident.Name == from { |
82 | msg := fmt.Sprintf("renaming %q to %q", from, to) |
83 | pass.Report(analysis.Diagnostic{ |
84 | Pos: ident.Pos(), |
85 | End: ident.End(), |
86 | Message: msg, |
87 | SuggestedFixes: []analysis.SuggestedFix{{ |
88 | Message: msg, |
89 | TextEdits: []analysis.TextEdit{{ |
90 | Pos: ident.Pos(), |
91 | End: ident.End(), |
92 | NewText: []byte(to), |
93 | }}, |
94 | }}, |
95 | }) |
96 | } |
97 | }) |
98 | |
99 | return nil, nil |
100 | } |
101 | |
102 | func TestRunDespiteErrors(t *testing.T) { |
103 | testenv.NeedsGoPackages(t) |
104 | |
105 | files := map[string]string{ |
106 | "rderr/test.go": `package rderr |
107 | |
108 | // Foo deliberately has a type error |
109 | func Foo(s string) int { |
110 | return s + 1 |
111 | } |
112 | `} |
113 | |
114 | testdata, cleanup, err := analysistest.WriteFiles(files) |
115 | if err != nil { |
116 | t.Fatal(err) |
117 | } |
118 | path := filepath.Join(testdata, "src/rderr/test.go") |
119 | |
120 | // A no-op analyzer that should finish regardless of |
121 | // parse or type errors in the code. |
122 | noop := &analysis.Analyzer{ |
123 | Name: "noop", |
124 | Requires: []*analysis.Analyzer{inspect.Analyzer}, |
125 | Run: func(pass *analysis.Pass) (interface{}, error) { |
126 | return nil, nil |
127 | }, |
128 | RunDespiteErrors: true, |
129 | } |
130 | |
131 | // A no-op analyzer that should finish regardless of |
132 | // parse or type errors in the code. |
133 | noopWithFact := &analysis.Analyzer{ |
134 | Name: "noopfact", |
135 | Requires: []*analysis.Analyzer{inspect.Analyzer}, |
136 | Run: func(pass *analysis.Pass) (interface{}, error) { |
137 | return nil, nil |
138 | }, |
139 | RunDespiteErrors: true, |
140 | FactTypes: []analysis.Fact{&EmptyFact{}}, |
141 | } |
142 | |
143 | for _, test := range []struct { |
144 | name string |
145 | pattern []string |
146 | analyzers []*analysis.Analyzer |
147 | code int |
148 | }{ |
149 | // parse/type errors |
150 | {name: "skip-error", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{analyzer}, code: 1}, |
151 | // RunDespiteErrors allows a driver to run an Analyzer even after parse/type errors. |
152 | // |
153 | // The noop analyzer doesn't use facts, so the driver loads only the root |
154 | // package from source. For the rest, it asks 'go list' for export data, |
155 | // which fails because the compiler encounters the type error. Since the |
156 | // errors come from 'go list', the driver doesn't run the analyzer. |
157 | {name: "despite-error", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{noop}, code: 1}, |
158 | // The noopfact analyzer does use facts, so the driver loads source for |
159 | // all dependencies, does type checking itself, recognizes the error as a |
160 | // type error, and runs the analyzer. |
161 | {name: "despite-error-fact", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{noopWithFact}, code: 0}, |
162 | // combination of parse/type errors and no errors |
163 | {name: "despite-error-and-no-error", pattern: []string{"file=" + path, "sort"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1}, |
164 | // non-existing package error |
165 | {name: "no-package", pattern: []string{"xyz"}, analyzers: []*analysis.Analyzer{analyzer}, code: 1}, |
166 | {name: "no-package-despite-error", pattern: []string{"abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1}, |
167 | {name: "no-multi-package-despite-error", pattern: []string{"xyz", "abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1}, |
168 | // combination of type/parsing and different errors |
169 | {name: "different-errors", pattern: []string{"file=" + path, "xyz"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1}, |
170 | // non existing dir error |
171 | {name: "no-match-dir", pattern: []string{"file=non/existing/dir"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1}, |
172 | // no errors |
173 | {name: "no-errors", pattern: []string{"sort"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 0}, |
174 | } { |
175 | if test.name == "despite-error" && testenv.Go1Point() < 20 { |
176 | // The behavior in the comment on the despite-error test only occurs for Go 1.20+. |
177 | continue |
178 | } |
179 | if got := checker.Run(test.pattern, test.analyzers); got != test.code { |
180 | t.Errorf("got incorrect exit code %d for test %s; want %d", got, test.name, test.code) |
181 | } |
182 | } |
183 | |
184 | defer cleanup() |
185 | } |
186 | |
187 | type EmptyFact struct{} |
188 | |
189 | func (f *EmptyFact) AFact() {} |
190 |
Members