@@ -19,7 +19,9 @@ import (
1919
2020 "golang.org/x/tools/go/analysis"
2121 "golang.org/x/tools/go/analysis/analysistest"
22+ "golang.org/x/tools/go/analysis/checker"
2223 "golang.org/x/tools/go/analysis/multichecker"
24+ "golang.org/x/tools/go/packages"
2325 "golang.org/x/tools/internal/testenv"
2426)
2527
@@ -126,15 +128,6 @@ func Foo() {
126128 _ = bar
127129}
128130
129- // the end
130- ` ,
131- "duplicate/dup.go" : `package duplicate
132-
133- func Foo() {
134- bar := 14
135- _ = bar
136- }
137-
138131// the end
139132` ,
140133 }
@@ -164,15 +157,6 @@ func Foo() {
164157 _ = baz
165158}
166159
167- // the end
168- ` ,
169- "duplicate/dup.go" : `package duplicate
170-
171- func Foo() {
172- baz := 14
173- _ = baz
174- }
175-
176160// the end
177161` ,
178162 }
@@ -182,7 +166,7 @@ func Foo() {
182166 }
183167 defer cleanup ()
184168
185- fix (t , dir , "rename,other" , exitCodeDiagnostics , "rename" , "duplicate" )
169+ fix (t , dir , "rename,other" , exitCodeDiagnostics , "rename" )
186170
187171 for name , want := range fixed {
188172 path := path .Join (dir , "src" , name )
@@ -196,6 +180,117 @@ func Foo() {
196180 }
197181}
198182
183+ // TestReportInvalidDiagnostic tests that a call to pass.Report with
184+ // certain kind of invalid diagnostic (e.g. conflicting fixes)
185+ // promptly results in a panic.
186+ func TestReportInvalidDiagnostic (t * testing.T ) {
187+ testenv .NeedsGoPackages (t )
188+
189+ // Load the errors package.
190+ cfg := & packages.Config {Mode : packages .LoadAllSyntax }
191+ initial , err := packages .Load (cfg , "errors" )
192+ if err != nil {
193+ t .Fatal (err )
194+ }
195+
196+ for _ , test := range []struct {
197+ name string
198+ want string
199+ diag func (pos token.Pos ) analysis.Diagnostic
200+ }{
201+ // Diagnostic has two alternative fixes with the same Message.
202+ {
203+ "duplicate message" ,
204+ `analyzer "a" suggests two fixes with same Message \(fix\)` ,
205+ func (pos token.Pos ) analysis.Diagnostic {
206+ return analysis.Diagnostic {
207+ Pos : pos ,
208+ Message : "oops" ,
209+ SuggestedFixes : []analysis.SuggestedFix {
210+ {Message : "fix" },
211+ {Message : "fix" },
212+ },
213+ }
214+ },
215+ },
216+ // TextEdit has invalid Pos.
217+ {
218+ "bad Pos" ,
219+ `analyzer "a" suggests invalid fix .*: missing file info for pos` ,
220+ func (pos token.Pos ) analysis.Diagnostic {
221+ return analysis.Diagnostic {
222+ Pos : pos ,
223+ Message : "oops" ,
224+ SuggestedFixes : []analysis.SuggestedFix {
225+ {
226+ Message : "fix" ,
227+ TextEdits : []analysis.TextEdit {{}},
228+ },
229+ },
230+ }
231+ },
232+ },
233+ // TextEdit has invalid End.
234+ {
235+ "End < Pos" ,
236+ `analyzer "a" suggests invalid fix .*: pos .* > end` ,
237+ func (pos token.Pos ) analysis.Diagnostic {
238+ return analysis.Diagnostic {
239+ Pos : pos ,
240+ Message : "oops" ,
241+ SuggestedFixes : []analysis.SuggestedFix {
242+ {
243+ Message : "fix" ,
244+ TextEdits : []analysis.TextEdit {{
245+ Pos : pos + 2 ,
246+ End : pos ,
247+ }},
248+ },
249+ },
250+ }
251+ },
252+ },
253+ // Two TextEdits overlap.
254+ {
255+ "overlapping edits" ,
256+ `analyzer "a" suggests invalid fix .*: overlapping edits to .*errors.go \(1:1-1:3 and 1:2-1:4\)` ,
257+ func (pos token.Pos ) analysis.Diagnostic {
258+ return analysis.Diagnostic {
259+ Pos : pos ,
260+ Message : "oops" ,
261+ SuggestedFixes : []analysis.SuggestedFix {
262+ {
263+ Message : "fix" ,
264+ TextEdits : []analysis.TextEdit {
265+ {Pos : pos , End : pos + 2 },
266+ {Pos : pos + 1 , End : pos + 3 },
267+ },
268+ },
269+ },
270+ }
271+ },
272+ },
273+ } {
274+ t .Run (test .name , func (t * testing.T ) {
275+ reached := false
276+ a := & analysis.Analyzer {Name : "a" , Doc : "doc" , Run : func (pass * analysis.Pass ) (any , error ) {
277+ reached = true
278+ panics (t , test .want , func () {
279+ pos := pass .Files [0 ].FileStart
280+ pass .Report (test .diag (pos ))
281+ })
282+ return nil , nil
283+ }}
284+ if _ , err := checker .Analyze ([]* analysis.Analyzer {a }, initial , & checker.Options {}); err != nil {
285+ t .Fatalf ("Analyze failed: %v" , err )
286+ }
287+ if ! reached {
288+ t .Error ("analyzer was never invoked" )
289+ }
290+ })
291+ }
292+ }
293+
199294// TestConflict ensures that checker.Run detects conflicts correctly.
200295// This test fork/execs the main function above.
201296func TestConflict (t * testing.T ) {
@@ -333,3 +428,17 @@ func init() {
333428 },
334429 }
335430}
431+
432+ // panics asserts that f() panics with with a value whose printed form matches the regexp want.
433+ func panics (t * testing.T , want string , f func ()) {
434+ defer func () {
435+ if x := recover (); x == nil {
436+ t .Errorf ("function returned normally, wanted panic" )
437+ } else if m , err := regexp .MatchString (want , fmt .Sprint (x )); err != nil {
438+ t .Errorf ("panics: invalid regexp %q" , want )
439+ } else if ! m {
440+ t .Errorf ("function panicked with value %q, want match for %q" , x , want )
441+ }
442+ }()
443+ f ()
444+ }
0 commit comments