golang.org/x/tools/gopls@v0.15.3/internal/test/integration/workspace/quickfix_test.go (about) 1 // Copyright 2023 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 workspace 6 7 import ( 8 "strings" 9 "testing" 10 11 "golang.org/x/tools/gopls/internal/protocol" 12 "golang.org/x/tools/gopls/internal/test/compare" 13 14 . "golang.org/x/tools/gopls/internal/test/integration" 15 ) 16 17 func TestQuickFix_UseModule(t *testing.T) { 18 t.Skip("temporary skip for golang/go#57979: with zero-config gopls these files are no longer orphaned") 19 20 const files = ` 21 -- go.work -- 22 go 1.20 23 24 use ( 25 ./a 26 ) 27 -- a/go.mod -- 28 module mod.com/a 29 30 go 1.18 31 32 -- a/main.go -- 33 package main 34 35 import "mod.com/a/lib" 36 37 func main() { 38 _ = lib.C 39 } 40 41 -- a/lib/lib.go -- 42 package lib 43 44 const C = "b" 45 -- b/go.mod -- 46 module mod.com/b 47 48 go 1.18 49 50 -- b/main.go -- 51 package main 52 53 import "mod.com/b/lib" 54 55 func main() { 56 _ = lib.C 57 } 58 59 -- b/lib/lib.go -- 60 package lib 61 62 const C = "b" 63 ` 64 65 for _, title := range []string{ 66 "Use this module", 67 "Use all modules", 68 } { 69 t.Run(title, func(t *testing.T) { 70 Run(t, files, func(t *testing.T, env *Env) { 71 env.OpenFile("b/main.go") 72 var d protocol.PublishDiagnosticsParams 73 env.AfterChange(ReadDiagnostics("b/main.go", &d)) 74 fixes := env.GetQuickFixes("b/main.go", d.Diagnostics) 75 var toApply []protocol.CodeAction 76 for _, fix := range fixes { 77 if strings.Contains(fix.Title, title) { 78 toApply = append(toApply, fix) 79 } 80 } 81 if len(toApply) != 1 { 82 t.Fatalf("codeAction: got %d quick fixes matching %q, want 1; got: %v", len(toApply), title, toApply) 83 } 84 env.ApplyCodeAction(toApply[0]) 85 env.AfterChange(NoDiagnostics()) 86 want := `go 1.20 87 88 use ( 89 ./a 90 ./b 91 ) 92 ` 93 got := env.ReadWorkspaceFile("go.work") 94 if diff := compare.Text(want, got); diff != "" { 95 t.Errorf("unexpeced go.work content:\n%s", diff) 96 } 97 }) 98 }) 99 } 100 } 101 102 func TestQuickFix_AddGoWork(t *testing.T) { 103 t.Skip("temporary skip for golang/go#57979: with zero-config gopls these files are no longer orphaned") 104 105 const files = ` 106 -- a/go.mod -- 107 module mod.com/a 108 109 go 1.18 110 111 -- a/main.go -- 112 package main 113 114 import "mod.com/a/lib" 115 116 func main() { 117 _ = lib.C 118 } 119 120 -- a/lib/lib.go -- 121 package lib 122 123 const C = "b" 124 -- b/go.mod -- 125 module mod.com/b 126 127 go 1.18 128 129 -- b/main.go -- 130 package main 131 132 import "mod.com/b/lib" 133 134 func main() { 135 _ = lib.C 136 } 137 138 -- b/lib/lib.go -- 139 package lib 140 141 const C = "b" 142 ` 143 144 tests := []struct { 145 name string 146 file string 147 title string 148 want string // expected go.work content, excluding go directive line 149 }{ 150 { 151 "use b", 152 "b/main.go", 153 "Add a go.work file using this module", 154 ` 155 use ./b 156 `, 157 }, 158 { 159 "use a", 160 "a/main.go", 161 "Add a go.work file using this module", 162 ` 163 use ./a 164 `, 165 }, 166 { 167 "use all", 168 "a/main.go", 169 "Add a go.work file using all modules", 170 ` 171 use ( 172 ./a 173 ./b 174 ) 175 `, 176 }, 177 } 178 179 for _, test := range tests { 180 t.Run(test.name, func(t *testing.T) { 181 Run(t, files, func(t *testing.T, env *Env) { 182 env.OpenFile(test.file) 183 var d protocol.PublishDiagnosticsParams 184 env.AfterChange(ReadDiagnostics(test.file, &d)) 185 fixes := env.GetQuickFixes(test.file, d.Diagnostics) 186 var toApply []protocol.CodeAction 187 for _, fix := range fixes { 188 if strings.Contains(fix.Title, test.title) { 189 toApply = append(toApply, fix) 190 } 191 } 192 if len(toApply) != 1 { 193 t.Fatalf("codeAction: got %d quick fixes matching %q, want 1; got: %v", len(toApply), test.title, toApply) 194 } 195 env.ApplyCodeAction(toApply[0]) 196 env.AfterChange( 197 NoDiagnostics(ForFile(test.file)), 198 ) 199 200 got := env.ReadWorkspaceFile("go.work") 201 // Ignore the `go` directive, which we assume is on the first line of 202 // the go.work file. This allows the test to be independent of go version. 203 got = strings.Join(strings.Split(got, "\n")[1:], "\n") 204 if diff := compare.Text(test.want, got); diff != "" { 205 t.Errorf("unexpected go.work content:\n%s", diff) 206 } 207 }) 208 }) 209 } 210 } 211 212 func TestQuickFix_UnsavedGoWork(t *testing.T) { 213 t.Skip("temporary skip for golang/go#57979: with zero-config gopls these files are no longer orphaned") 214 215 const files = ` 216 -- go.work -- 217 go 1.21 218 219 use ( 220 ./a 221 ) 222 -- a/go.mod -- 223 module mod.com/a 224 225 go 1.18 226 227 -- a/main.go -- 228 package main 229 230 func main() {} 231 -- b/go.mod -- 232 module mod.com/b 233 234 go 1.18 235 236 -- b/main.go -- 237 package main 238 239 func main() {} 240 ` 241 242 for _, title := range []string{ 243 "Use this module", 244 "Use all modules", 245 } { 246 t.Run(title, func(t *testing.T) { 247 Run(t, files, func(t *testing.T, env *Env) { 248 env.OpenFile("go.work") 249 env.OpenFile("b/main.go") 250 env.RegexpReplace("go.work", "go 1.21", "go 1.21 // arbitrary comment") 251 var d protocol.PublishDiagnosticsParams 252 env.AfterChange(ReadDiagnostics("b/main.go", &d)) 253 fixes := env.GetQuickFixes("b/main.go", d.Diagnostics) 254 var toApply []protocol.CodeAction 255 for _, fix := range fixes { 256 if strings.Contains(fix.Title, title) { 257 toApply = append(toApply, fix) 258 } 259 } 260 if len(toApply) != 1 { 261 t.Fatalf("codeAction: got %d quick fixes matching %q, want 1; got: %v", len(toApply), title, toApply) 262 } 263 fix := toApply[0] 264 err := env.Editor.ApplyCodeAction(env.Ctx, fix) 265 if err == nil { 266 t.Fatalf("codeAction(%q) succeeded unexpectedly", fix.Title) 267 } 268 269 if got := err.Error(); !strings.Contains(got, "must save") { 270 t.Errorf("codeAction(%q) returned error %q, want containing \"must save\"", fix.Title, err) 271 } 272 }) 273 }) 274 } 275 } 276 277 func TestQuickFix_GOWORKOff(t *testing.T) { 278 t.Skip("temporary skip for golang/go#57979: with zero-config gopls these files are no longer orphaned") 279 280 const files = ` 281 -- go.work -- 282 go 1.21 283 284 use ( 285 ./a 286 ) 287 -- a/go.mod -- 288 module mod.com/a 289 290 go 1.18 291 292 -- a/main.go -- 293 package main 294 295 func main() {} 296 -- b/go.mod -- 297 module mod.com/b 298 299 go 1.18 300 301 -- b/main.go -- 302 package main 303 304 func main() {} 305 ` 306 307 for _, title := range []string{ 308 "Use this module", 309 "Use all modules", 310 } { 311 t.Run(title, func(t *testing.T) { 312 WithOptions( 313 EnvVars{"GOWORK": "off"}, 314 ).Run(t, files, func(t *testing.T, env *Env) { 315 env.OpenFile("go.work") 316 env.OpenFile("b/main.go") 317 var d protocol.PublishDiagnosticsParams 318 env.AfterChange(ReadDiagnostics("b/main.go", &d)) 319 fixes := env.GetQuickFixes("b/main.go", d.Diagnostics) 320 var toApply []protocol.CodeAction 321 for _, fix := range fixes { 322 if strings.Contains(fix.Title, title) { 323 toApply = append(toApply, fix) 324 } 325 } 326 if len(toApply) != 1 { 327 t.Fatalf("codeAction: got %d quick fixes matching %q, want 1; got: %v", len(toApply), title, toApply) 328 } 329 fix := toApply[0] 330 err := env.Editor.ApplyCodeAction(env.Ctx, fix) 331 if err == nil { 332 t.Fatalf("codeAction(%q) succeeded unexpectedly", fix.Title) 333 } 334 335 if got := err.Error(); !strings.Contains(got, "GOWORK=off") { 336 t.Errorf("codeAction(%q) returned error %q, want containing \"GOWORK=off\"", fix.Title, err) 337 } 338 }) 339 }) 340 } 341 } 342 343 func TestStubMethods64087(t *testing.T) { 344 // We can't use the @fix or @suggestedfixerr or @codeactionerr 345 // because the error now reported by the corrected logic 346 // is internal and silently causes no fix to be offered. 347 // 348 // See also the similar TestStubMethods64545 below. 349 350 const files = ` 351 This is a regression test for a panic (issue #64087) in stub methods. 352 353 The illegal expression int("") caused a "cannot convert" error that 354 spuriously triggered the "stub methods" in a function whose return 355 statement had too many operands, leading to an out-of-bounds index. 356 357 -- go.mod -- 358 module mod.com 359 go 1.18 360 361 -- a.go -- 362 package a 363 364 func f() error { 365 return nil, myerror{int("")} 366 } 367 368 type myerror struct{any} 369 ` 370 Run(t, files, func(t *testing.T, env *Env) { 371 env.OpenFile("a.go") 372 373 // Expect a "wrong result count" diagnostic. 374 var d protocol.PublishDiagnosticsParams 375 env.AfterChange(ReadDiagnostics("a.go", &d)) 376 377 // In no particular order, we expect: 378 // "...too many return values..." (compiler) 379 // "...cannot convert..." (compiler) 380 // and possibly: 381 // "...too many return values..." (fillreturns) 382 // We check only for the first of these. 383 found := false 384 for i, diag := range d.Diagnostics { 385 t.Logf("Diagnostics[%d] = %q (%s)", i, diag.Message, diag.Source) 386 if strings.Contains(diag.Message, "too many return") { 387 found = true 388 } 389 } 390 if !found { 391 t.Fatalf("Expected WrongResultCount diagnostic not found.") 392 } 393 394 // GetQuickFixes should not panic (the original bug). 395 fixes := env.GetQuickFixes("a.go", d.Diagnostics) 396 397 // We should not be offered a "stub methods" fix. 398 for _, fix := range fixes { 399 if strings.Contains(fix.Title, "Implement error") { 400 t.Errorf("unexpected 'stub methods' fix: %#v", fix) 401 } 402 } 403 }) 404 } 405 406 func TestStubMethods64545(t *testing.T) { 407 // We can't use the @fix or @suggestedfixerr or @codeactionerr 408 // because the error now reported by the corrected logic 409 // is internal and silently causes no fix to be offered. 410 // 411 // TODO(adonovan): we may need to generalize this test and 412 // TestStubMethods64087 if this happens a lot. 413 414 const files = ` 415 This is a regression test for a panic (issue #64545) in stub methods. 416 417 The illegal expression int("") caused a "cannot convert" error that 418 spuriously triggered the "stub methods" in a function whose var 419 spec had no RHS values, leading to an out-of-bounds index. 420 421 -- go.mod -- 422 module mod.com 423 go 1.18 424 425 -- a.go -- 426 package a 427 428 var _ [int("")]byte 429 ` 430 Run(t, files, func(t *testing.T, env *Env) { 431 env.OpenFile("a.go") 432 433 // Expect a "cannot convert" diagnostic, and perhaps others. 434 var d protocol.PublishDiagnosticsParams 435 env.AfterChange(ReadDiagnostics("a.go", &d)) 436 437 found := false 438 for i, diag := range d.Diagnostics { 439 t.Logf("Diagnostics[%d] = %q (%s)", i, diag.Message, diag.Source) 440 if strings.Contains(diag.Message, "cannot convert") { 441 found = true 442 } 443 } 444 if !found { 445 t.Fatalf("Expected 'cannot convert' diagnostic not found.") 446 } 447 448 // GetQuickFixes should not panic (the original bug). 449 fixes := env.GetQuickFixes("a.go", d.Diagnostics) 450 451 // We should not be offered a "stub methods" fix. 452 for _, fix := range fixes { 453 if strings.Contains(fix.Title, "Implement error") { 454 t.Errorf("unexpected 'stub methods' fix: %#v", fix) 455 } 456 } 457 }) 458 }