1// Copyright 2020 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 5package regtest 6 7import ( 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "golang.org/x/tools/internal/lsp" 13 "golang.org/x/tools/internal/lsp/protocol" 14 "golang.org/x/tools/internal/lsp/tests" 15 "golang.org/x/tools/internal/testenv" 16) 17 18const proxy = ` 19-- example.com@v1.2.3/go.mod -- 20module example.com 21 22go 1.12 23-- example.com@v1.2.3/blah/blah.go -- 24package blah 25 26const Name = "Blah" 27-- random.org@v1.2.3/go.mod -- 28module random.org 29 30go 1.12 31-- random.org@v1.2.3/blah/blah.go -- 32package hello 33 34const Name = "Hello" 35` 36 37func TestModFileModification(t *testing.T) { 38 testenv.NeedsGo1Point(t, 14) 39 40 const untidyModule = ` 41-- a/go.mod -- 42module mod.com 43 44-- a/main.go -- 45package main 46 47import "example.com/blah" 48 49func main() { 50 println(blah.Name) 51} 52` 53 54 runner := runMultiple{ 55 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 56 {"nested", withOptions(ProxyFiles(proxy))}, 57 } 58 59 t.Run("basic", func(t *testing.T) { 60 runner.run(t, untidyModule, func(t *testing.T, env *Env) { 61 // Open the file and make sure that the initial workspace load does not 62 // modify the go.mod file. 63 goModContent := env.ReadWorkspaceFile("a/go.mod") 64 env.OpenFile("a/main.go") 65 env.Await( 66 env.DiagnosticAtRegexp("a/main.go", "\"example.com/blah\""), 67 ) 68 if got := env.ReadWorkspaceFile("a/go.mod"); got != goModContent { 69 t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(t, goModContent, got)) 70 } 71 // Save the buffer, which will format and organize imports. 72 // Confirm that the go.mod file still does not change. 73 env.SaveBuffer("a/main.go") 74 env.Await( 75 env.DiagnosticAtRegexp("a/main.go", "\"example.com/blah\""), 76 ) 77 if got := env.ReadWorkspaceFile("a/go.mod"); got != goModContent { 78 t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(t, goModContent, got)) 79 } 80 }) 81 }) 82 83 // Reproduce golang/go#40269 by deleting and recreating main.go. 84 t.Run("delete main.go", func(t *testing.T) { 85 t.Skip("This test will be flaky until golang/go#40269 is resolved.") 86 87 runner.run(t, untidyModule, func(t *testing.T, env *Env) { 88 goModContent := env.ReadWorkspaceFile("a/go.mod") 89 mainContent := env.ReadWorkspaceFile("a/main.go") 90 env.OpenFile("a/main.go") 91 env.SaveBuffer("a/main.go") 92 93 env.RemoveWorkspaceFile("a/main.go") 94 env.Await( 95 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1), 96 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidSave), 1), 97 CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 2), 98 ) 99 100 env.WriteWorkspaceFile("main.go", mainContent) 101 env.Await( 102 env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""), 103 ) 104 if got := env.ReadWorkspaceFile("go.mod"); got != goModContent { 105 t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(t, goModContent, got)) 106 } 107 }) 108 }) 109} 110 111func TestGoGetFix(t *testing.T) { 112 testenv.NeedsGo1Point(t, 14) 113 const mod = ` 114-- a/go.mod -- 115module mod.com 116 117go 1.12 118 119-- a/main.go -- 120package main 121 122import "example.com/blah" 123 124var _ = blah.Name 125` 126 127 const want = `module mod.com 128 129go 1.12 130 131require example.com v1.2.3 132` 133 134 runMultiple{ 135 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 136 {"nested", withOptions(ProxyFiles(proxy))}, 137 }.run(t, mod, func(t *testing.T, env *Env) { 138 if strings.Contains(t.Name(), "workspace_module") { 139 t.Skip("workspace module mode doesn't set -mod=readonly") 140 } 141 env.OpenFile("a/main.go") 142 var d protocol.PublishDiagnosticsParams 143 env.Await( 144 OnceMet( 145 env.DiagnosticAtRegexp("a/main.go", `"example.com/blah"`), 146 ReadDiagnostics("a/main.go", &d), 147 ), 148 ) 149 var goGetDiag protocol.Diagnostic 150 for _, diag := range d.Diagnostics { 151 if strings.Contains(diag.Message, "could not import") { 152 goGetDiag = diag 153 } 154 } 155 env.ApplyQuickFixes("a/main.go", []protocol.Diagnostic{goGetDiag}) 156 if got := env.ReadWorkspaceFile("a/go.mod"); got != want { 157 t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got)) 158 } 159 }) 160} 161 162// Tests that multiple missing dependencies gives good single fixes. 163func TestMissingDependencyFixes(t *testing.T) { 164 testenv.NeedsGo1Point(t, 14) 165 const mod = ` 166-- a/go.mod -- 167module mod.com 168 169go 1.12 170 171-- a/main.go -- 172package main 173 174import "example.com/blah" 175import "random.org/blah" 176 177var _, _ = blah.Name, hello.Name 178` 179 180 const want = `module mod.com 181 182go 1.12 183 184require random.org v1.2.3 185` 186 187 runMultiple{ 188 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 189 {"nested", withOptions(ProxyFiles(proxy))}, 190 }.run(t, mod, func(t *testing.T, env *Env) { 191 env.OpenFile("a/main.go") 192 var d protocol.PublishDiagnosticsParams 193 env.Await( 194 OnceMet( 195 env.DiagnosticAtRegexp("a/main.go", `"random.org/blah"`), 196 ReadDiagnostics("a/main.go", &d), 197 ), 198 ) 199 var randomDiag protocol.Diagnostic 200 for _, diag := range d.Diagnostics { 201 if strings.Contains(diag.Message, "random.org") { 202 randomDiag = diag 203 } 204 } 205 env.ApplyQuickFixes("a/main.go", []protocol.Diagnostic{randomDiag}) 206 if got := env.ReadWorkspaceFile("a/go.mod"); got != want { 207 t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got)) 208 } 209 }) 210} 211 212func TestIndirectDependencyFix(t *testing.T) { 213 testenv.NeedsGo1Point(t, 14) 214 215 const mod = ` 216-- a/go.mod -- 217module mod.com 218 219go 1.12 220 221require example.com v1.2.3 // indirect 222-- a/go.sum -- 223example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= 224example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 225-- a/main.go -- 226package main 227 228import "example.com/blah" 229 230func main() { 231 fmt.Println(blah.Name) 232` 233 const want = `module mod.com 234 235go 1.12 236 237require example.com v1.2.3 238` 239 240 runMultiple{ 241 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 242 {"nested", withOptions(ProxyFiles(proxy))}, 243 }.run(t, mod, func(t *testing.T, env *Env) { 244 env.OpenFile("a/go.mod") 245 var d protocol.PublishDiagnosticsParams 246 env.Await( 247 OnceMet( 248 env.DiagnosticAtRegexp("a/go.mod", "// indirect"), 249 ReadDiagnostics("a/go.mod", &d), 250 ), 251 ) 252 env.ApplyQuickFixes("a/go.mod", d.Diagnostics) 253 if got := env.Editor.BufferText("a/go.mod"); got != want { 254 t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got)) 255 } 256 }) 257} 258 259func TestUnusedDiag(t *testing.T) { 260 testenv.NeedsGo1Point(t, 14) 261 262 const proxy = ` 263-- example.com@v1.0.0/x.go -- 264package pkg 265const X = 1 266` 267 const files = ` 268-- a/go.mod -- 269module mod.com 270go 1.14 271require example.com v1.0.0 272-- a/go.sum -- 273example.com v1.0.0 h1:38O7j5rEBajXk+Q5wzLbRN7KqMkSgEiN9NqcM1O2bBM= 274example.com v1.0.0/go.mod h1:vUsPMGpx9ZXXzECCOsOmYCW7npJTwuA16yl89n3Mgls= 275-- a/main.go -- 276package main 277func main() {} 278` 279 280 const want = `module mod.com 281 282go 1.14 283` 284 285 runMultiple{ 286 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 287 {"nested", withOptions(ProxyFiles(proxy))}, 288 }.run(t, files, func(t *testing.T, env *Env) { 289 env.OpenFile("a/go.mod") 290 var d protocol.PublishDiagnosticsParams 291 env.Await( 292 OnceMet( 293 env.DiagnosticAtRegexp("a/go.mod", `require example.com`), 294 ReadDiagnostics("a/go.mod", &d), 295 ), 296 ) 297 env.ApplyQuickFixes("a/go.mod", d.Diagnostics) 298 if got := env.ReadWorkspaceFile("a/go.mod"); got != want { 299 t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got)) 300 } 301 }) 302} 303 304// Test to reproduce golang/go#39041. It adds a new require to a go.mod file 305// that already has an unused require. 306func TestNewDepWithUnusedDep(t *testing.T) { 307 testenv.NeedsGo1Point(t, 14) 308 309 const proxy = ` 310-- github.com/esimov/caire@v1.2.5/go.mod -- 311module github.com/esimov/caire 312 313go 1.12 314-- github.com/esimov/caire@v1.2.5/caire.go -- 315package caire 316 317func RemoveTempImage() {} 318-- google.golang.org/protobuf@v1.20.0/go.mod -- 319module google.golang.org/protobuf 320 321go 1.12 322-- google.golang.org/protobuf@v1.20.0/hello/hello.go -- 323package hello 324` 325 const repro = ` 326-- a/go.mod -- 327module mod.com 328 329go 1.14 330 331require google.golang.org/protobuf v1.20.0 332-- a/go.sum -- 333github.com/esimov/caire v1.2.5 h1:OcqDII/BYxcBYj3DuwDKjd+ANhRxRqLa2n69EGje7qw= 334github.com/esimov/caire v1.2.5/go.mod h1:mXnjRjg3+WUtuhfSC1rKRmdZU9vJZyS1ZWU0qSvJhK8= 335google.golang.org/protobuf v1.20.0 h1:y9T1vAtFKQg0faFNMOxJU7WuEqPWolVkjIkU6aI8qCY= 336google.golang.org/protobuf v1.20.0/go.mod h1:FcqsytGClbtLv1ot8NvsJHjBi0h22StKVP+K/j2liKA= 337-- a/main.go -- 338package main 339 340import ( 341 "github.com/esimov/caire" 342) 343 344func _() { 345 caire.RemoveTempImage() 346}` 347 348 runMultiple{ 349 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 350 {"nested", withOptions(ProxyFiles(proxy))}, 351 }.run(t, repro, func(t *testing.T, env *Env) { 352 env.OpenFile("a/main.go") 353 var d protocol.PublishDiagnosticsParams 354 env.Await( 355 OnceMet( 356 env.DiagnosticAtRegexp("a/main.go", `"github.com/esimov/caire"`), 357 ReadDiagnostics("a/main.go", &d), 358 ), 359 ) 360 env.ApplyQuickFixes("a/main.go", d.Diagnostics) 361 want := `module mod.com 362 363go 1.14 364 365require ( 366 github.com/esimov/caire v1.2.5 367 google.golang.org/protobuf v1.20.0 368) 369` 370 if got := env.ReadWorkspaceFile("a/go.mod"); got != want { 371 t.Fatalf("TestNewDepWithUnusedDep failed:\n%s", tests.Diff(t, want, got)) 372 } 373 }) 374} 375 376// TODO: For this test to be effective, the sandbox's file watcher must respect 377// the file watching GlobPattern in the capability registration. See 378// golang/go#39384. 379func TestModuleChangesOnDisk(t *testing.T) { 380 testenv.NeedsGo1Point(t, 14) 381 382 const mod = ` 383-- a/go.mod -- 384module mod.com 385 386go 1.12 387 388require example.com v1.2.3 389-- a/go.sum -- 390example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= 391example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 392-- a/main.go -- 393package main 394 395func main() { 396 fmt.Println(blah.Name) 397` 398 runMultiple{ 399 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 400 {"nested", withOptions(ProxyFiles(proxy))}, 401 }.run(t, mod, func(t *testing.T, env *Env) { 402 env.Await(env.DiagnosticAtRegexp("a/go.mod", "require")) 403 env.RunGoCommandInDir("a", "mod", "tidy") 404 env.Await( 405 EmptyDiagnostics("a/go.mod"), 406 ) 407 }) 408} 409 410// Tests golang/go#39784: a missing indirect dependency, necessary 411// due to blah@v2.0.0's incomplete go.mod file. 412func TestBadlyVersionedModule(t *testing.T) { 413 testenv.NeedsGo1Point(t, 14) 414 415 const proxy = ` 416-- example.com/blah/@v/v1.0.0.mod -- 417module example.com 418 419go 1.12 420-- example.com/blah@v1.0.0/blah.go -- 421package blah 422 423const Name = "Blah" 424-- example.com/blah/v2/@v/v2.0.0.mod -- 425module example.com 426 427go 1.12 428-- example.com/blah/v2@v2.0.0/blah.go -- 429package blah 430 431import "example.com/blah" 432 433var _ = blah.Name 434const Name = "Blah" 435` 436 const files = ` 437-- a/go.mod -- 438module mod.com 439 440go 1.12 441 442require example.com/blah/v2 v2.0.0 443-- a/go.sum -- 444example.com/blah v1.0.0 h1:kGPlWJbMsn1P31H9xp/q2mYI32cxLnCvauHN0AVaHnc= 445example.com/blah v1.0.0/go.mod h1:PZUQaGFeVjyDmAE8ywmLbmDn3fj4Ws8epg4oLuDzW3M= 446example.com/blah/v2 v2.0.0 h1:w5baE9JuuU11s3de3yWx2sU05AhNkgLYdZ4qukv+V0k= 447example.com/blah/v2 v2.0.0/go.mod h1:UZiKbTwobERo/hrqFLvIQlJwQZQGxWMVY4xere8mj7w= 448-- a/main.go -- 449package main 450 451import "example.com/blah/v2" 452 453var _ = blah.Name 454` 455 runMultiple{ 456 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 457 {"nested", withOptions(ProxyFiles(proxy))}, 458 }.run(t, files, func(t *testing.T, env *Env) { 459 env.OpenFile("a/main.go") 460 env.OpenFile("a/go.mod") 461 var d protocol.PublishDiagnosticsParams 462 env.Await( 463 OnceMet( 464 DiagnosticAt("a/go.mod", 0, 0), 465 ReadDiagnostics("a/go.mod", &d), 466 ), 467 ) 468 env.ApplyQuickFixes("a/main.go", d.Diagnostics) 469 const want = `module mod.com 470 471go 1.12 472 473require ( 474 example.com/blah v1.0.0 // indirect 475 example.com/blah/v2 v2.0.0 476) 477` 478 env.Await(EmptyDiagnostics("a/go.mod")) 479 if got := env.Editor.BufferText("a/go.mod"); got != want { 480 t.Fatalf("suggested fixes failed:\n%s", tests.Diff(t, want, got)) 481 } 482 }) 483} 484 485// Reproduces golang/go#38232. 486func TestUnknownRevision(t *testing.T) { 487 testenv.NeedsGo1Point(t, 14) 488 489 const unknown = ` 490-- a/go.mod -- 491module mod.com 492 493require ( 494 example.com v1.2.2 495) 496-- a/main.go -- 497package main 498 499import "example.com/blah" 500 501func main() { 502 var x = blah.Name 503} 504` 505 506 runner := runMultiple{ 507 {"default", withOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, 508 {"nested", withOptions(ProxyFiles(proxy))}, 509 } 510 // Start from a bad state/bad IWL, and confirm that we recover. 511 t.Run("bad", func(t *testing.T) { 512 runner.run(t, unknown, func(t *testing.T, env *Env) { 513 env.OpenFile("a/go.mod") 514 env.Await( 515 env.DiagnosticAtRegexp("a/go.mod", "example.com v1.2.2"), 516 ) 517 env.RegexpReplace("a/go.mod", "v1.2.2", "v1.2.3") 518 env.Editor.SaveBuffer(env.Ctx, "a/go.mod") // go.mod changes must be on disk 519 520 d := protocol.PublishDiagnosticsParams{} 521 env.Await( 522 OnceMet( 523 env.DiagnosticAtRegexpWithMessage("a/go.mod", "example.com v1.2.3", "example.com@v1.2.3"), 524 ReadDiagnostics("a/go.mod", &d), 525 ), 526 ) 527 env.ApplyQuickFixes("a/go.mod", d.Diagnostics) 528 529 env.Await( 530 EmptyDiagnostics("a/go.mod"), 531 env.DiagnosticAtRegexp("a/main.go", "x = "), 532 ) 533 }) 534 }) 535 536 const known = ` 537-- a/go.mod -- 538module mod.com 539 540require ( 541 example.com v1.2.3 542) 543-- a/go.sum -- 544example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= 545example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 546-- a/main.go -- 547package main 548 549import "example.com/blah" 550 551func main() { 552 var x = blah.Name 553} 554` 555 // Start from a good state, transform to a bad state, and confirm that we 556 // still recover. 557 t.Run("good", func(t *testing.T) { 558 runner.run(t, known, func(t *testing.T, env *Env) { 559 env.OpenFile("a/go.mod") 560 env.Await( 561 env.DiagnosticAtRegexp("a/main.go", "x = "), 562 ) 563 env.RegexpReplace("a/go.mod", "v1.2.3", "v1.2.2") 564 env.Editor.SaveBuffer(env.Ctx, "a/go.mod") // go.mod changes must be on disk 565 env.Await( 566 env.DiagnosticAtRegexp("a/go.mod", "example.com v1.2.2"), 567 ) 568 env.RegexpReplace("a/go.mod", "v1.2.2", "v1.2.3") 569 env.Editor.SaveBuffer(env.Ctx, "a/go.mod") // go.mod changes must be on disk 570 env.Await( 571 env.DiagnosticAtRegexp("a/main.go", "x = "), 572 ) 573 }) 574 }) 575} 576 577// Confirm that an error in an indirect dependency of a requirement is surfaced 578// as a diagnostic in the go.mod file. 579func TestErrorInIndirectDependency(t *testing.T) { 580 testenv.NeedsGo1Point(t, 14) 581 582 const badProxy = ` 583-- example.com@v1.2.3/go.mod -- 584module example.com 585 586go 1.12 587 588require random.org v1.2.3 // indirect 589-- example.com@v1.2.3/blah/blah.go -- 590package blah 591 592const Name = "Blah" 593-- random.org@v1.2.3/go.mod -- 594module bob.org 595 596go 1.12 597-- random.org@v1.2.3/blah/blah.go -- 598package hello 599 600const Name = "Hello" 601` 602 const module = ` 603-- a/go.mod -- 604module mod.com 605 606go 1.14 607 608require example.com v1.2.3 609-- a/main.go -- 610package main 611 612import "example.com/blah" 613 614func main() { 615 println(blah.Name) 616} 617` 618 runMultiple{ 619 {"default", withOptions(ProxyFiles(badProxy), WorkspaceFolders("a"))}, 620 {"nested", withOptions(ProxyFiles(badProxy))}, 621 }.run(t, module, func(t *testing.T, env *Env) { 622 env.OpenFile("a/go.mod") 623 env.Await( 624 env.DiagnosticAtRegexp("a/go.mod", "require example.com v1.2.3"), 625 ) 626 }) 627} 628 629// A copy of govim's config_set_env_goflags_mod_readonly test. 630func TestGovimModReadonly(t *testing.T) { 631 const mod = ` 632-- go.mod -- 633module mod.com 634 635go 1.13 636-- main.go -- 637package main 638 639import "example.com/blah" 640 641func main() { 642 println(blah.Name) 643} 644` 645 withOptions( 646 EditorConfig{ 647 Env: map[string]string{ 648 "GOFLAGS": "-mod=readonly", 649 }, 650 }, 651 ProxyFiles(proxy), 652 Modes(Singleton), 653 ).run(t, mod, func(t *testing.T, env *Env) { 654 env.OpenFile("main.go") 655 original := env.ReadWorkspaceFile("go.mod") 656 env.Await( 657 env.DiagnosticAtRegexp("main.go", `"example.com/blah"`), 658 ) 659 got := env.ReadWorkspaceFile("go.mod") 660 if got != original { 661 t.Fatalf("go.mod file modified:\n%s", tests.Diff(t, original, got)) 662 } 663 env.RunGoCommand("get", "example.com/blah@v1.2.3") 664 env.RunGoCommand("mod", "tidy") 665 env.Await( 666 EmptyDiagnostics("main.go"), 667 ) 668 }) 669} 670 671func TestMultiModuleModDiagnostics(t *testing.T) { 672 testenv.NeedsGo1Point(t, 14) 673 674 const mod = ` 675-- a/go.mod -- 676module mod.com 677 678go 1.14 679 680require ( 681 example.com v1.2.3 682) 683-- a/main.go -- 684package main 685 686func main() {} 687-- b/go.mod -- 688module mod.com 689 690go 1.14 691-- b/main.go -- 692package main 693 694import "example.com/blah" 695 696func main() { 697 blah.SaySomething() 698} 699` 700 withOptions( 701 ProxyFiles(workspaceProxy), 702 Modes(Experimental), 703 ).run(t, mod, func(t *testing.T, env *Env) { 704 env.Await( 705 env.DiagnosticAtRegexp("a/go.mod", "example.com v1.2.3"), 706 env.DiagnosticAtRegexp("b/go.mod", "module mod.com"), 707 ) 708 }) 709} 710 711func TestModTidyWithBuildTags(t *testing.T) { 712 testenv.NeedsGo1Point(t, 14) 713 714 const mod = ` 715-- go.mod -- 716module mod.com 717 718go 1.14 719-- main.go -- 720// +build bob 721 722package main 723 724import "example.com/blah" 725 726func main() { 727 blah.SaySomething() 728} 729` 730 withOptions( 731 ProxyFiles(workspaceProxy), 732 EditorConfig{ 733 BuildFlags: []string{"-tags", "bob"}, 734 }, 735 ).run(t, mod, func(t *testing.T, env *Env) { 736 env.Await( 737 env.DiagnosticAtRegexp("main.go", `"example.com/blah"`), 738 ) 739 }) 740} 741 742func TestModTypoDiagnostic(t *testing.T) { 743 const mod = ` 744-- go.mod -- 745module mod.com 746 747go 1.12 748-- main.go -- 749package main 750 751func main() {} 752` 753 run(t, mod, func(t *testing.T, env *Env) { 754 env.OpenFile("go.mod") 755 env.RegexpReplace("go.mod", "module", "modul") 756 env.Await( 757 env.DiagnosticAtRegexp("go.mod", "modul"), 758 ) 759 }) 760} 761 762func TestSumUpdateFixesDiagnostics(t *testing.T) { 763 testenv.NeedsGo1Point(t, 14) 764 765 const mod = ` 766-- go.mod -- 767module mod.com 768 769go 1.12 770 771require ( 772 example.com v1.2.3 773) 774-- go.sum -- 775-- main.go -- 776package main 777 778import ( 779 "example.com/blah" 780) 781 782func main() { 783 println(blah.Name) 784} 785` 786 withOptions( 787 Modes(Singleton), // workspace modules don't use -mod=readonly (golang/go#43346) 788 ProxyFiles(workspaceProxy), 789 ).run(t, mod, func(t *testing.T, env *Env) { 790 d := &protocol.PublishDiagnosticsParams{} 791 env.OpenFile("go.mod") 792 env.Await( 793 OnceMet( 794 DiagnosticAt("go.mod", 0, 0), 795 ReadDiagnostics("go.mod", d), 796 ), 797 ) 798 env.ApplyQuickFixes("go.mod", d.Diagnostics) 799 env.Await( 800 EmptyDiagnostics("go.mod"), 801 ) 802 }) 803} 804 805// This test confirms that editing a go.mod file only causes metadata 806// to be invalidated when it's saved. 807func TestGoModInvalidatesOnSave(t *testing.T) { 808 const mod = ` 809-- go.mod -- 810module mod.com 811 812go 1.12 813-- main.go -- 814package main 815 816func main() { 817 hello() 818} 819-- hello.go -- 820package main 821 822func hello() {} 823` 824 withOptions( 825 // TODO(rFindley) this doesn't work in multi-module workspace mode, because 826 // it keeps around the last parsing modfile. Update this test to also 827 // exercise the workspace module. 828 Modes(Singleton), 829 ).run(t, mod, func(t *testing.T, env *Env) { 830 env.OpenFile("go.mod") 831 env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1)) 832 env.RegexpReplace("go.mod", "module", "modul") 833 // Confirm that we still have metadata with only on-disk edits. 834 env.OpenFile("main.go") 835 file, _ := env.GoToDefinition("main.go", env.RegexpSearch("main.go", "hello")) 836 if filepath.Base(file) != "hello.go" { 837 t.Fatalf("expected definition in hello.go, got %s", file) 838 } 839 // Confirm that we no longer have metadata when the file is saved. 840 env.SaveBufferWithoutActions("go.mod") 841 _, _, err := env.Editor.GoToDefinition(env.Ctx, "main.go", env.RegexpSearch("main.go", "hello")) 842 if err == nil { 843 t.Fatalf("expected error, got none") 844 } 845 }) 846} 847 848func TestRemoveUnusedDependency(t *testing.T) { 849 testenv.NeedsGo1Point(t, 14) 850 851 const proxy = ` 852-- hasdep.com@v1.2.3/go.mod -- 853module hasdep.com 854 855go 1.12 856 857require example.com v1.2.3 858-- hasdep.com@v1.2.3/a/a.go -- 859package a 860-- example.com@v1.2.3/go.mod -- 861module example.com 862 863go 1.12 864-- example.com@v1.2.3/blah/blah.go -- 865package blah 866 867const Name = "Blah" 868-- random.com@v1.2.3/go.mod -- 869module random.com 870 871go 1.12 872-- random.com@v1.2.3/blah/blah.go -- 873package blah 874 875const Name = "Blah" 876` 877 t.Run("almost tidied", func(t *testing.T) { 878 const mod = ` 879-- go.mod -- 880module mod.com 881 882go 1.12 883 884require hasdep.com v1.2.3 885-- go.sum -- 886example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= 887example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 888hasdep.com v1.2.3 h1:00y+N5oD+SpKoqV1zP2VOPawcW65Zb9NebANY3GSzGI= 889hasdep.com v1.2.3/go.mod h1:ePVZOlez+KZEOejfLPGL2n4i8qiAjrkhQZ4wcImqAes= 890-- main.go -- 891package main 892 893func main() {} 894` 895 withOptions( 896 ProxyFiles(proxy), 897 ).run(t, mod, func(t *testing.T, env *Env) { 898 d := &protocol.PublishDiagnosticsParams{} 899 env.Await( 900 OnceMet( 901 env.DiagnosticAtRegexp("go.mod", "require hasdep.com v1.2.3"), 902 ReadDiagnostics("go.mod", d), 903 ), 904 ) 905 const want = `module mod.com 906 907go 1.12 908` 909 env.ApplyQuickFixes("go.mod", d.Diagnostics) 910 if got := env.ReadWorkspaceFile("go.mod"); got != want { 911 t.Fatalf("unexpected content in go.mod:\n%s", tests.Diff(t, want, got)) 912 } 913 }) 914 }) 915 916 t.Run("not tidied", func(t *testing.T) { 917 const mod = ` 918-- go.mod -- 919module mod.com 920 921go 1.12 922 923require hasdep.com v1.2.3 924require random.com v1.2.3 925-- go.sum -- 926example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= 927example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 928hasdep.com v1.2.3 h1:00y+N5oD+SpKoqV1zP2VOPawcW65Zb9NebANY3GSzGI= 929hasdep.com v1.2.3/go.mod h1:ePVZOlez+KZEOejfLPGL2n4i8qiAjrkhQZ4wcImqAes= 930random.com v1.2.3 h1:PzYTykzqqH6+qU0dIgh9iPFbfb4Mm8zNBjWWreRKtx0= 931random.com v1.2.3/go.mod h1:8EGj+8a4Hw1clAp8vbaeHAsKE4sbm536FP7nKyXO+qQ= 932-- main.go -- 933package main 934 935func main() {} 936` 937 withOptions( 938 ProxyFiles(proxy), 939 ).run(t, mod, func(t *testing.T, env *Env) { 940 d := &protocol.PublishDiagnosticsParams{} 941 env.OpenFile("go.mod") 942 pos := env.RegexpSearch("go.mod", "require hasdep.com v1.2.3") 943 env.Await( 944 OnceMet( 945 DiagnosticAt("go.mod", pos.Line, pos.Column), 946 ReadDiagnostics("go.mod", d), 947 ), 948 ) 949 const want = `module mod.com 950 951go 1.12 952 953require random.com v1.2.3 954` 955 var diagnostics []protocol.Diagnostic 956 for _, d := range d.Diagnostics { 957 if d.Range.Start.Line != float64(pos.Line) { 958 continue 959 } 960 diagnostics = append(diagnostics, d) 961 } 962 env.ApplyQuickFixes("go.mod", diagnostics) 963 if got := env.Editor.BufferText("go.mod"); got != want { 964 t.Fatalf("unexpected content in go.mod:\n%s", tests.Diff(t, want, got)) 965 } 966 }) 967 }) 968} 969 970func TestSumUpdateQuickFix(t *testing.T) { 971 // Error messages changed in 1.16 that changed the diagnostic positions. 972 testenv.NeedsGo1Point(t, 16) 973 974 const mod = ` 975-- go.mod -- 976module mod.com 977 978go 1.12 979 980require ( 981 example.com v1.2.3 982) 983-- go.sum -- 984-- main.go -- 985package main 986 987import ( 988 "example.com/blah" 989) 990 991func main() { 992 blah.Hello() 993} 994` 995 withOptions( 996 ProxyFiles(workspaceProxy), 997 Modes(Singleton), 998 ).run(t, mod, func(t *testing.T, env *Env) { 999 env.OpenFile("go.mod") 1000 pos := env.RegexpSearch("go.mod", "example.com") 1001 params := &protocol.PublishDiagnosticsParams{} 1002 env.Await( 1003 OnceMet( 1004 env.DiagnosticAtRegexp("go.mod", "example.com"), 1005 ReadDiagnostics("go.mod", params), 1006 ), 1007 ) 1008 var diagnostic protocol.Diagnostic 1009 for _, d := range params.Diagnostics { 1010 if d.Range.Start.Line == float64(pos.Line) { 1011 diagnostic = d 1012 break 1013 } 1014 } 1015 env.ApplyQuickFixes("go.mod", []protocol.Diagnostic{diagnostic}) 1016 const want = `example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c= 1017example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= 1018` 1019 if got := env.ReadWorkspaceFile("go.sum"); got != want { 1020 t.Fatalf("unexpected go.sum contents:\n%s", tests.Diff(t, want, got)) 1021 } 1022 }) 1023} 1024