github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/lsp_test.go (about) 1 // Copyright 2018 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 lsp 6 7 import ( 8 "context" 9 "fmt" 10 "go/token" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "sort" 15 "strings" 16 "testing" 17 18 "github.com/powerman/golang-tools/internal/lsp/cache" 19 "github.com/powerman/golang-tools/internal/lsp/command" 20 "github.com/powerman/golang-tools/internal/lsp/diff" 21 "github.com/powerman/golang-tools/internal/lsp/diff/myers" 22 "github.com/powerman/golang-tools/internal/lsp/protocol" 23 "github.com/powerman/golang-tools/internal/lsp/source" 24 "github.com/powerman/golang-tools/internal/lsp/tests" 25 "github.com/powerman/golang-tools/internal/span" 26 "github.com/powerman/golang-tools/internal/testenv" 27 ) 28 29 func TestMain(m *testing.M) { 30 testenv.ExitIfSmallMachine() 31 os.Exit(m.Run()) 32 } 33 34 func TestLSP(t *testing.T) { 35 tests.RunTests(t, "testdata", true, testLSP) 36 } 37 38 type runner struct { 39 server *Server 40 data *tests.Data 41 diagnostics map[span.URI][]*source.Diagnostic 42 ctx context.Context 43 normalizers []tests.Normalizer 44 editRecv chan map[span.URI]string 45 } 46 47 func testLSP(t *testing.T, datum *tests.Data) { 48 ctx := tests.Context(t) 49 50 cache := cache.New(nil) 51 session := cache.NewSession(ctx) 52 options := source.DefaultOptions().Clone() 53 tests.DefaultOptions(options) 54 session.SetOptions(options) 55 options.SetEnvSlice(datum.Config.Env) 56 view, snapshot, release, err := session.NewView(ctx, datum.Config.Dir, span.URIFromPath(datum.Config.Dir), options) 57 if err != nil { 58 t.Fatal(err) 59 } 60 61 defer view.Shutdown(ctx) 62 63 // Enable type error analyses for tests. 64 // TODO(golang/go#38212): Delete this once they are enabled by default. 65 tests.EnableAllAnalyzers(view, options) 66 view.SetOptions(ctx, options) 67 68 // Only run the -modfile specific tests in module mode with Go 1.14 or above. 69 datum.ModfileFlagAvailable = len(snapshot.ModFiles()) > 0 && testenv.Go1Point() >= 14 70 release() 71 72 var modifications []source.FileModification 73 for filename, content := range datum.Config.Overlay { 74 if filepath.Ext(filename) != ".go" { 75 continue 76 } 77 modifications = append(modifications, source.FileModification{ 78 URI: span.URIFromPath(filename), 79 Action: source.Open, 80 Version: -1, 81 Text: content, 82 LanguageID: "go", 83 }) 84 } 85 if err := session.ModifyFiles(ctx, modifications); err != nil { 86 t.Fatal(err) 87 } 88 r := &runner{ 89 data: datum, 90 ctx: ctx, 91 normalizers: tests.CollectNormalizers(datum.Exported), 92 editRecv: make(chan map[span.URI]string, 1), 93 } 94 95 r.server = NewServer(session, testClient{runner: r}) 96 tests.Run(t, r, datum) 97 } 98 99 // testClient stubs any client functions that may be called by LSP functions. 100 type testClient struct { 101 protocol.Client 102 runner *runner 103 } 104 105 func (c testClient) Close() error { 106 return nil 107 } 108 109 // Trivially implement PublishDiagnostics so that we can call 110 // server.publishReports below to de-dup sent diagnostics. 111 func (c testClient) PublishDiagnostics(context.Context, *protocol.PublishDiagnosticsParams) error { 112 return nil 113 } 114 115 func (c testClient) ShowMessage(context.Context, *protocol.ShowMessageParams) error { 116 return nil 117 } 118 119 func (c testClient) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) { 120 res, err := applyTextDocumentEdits(c.runner, params.Edit.DocumentChanges) 121 if err != nil { 122 return nil, err 123 } 124 c.runner.editRecv <- res 125 return &protocol.ApplyWorkspaceEditResult{Applied: true}, nil 126 } 127 128 func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) { 129 mapper, err := r.data.Mapper(spn.URI()) 130 if err != nil { 131 t.Fatal(err) 132 } 133 loc, err := mapper.Location(spn) 134 if err != nil { 135 t.Fatalf("failed for %v: %v", spn, err) 136 } 137 138 params := &protocol.CallHierarchyPrepareParams{ 139 TextDocumentPositionParams: protocol.TextDocumentPositionParams{ 140 TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, 141 Position: loc.Range.Start, 142 }, 143 } 144 145 items, err := r.server.PrepareCallHierarchy(r.ctx, params) 146 if err != nil { 147 t.Fatal(err) 148 } 149 if len(items) == 0 { 150 t.Fatalf("expected call hierarchy item to be returned for identifier at %v\n", loc.Range) 151 } 152 153 callLocation := protocol.Location{ 154 URI: items[0].URI, 155 Range: items[0].Range, 156 } 157 if callLocation != loc { 158 t.Fatalf("expected server.PrepareCallHierarchy to return identifier at %v but got %v\n", loc, callLocation) 159 } 160 161 incomingCalls, err := r.server.IncomingCalls(r.ctx, &protocol.CallHierarchyIncomingCallsParams{Item: items[0]}) 162 if err != nil { 163 t.Error(err) 164 } 165 var incomingCallItems []protocol.CallHierarchyItem 166 for _, item := range incomingCalls { 167 incomingCallItems = append(incomingCallItems, item.From) 168 } 169 msg := tests.DiffCallHierarchyItems(incomingCallItems, expectedCalls.IncomingCalls) 170 if msg != "" { 171 t.Error(fmt.Sprintf("incoming calls: %s", msg)) 172 } 173 174 outgoingCalls, err := r.server.OutgoingCalls(r.ctx, &protocol.CallHierarchyOutgoingCallsParams{Item: items[0]}) 175 if err != nil { 176 t.Error(err) 177 } 178 var outgoingCallItems []protocol.CallHierarchyItem 179 for _, item := range outgoingCalls { 180 outgoingCallItems = append(outgoingCallItems, item.To) 181 } 182 msg = tests.DiffCallHierarchyItems(outgoingCallItems, expectedCalls.OutgoingCalls) 183 if msg != "" { 184 t.Error(fmt.Sprintf("outgoing calls: %s", msg)) 185 } 186 } 187 188 func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) { 189 if !strings.HasSuffix(uri.Filename(), "go.mod") { 190 return 191 } 192 got, err := r.server.codeLens(r.ctx, &protocol.CodeLensParams{ 193 TextDocument: protocol.TextDocumentIdentifier{ 194 URI: protocol.DocumentURI(uri), 195 }, 196 }) 197 if err != nil { 198 t.Fatal(err) 199 } 200 if diff := tests.DiffCodeLens(uri, want, got); diff != "" { 201 t.Errorf("%s: %s", uri, diff) 202 } 203 } 204 205 func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []*source.Diagnostic) { 206 // Get the diagnostics for this view if we have not done it before. 207 v := r.server.session.View(r.data.Config.Dir) 208 r.collectDiagnostics(v) 209 d := r.diagnostics[uri] 210 got := make([]*source.Diagnostic, len(d)) 211 copy(got, d) 212 // A special case to test that there are no diagnostics for a file. 213 if len(want) == 1 && want[0].Source == "no_diagnostics" { 214 if len(got) != 0 { 215 t.Errorf("expected no diagnostics for %s, got %v", uri, got) 216 } 217 return 218 } 219 if diff := tests.DiffDiagnostics(uri, want, got); diff != "" { 220 t.Error(diff) 221 } 222 } 223 224 func (r *runner) FoldingRanges(t *testing.T, spn span.Span) { 225 uri := spn.URI() 226 view, err := r.server.session.ViewOf(uri) 227 if err != nil { 228 t.Fatal(err) 229 } 230 original := view.Options() 231 modified := original 232 233 // Test all folding ranges. 234 modified.LineFoldingOnly = false 235 view, err = view.SetOptions(r.ctx, modified) 236 if err != nil { 237 t.Error(err) 238 return 239 } 240 ranges, err := r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{ 241 TextDocument: protocol.TextDocumentIdentifier{ 242 URI: protocol.URIFromSpanURI(uri), 243 }, 244 }) 245 if err != nil { 246 t.Error(err) 247 return 248 } 249 r.foldingRanges(t, "foldingRange", uri, ranges) 250 251 // Test folding ranges with lineFoldingOnly = true. 252 modified.LineFoldingOnly = true 253 view, err = view.SetOptions(r.ctx, modified) 254 if err != nil { 255 t.Error(err) 256 return 257 } 258 ranges, err = r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{ 259 TextDocument: protocol.TextDocumentIdentifier{ 260 URI: protocol.URIFromSpanURI(uri), 261 }, 262 }) 263 if err != nil { 264 t.Error(err) 265 return 266 } 267 r.foldingRanges(t, "foldingRange-lineFolding", uri, ranges) 268 view.SetOptions(r.ctx, original) 269 } 270 271 func (r *runner) foldingRanges(t *testing.T, prefix string, uri span.URI, ranges []protocol.FoldingRange) { 272 m, err := r.data.Mapper(uri) 273 if err != nil { 274 t.Fatal(err) 275 } 276 // Fold all ranges. 277 nonOverlapping := nonOverlappingRanges(ranges) 278 for i, rngs := range nonOverlapping { 279 got, err := foldRanges(m, string(m.Content), rngs) 280 if err != nil { 281 t.Error(err) 282 continue 283 } 284 tag := fmt.Sprintf("%s-%d", prefix, i) 285 want := string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) { 286 return []byte(got), nil 287 })) 288 289 if want != got { 290 t.Errorf("%s: foldingRanges failed for %s, expected:\n%v\ngot:\n%v", tag, uri.Filename(), want, got) 291 } 292 } 293 294 // Filter by kind. 295 kinds := []protocol.FoldingRangeKind{protocol.Imports, protocol.Comment} 296 for _, kind := range kinds { 297 var kindOnly []protocol.FoldingRange 298 for _, fRng := range ranges { 299 if fRng.Kind == string(kind) { 300 kindOnly = append(kindOnly, fRng) 301 } 302 } 303 304 nonOverlapping := nonOverlappingRanges(kindOnly) 305 for i, rngs := range nonOverlapping { 306 got, err := foldRanges(m, string(m.Content), rngs) 307 if err != nil { 308 t.Error(err) 309 continue 310 } 311 tag := fmt.Sprintf("%s-%s-%d", prefix, kind, i) 312 want := string(r.data.Golden(tag, uri.Filename(), func() ([]byte, error) { 313 return []byte(got), nil 314 })) 315 316 if want != got { 317 t.Errorf("%s: foldingRanges failed for %s, expected:\n%v\ngot:\n%v", tag, uri.Filename(), want, got) 318 } 319 } 320 321 } 322 } 323 324 func nonOverlappingRanges(ranges []protocol.FoldingRange) (res [][]protocol.FoldingRange) { 325 for _, fRng := range ranges { 326 setNum := len(res) 327 for i := 0; i < len(res); i++ { 328 canInsert := true 329 for _, rng := range res[i] { 330 if conflict(rng, fRng) { 331 canInsert = false 332 break 333 } 334 } 335 if canInsert { 336 setNum = i 337 break 338 } 339 } 340 if setNum == len(res) { 341 res = append(res, []protocol.FoldingRange{}) 342 } 343 res[setNum] = append(res[setNum], fRng) 344 } 345 return res 346 } 347 348 func conflict(a, b protocol.FoldingRange) bool { 349 // a start position is <= b start positions 350 return (a.StartLine < b.StartLine || (a.StartLine == b.StartLine && a.StartCharacter <= b.StartCharacter)) && 351 (a.EndLine > b.StartLine || (a.EndLine == b.StartLine && a.EndCharacter > b.StartCharacter)) 352 } 353 354 func foldRanges(m *protocol.ColumnMapper, contents string, ranges []protocol.FoldingRange) (string, error) { 355 foldedText := "<>" 356 res := contents 357 // Apply the edits from the end of the file forward 358 // to preserve the offsets 359 for i := len(ranges) - 1; i >= 0; i-- { 360 fRange := ranges[i] 361 spn, err := m.RangeSpan(protocol.Range{ 362 Start: protocol.Position{ 363 Line: fRange.StartLine, 364 Character: fRange.StartCharacter, 365 }, 366 End: protocol.Position{ 367 Line: fRange.EndLine, 368 Character: fRange.EndCharacter, 369 }, 370 }) 371 if err != nil { 372 return "", err 373 } 374 start := spn.Start().Offset() 375 end := spn.End().Offset() 376 377 tmp := res[0:start] + foldedText 378 res = tmp + res[end:] 379 } 380 return res, nil 381 } 382 383 func (r *runner) Format(t *testing.T, spn span.Span) { 384 uri := spn.URI() 385 filename := uri.Filename() 386 gofmted := string(r.data.Golden("gofmt", filename, func() ([]byte, error) { 387 cmd := exec.Command("gofmt", filename) 388 out, _ := cmd.Output() // ignore error, sometimes we have intentionally ungofmt-able files 389 return out, nil 390 })) 391 392 edits, err := r.server.Formatting(r.ctx, &protocol.DocumentFormattingParams{ 393 TextDocument: protocol.TextDocumentIdentifier{ 394 URI: protocol.URIFromSpanURI(uri), 395 }, 396 }) 397 if err != nil { 398 if gofmted != "" { 399 t.Error(err) 400 } 401 return 402 } 403 m, err := r.data.Mapper(uri) 404 if err != nil { 405 t.Fatal(err) 406 } 407 sedits, err := source.FromProtocolEdits(m, edits) 408 if err != nil { 409 t.Error(err) 410 } 411 got := diff.ApplyEdits(string(m.Content), sedits) 412 if gofmted != got { 413 t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got) 414 } 415 } 416 417 func (r *runner) SemanticTokens(t *testing.T, spn span.Span) { 418 uri := spn.URI() 419 filename := uri.Filename() 420 // this is called solely for coverage in semantic.go 421 _, err := r.server.semanticTokensFull(r.ctx, &protocol.SemanticTokensParams{ 422 TextDocument: protocol.TextDocumentIdentifier{ 423 URI: protocol.URIFromSpanURI(uri), 424 }, 425 }) 426 if err != nil { 427 t.Errorf("%v for %s", err, filename) 428 } 429 _, err = r.server.semanticTokensRange(r.ctx, &protocol.SemanticTokensRangeParams{ 430 TextDocument: protocol.TextDocumentIdentifier{ 431 URI: protocol.URIFromSpanURI(uri), 432 }, 433 // any legal range. Just to exercise the call. 434 Range: protocol.Range{ 435 Start: protocol.Position{ 436 Line: 0, 437 Character: 0, 438 }, 439 End: protocol.Position{ 440 Line: 2, 441 Character: 0, 442 }, 443 }, 444 }) 445 if err != nil { 446 t.Errorf("%v for Range %s", err, filename) 447 } 448 } 449 450 func (r *runner) Import(t *testing.T, spn span.Span) { 451 uri := spn.URI() 452 filename := uri.Filename() 453 actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ 454 TextDocument: protocol.TextDocumentIdentifier{ 455 URI: protocol.URIFromSpanURI(uri), 456 }, 457 }) 458 if err != nil { 459 t.Fatal(err) 460 } 461 m, err := r.data.Mapper(uri) 462 if err != nil { 463 t.Fatal(err) 464 } 465 got := string(m.Content) 466 if len(actions) > 0 { 467 res, err := applyTextDocumentEdits(r, actions[0].Edit.DocumentChanges) 468 if err != nil { 469 t.Fatal(err) 470 } 471 got = res[uri] 472 } 473 want := string(r.data.Golden("goimports", filename, func() ([]byte, error) { 474 return []byte(got), nil 475 })) 476 if want != got { 477 d, err := myers.ComputeEdits(uri, want, got) 478 if err != nil { 479 t.Fatal(err) 480 } 481 t.Errorf("import failed for %s: %s", filename, diff.ToUnified("want", "got", want, d)) 482 } 483 } 484 485 func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string, expectedActions int) { 486 uri := spn.URI() 487 view, err := r.server.session.ViewOf(uri) 488 if err != nil { 489 t.Fatal(err) 490 } 491 492 m, err := r.data.Mapper(uri) 493 if err != nil { 494 t.Fatal(err) 495 } 496 rng, err := m.Range(spn) 497 if err != nil { 498 t.Fatal(err) 499 } 500 // Get the diagnostics for this view if we have not done it before. 501 r.collectDiagnostics(view) 502 var diagnostics []protocol.Diagnostic 503 for _, d := range r.diagnostics[uri] { 504 // Compare the start positions rather than the entire range because 505 // some diagnostics have a range with the same start and end position (8:1-8:1). 506 // The current marker functionality prevents us from having a range of 0 length. 507 if protocol.ComparePosition(d.Range.Start, rng.Start) == 0 { 508 diagnostics = append(diagnostics, toProtocolDiagnostics([]*source.Diagnostic{d})...) 509 break 510 } 511 } 512 codeActionKinds := []protocol.CodeActionKind{} 513 for _, k := range actionKinds { 514 codeActionKinds = append(codeActionKinds, protocol.CodeActionKind(k)) 515 } 516 actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ 517 TextDocument: protocol.TextDocumentIdentifier{ 518 URI: protocol.URIFromSpanURI(uri), 519 }, 520 Range: rng, 521 Context: protocol.CodeActionContext{ 522 Only: codeActionKinds, 523 Diagnostics: diagnostics, 524 }, 525 }) 526 if err != nil { 527 t.Fatalf("CodeAction %s failed: %v", spn, err) 528 } 529 if len(actions) != expectedActions { 530 // Hack: We assume that we only get one code action per range. 531 var cmds []string 532 for _, a := range actions { 533 cmds = append(cmds, fmt.Sprintf("%s (%s)", a.Command, a.Title)) 534 } 535 t.Fatalf("unexpected number of code actions, want %d, got %d: %v", expectedActions, len(actions), cmds) 536 } 537 action := actions[0] 538 var match bool 539 for _, k := range codeActionKinds { 540 if action.Kind == k { 541 match = true 542 break 543 } 544 } 545 if !match { 546 t.Fatalf("unexpected kind for code action %s, expected one of %v, got %v", action.Title, codeActionKinds, action.Kind) 547 } 548 var res map[span.URI]string 549 if cmd := action.Command; cmd != nil { 550 _, err := r.server.ExecuteCommand(r.ctx, &protocol.ExecuteCommandParams{ 551 Command: action.Command.Command, 552 Arguments: action.Command.Arguments, 553 }) 554 if err != nil { 555 t.Fatalf("error converting command %q to edits: %v", action.Command.Command, err) 556 } 557 res = <-r.editRecv 558 } else { 559 res, err = applyTextDocumentEdits(r, action.Edit.DocumentChanges) 560 if err != nil { 561 t.Fatal(err) 562 } 563 } 564 for u, got := range res { 565 want := string(r.data.Golden("suggestedfix_"+tests.SpanName(spn), u.Filename(), func() ([]byte, error) { 566 return []byte(got), nil 567 })) 568 if want != got { 569 t.Errorf("suggested fixes failed for %s:\n%s", u.Filename(), tests.Diff(t, want, got)) 570 } 571 } 572 } 573 574 func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) { 575 uri := start.URI() 576 m, err := r.data.Mapper(uri) 577 if err != nil { 578 t.Fatal(err) 579 } 580 spn := span.New(start.URI(), start.Start(), end.End()) 581 rng, err := m.Range(spn) 582 if err != nil { 583 t.Fatal(err) 584 } 585 actionsRaw, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ 586 TextDocument: protocol.TextDocumentIdentifier{ 587 URI: protocol.URIFromSpanURI(uri), 588 }, 589 Range: rng, 590 Context: protocol.CodeActionContext{ 591 Only: []protocol.CodeActionKind{"refactor.extract"}, 592 }, 593 }) 594 if err != nil { 595 t.Fatal(err) 596 } 597 var actions []protocol.CodeAction 598 for _, action := range actionsRaw { 599 if action.Command.Title == "Extract function" { 600 actions = append(actions, action) 601 } 602 } 603 // Hack: We assume that we only get one code action per range. 604 // TODO(rstambler): Support multiple code actions per test. 605 if len(actions) == 0 || len(actions) > 1 { 606 t.Fatalf("unexpected number of code actions, want 1, got %v", len(actions)) 607 } 608 _, err = r.server.ExecuteCommand(r.ctx, &protocol.ExecuteCommandParams{ 609 Command: actions[0].Command.Command, 610 Arguments: actions[0].Command.Arguments, 611 }) 612 if err != nil { 613 t.Fatal(err) 614 } 615 res := <-r.editRecv 616 for u, got := range res { 617 want := string(r.data.Golden("functionextraction_"+tests.SpanName(spn), u.Filename(), func() ([]byte, error) { 618 return []byte(got), nil 619 })) 620 if want != got { 621 t.Errorf("function extraction failed for %s:\n%s", u.Filename(), tests.Diff(t, want, got)) 622 } 623 } 624 } 625 626 func (r *runner) MethodExtraction(t *testing.T, start span.Span, end span.Span) { 627 uri := start.URI() 628 m, err := r.data.Mapper(uri) 629 if err != nil { 630 t.Fatal(err) 631 } 632 spn := span.New(start.URI(), start.Start(), end.End()) 633 rng, err := m.Range(spn) 634 if err != nil { 635 t.Fatal(err) 636 } 637 actionsRaw, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{ 638 TextDocument: protocol.TextDocumentIdentifier{ 639 URI: protocol.URIFromSpanURI(uri), 640 }, 641 Range: rng, 642 Context: protocol.CodeActionContext{ 643 Only: []protocol.CodeActionKind{"refactor.extract"}, 644 }, 645 }) 646 if err != nil { 647 t.Fatal(err) 648 } 649 var actions []protocol.CodeAction 650 for _, action := range actionsRaw { 651 if action.Command.Title == "Extract method" { 652 actions = append(actions, action) 653 } 654 } 655 // Hack: We assume that we only get one matching code action per range. 656 // TODO(rstambler): Support multiple code actions per test. 657 if len(actions) == 0 || len(actions) > 1 { 658 t.Fatalf("unexpected number of code actions, want 1, got %v", len(actions)) 659 } 660 _, err = r.server.ExecuteCommand(r.ctx, &protocol.ExecuteCommandParams{ 661 Command: actions[0].Command.Command, 662 Arguments: actions[0].Command.Arguments, 663 }) 664 if err != nil { 665 t.Fatal(err) 666 } 667 res := <-r.editRecv 668 for u, got := range res { 669 want := string(r.data.Golden("methodextraction_"+tests.SpanName(spn), u.Filename(), func() ([]byte, error) { 670 return []byte(got), nil 671 })) 672 if want != got { 673 t.Errorf("method extraction failed for %s:\n%s", u.Filename(), tests.Diff(t, want, got)) 674 } 675 } 676 } 677 678 func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) { 679 sm, err := r.data.Mapper(d.Src.URI()) 680 if err != nil { 681 t.Fatal(err) 682 } 683 loc, err := sm.Location(d.Src) 684 if err != nil { 685 t.Fatalf("failed for %v: %v", d.Src, err) 686 } 687 tdpp := protocol.TextDocumentPositionParams{ 688 TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, 689 Position: loc.Range.Start, 690 } 691 var locs []protocol.Location 692 var hover *protocol.Hover 693 if d.IsType { 694 params := &protocol.TypeDefinitionParams{ 695 TextDocumentPositionParams: tdpp, 696 } 697 locs, err = r.server.TypeDefinition(r.ctx, params) 698 } else { 699 params := &protocol.DefinitionParams{ 700 TextDocumentPositionParams: tdpp, 701 } 702 locs, err = r.server.Definition(r.ctx, params) 703 if err != nil { 704 t.Fatalf("failed for %v: %+v", d.Src, err) 705 } 706 v := &protocol.HoverParams{ 707 TextDocumentPositionParams: tdpp, 708 } 709 hover, err = r.server.Hover(r.ctx, v) 710 } 711 if err != nil { 712 t.Fatalf("failed for %v: %v", d.Src, err) 713 } 714 if len(locs) != 1 { 715 t.Errorf("got %d locations for definition, expected 1", len(locs)) 716 } 717 didSomething := false 718 if hover != nil { 719 didSomething = true 720 tag := fmt.Sprintf("%s-hoverdef", d.Name) 721 expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) { 722 return []byte(hover.Contents.Value), nil 723 })) 724 got := tests.StripSubscripts(hover.Contents.Value) 725 expectHover = tests.StripSubscripts(expectHover) 726 if got != expectHover { 727 t.Errorf("%s:\n%s", d.Src, tests.Diff(t, expectHover, got)) 728 } 729 } 730 if !d.OnlyHover { 731 didSomething = true 732 locURI := locs[0].URI.SpanURI() 733 lm, err := r.data.Mapper(locURI) 734 if err != nil { 735 t.Fatal(err) 736 } 737 if def, err := lm.Span(locs[0]); err != nil { 738 t.Fatalf("failed for %v: %v", locs[0], err) 739 } else if def != d.Def { 740 t.Errorf("for %v got %v want %v", d.Src, def, d.Def) 741 } 742 } 743 if !didSomething { 744 t.Errorf("no tests ran for %s", d.Src.URI()) 745 } 746 } 747 748 func (r *runner) Implementation(t *testing.T, spn span.Span, impls []span.Span) { 749 sm, err := r.data.Mapper(spn.URI()) 750 if err != nil { 751 t.Fatal(err) 752 } 753 loc, err := sm.Location(spn) 754 if err != nil { 755 t.Fatalf("failed for %v: %v", spn, err) 756 } 757 tdpp := protocol.TextDocumentPositionParams{ 758 TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, 759 Position: loc.Range.Start, 760 } 761 var locs []protocol.Location 762 params := &protocol.ImplementationParams{ 763 TextDocumentPositionParams: tdpp, 764 } 765 locs, err = r.server.Implementation(r.ctx, params) 766 if err != nil { 767 t.Fatalf("failed for %v: %v", spn, err) 768 } 769 if len(locs) != len(impls) { 770 t.Fatalf("got %d locations for implementation, expected %d", len(locs), len(impls)) 771 } 772 773 var results []span.Span 774 for i := range locs { 775 locURI := locs[i].URI.SpanURI() 776 lm, err := r.data.Mapper(locURI) 777 if err != nil { 778 t.Fatal(err) 779 } 780 imp, err := lm.Span(locs[i]) 781 if err != nil { 782 t.Fatalf("failed for %v: %v", locs[i], err) 783 } 784 results = append(results, imp) 785 } 786 // Sort results and expected to make tests deterministic. 787 sort.SliceStable(results, func(i, j int) bool { 788 return span.Compare(results[i], results[j]) == -1 789 }) 790 sort.SliceStable(impls, func(i, j int) bool { 791 return span.Compare(impls[i], impls[j]) == -1 792 }) 793 for i := range results { 794 if results[i] != impls[i] { 795 t.Errorf("for %dth implementation of %v got %v want %v", i, spn, results[i], impls[i]) 796 } 797 } 798 } 799 800 func (r *runner) Highlight(t *testing.T, src span.Span, locations []span.Span) { 801 m, err := r.data.Mapper(src.URI()) 802 if err != nil { 803 t.Fatal(err) 804 } 805 loc, err := m.Location(src) 806 if err != nil { 807 t.Fatalf("failed for %v: %v", locations[0], err) 808 } 809 tdpp := protocol.TextDocumentPositionParams{ 810 TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, 811 Position: loc.Range.Start, 812 } 813 params := &protocol.DocumentHighlightParams{ 814 TextDocumentPositionParams: tdpp, 815 } 816 highlights, err := r.server.DocumentHighlight(r.ctx, params) 817 if err != nil { 818 t.Fatal(err) 819 } 820 if len(highlights) != len(locations) { 821 t.Fatalf("got %d highlights for highlight at %v:%v:%v, expected %d", len(highlights), src.URI().Filename(), src.Start().Line(), src.Start().Column(), len(locations)) 822 } 823 // Check to make sure highlights have a valid range. 824 var results []span.Span 825 for i := range highlights { 826 h, err := m.RangeSpan(highlights[i].Range) 827 if err != nil { 828 t.Fatalf("failed for %v: %v", highlights[i], err) 829 } 830 results = append(results, h) 831 } 832 // Sort results to make tests deterministic since DocumentHighlight uses a map. 833 sort.SliceStable(results, func(i, j int) bool { 834 return span.Compare(results[i], results[j]) == -1 835 }) 836 // Check to make sure all the expected highlights are found. 837 for i := range results { 838 if results[i] != locations[i] { 839 t.Errorf("want %v, got %v\n", locations[i], results[i]) 840 } 841 } 842 } 843 844 func (r *runner) Hover(t *testing.T, src span.Span, text string) { 845 m, err := r.data.Mapper(src.URI()) 846 if err != nil { 847 t.Fatal(err) 848 } 849 loc, err := m.Location(src) 850 if err != nil { 851 t.Fatalf("failed for %v", err) 852 } 853 tdpp := protocol.TextDocumentPositionParams{ 854 TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, 855 Position: loc.Range.Start, 856 } 857 params := &protocol.HoverParams{ 858 TextDocumentPositionParams: tdpp, 859 } 860 hover, err := r.server.Hover(r.ctx, params) 861 if err != nil { 862 t.Fatal(err) 863 } 864 if text == "" { 865 if hover != nil { 866 t.Errorf("want nil, got %v\n", hover) 867 } 868 } else { 869 if hover == nil { 870 t.Fatalf("want hover result to include %s, but got nil", text) 871 } 872 if got := hover.Contents.Value; got != text { 873 t.Errorf("want %v, got %v\n", text, got) 874 } 875 if want, got := loc.Range, hover.Range; want != got { 876 t.Errorf("want range %v, got %v instead", want, got) 877 } 878 } 879 } 880 881 func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) { 882 sm, err := r.data.Mapper(src.URI()) 883 if err != nil { 884 t.Fatal(err) 885 } 886 loc, err := sm.Location(src) 887 if err != nil { 888 t.Fatalf("failed for %v: %v", src, err) 889 } 890 for _, includeDeclaration := range []bool{true, false} { 891 t.Run(fmt.Sprintf("refs-declaration-%v", includeDeclaration), func(t *testing.T) { 892 want := make(map[protocol.Location]bool) 893 for i, pos := range itemList { 894 // We don't want the first result if we aren't including the declaration. 895 if i == 0 && !includeDeclaration { 896 continue 897 } 898 m, err := r.data.Mapper(pos.URI()) 899 if err != nil { 900 t.Fatal(err) 901 } 902 loc, err := m.Location(pos) 903 if err != nil { 904 t.Fatalf("failed for %v: %v", src, err) 905 } 906 want[loc] = true 907 } 908 params := &protocol.ReferenceParams{ 909 TextDocumentPositionParams: protocol.TextDocumentPositionParams{ 910 TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, 911 Position: loc.Range.Start, 912 }, 913 Context: protocol.ReferenceContext{ 914 IncludeDeclaration: includeDeclaration, 915 }, 916 } 917 got, err := r.server.References(r.ctx, params) 918 if err != nil { 919 t.Fatalf("failed for %v: %v", src, err) 920 } 921 if len(got) != len(want) { 922 t.Errorf("references failed: different lengths got %v want %v", len(got), len(want)) 923 } 924 for _, loc := range got { 925 if !want[loc] { 926 t.Errorf("references failed: incorrect references got %v want %v", loc, want) 927 } 928 } 929 }) 930 } 931 } 932 933 func (r *runner) Rename(t *testing.T, spn span.Span, newText string) { 934 tag := fmt.Sprintf("%s-rename", newText) 935 936 uri := spn.URI() 937 filename := uri.Filename() 938 sm, err := r.data.Mapper(uri) 939 if err != nil { 940 t.Fatal(err) 941 } 942 loc, err := sm.Location(spn) 943 if err != nil { 944 t.Fatalf("failed for %v: %v", spn, err) 945 } 946 947 wedit, err := r.server.Rename(r.ctx, &protocol.RenameParams{ 948 TextDocument: protocol.TextDocumentIdentifier{ 949 URI: protocol.URIFromSpanURI(uri), 950 }, 951 Position: loc.Range.Start, 952 NewName: newText, 953 }) 954 if err != nil { 955 renamed := string(r.data.Golden(tag, filename, func() ([]byte, error) { 956 return []byte(err.Error()), nil 957 })) 958 if err.Error() != renamed { 959 t.Errorf("rename failed for %s, expected:\n%v\ngot:\n%v\n", newText, renamed, err) 960 } 961 return 962 } 963 res, err := applyTextDocumentEdits(r, wedit.DocumentChanges) 964 if err != nil { 965 t.Fatal(err) 966 } 967 var orderedURIs []string 968 for uri := range res { 969 orderedURIs = append(orderedURIs, string(uri)) 970 } 971 sort.Strings(orderedURIs) 972 973 var got string 974 for i := 0; i < len(res); i++ { 975 if i != 0 { 976 got += "\n" 977 } 978 uri := span.URIFromURI(orderedURIs[i]) 979 if len(res) > 1 { 980 got += filepath.Base(uri.Filename()) + ":\n" 981 } 982 val := res[uri] 983 got += val 984 } 985 want := string(r.data.Golden(tag, filename, func() ([]byte, error) { 986 return []byte(got), nil 987 })) 988 if want != got { 989 t.Errorf("rename failed for %s:\n%s", newText, tests.Diff(t, want, got)) 990 } 991 } 992 993 func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) { 994 m, err := r.data.Mapper(src.URI()) 995 if err != nil { 996 t.Fatal(err) 997 } 998 loc, err := m.Location(src) 999 if err != nil { 1000 t.Fatalf("failed for %v: %v", src, err) 1001 } 1002 tdpp := protocol.TextDocumentPositionParams{ 1003 TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, 1004 Position: loc.Range.Start, 1005 } 1006 params := &protocol.PrepareRenameParams{ 1007 TextDocumentPositionParams: tdpp, 1008 } 1009 got, err := r.server.PrepareRename(context.Background(), params) 1010 if err != nil { 1011 t.Errorf("prepare rename failed for %v: got error: %v", src, err) 1012 return 1013 } 1014 // we all love typed nils 1015 if got == nil { 1016 if want.Text != "" { // expected an ident. 1017 t.Errorf("prepare rename failed for %v: got nil", src) 1018 } 1019 return 1020 } 1021 if got.Range.Start == got.Range.End { 1022 // Special case for 0-length ranges. Marks can't specify a 0-length range, 1023 // so just compare the start. 1024 if got.Range.Start != want.Range.Start { 1025 t.Errorf("prepare rename failed: incorrect point, got %v want %v", got.Range.Start, want.Range.Start) 1026 } 1027 } else { 1028 if protocol.CompareRange(got.Range, want.Range) != 0 { 1029 t.Errorf("prepare rename failed: incorrect range got %v want %v", got.Range, want.Range) 1030 } 1031 } 1032 if got.Placeholder != want.Text { 1033 t.Errorf("prepare rename failed: incorrect text got %v want %v", got.Placeholder, want.Text) 1034 } 1035 } 1036 1037 func applyTextDocumentEdits(r *runner, edits []protocol.TextDocumentEdit) (map[span.URI]string, error) { 1038 res := map[span.URI]string{} 1039 for _, docEdits := range edits { 1040 uri := docEdits.TextDocument.URI.SpanURI() 1041 var m *protocol.ColumnMapper 1042 // If we have already edited this file, we use the edited version (rather than the 1043 // file in its original state) so that we preserve our initial changes. 1044 if content, ok := res[uri]; ok { 1045 m = &protocol.ColumnMapper{ 1046 URI: uri, 1047 Converter: span.NewContentConverter( 1048 uri.Filename(), []byte(content)), 1049 Content: []byte(content), 1050 } 1051 } else { 1052 var err error 1053 if m, err = r.data.Mapper(uri); err != nil { 1054 return nil, err 1055 } 1056 } 1057 res[uri] = string(m.Content) 1058 sedits, err := source.FromProtocolEdits(m, docEdits.Edits) 1059 if err != nil { 1060 return nil, err 1061 } 1062 res[uri] = applyEdits(res[uri], sedits) 1063 } 1064 return res, nil 1065 } 1066 1067 func applyEdits(contents string, edits []diff.TextEdit) string { 1068 res := contents 1069 1070 // Apply the edits from the end of the file forward 1071 // to preserve the offsets 1072 for i := len(edits) - 1; i >= 0; i-- { 1073 edit := edits[i] 1074 start := edit.Span.Start().Offset() 1075 end := edit.Span.End().Offset() 1076 tmp := res[0:start] + edit.NewText 1077 res = tmp + res[end:] 1078 } 1079 return res 1080 } 1081 1082 func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { 1083 params := &protocol.DocumentSymbolParams{ 1084 TextDocument: protocol.TextDocumentIdentifier{ 1085 URI: protocol.URIFromSpanURI(uri), 1086 }, 1087 } 1088 got, err := r.server.DocumentSymbol(r.ctx, params) 1089 if err != nil { 1090 t.Fatal(err) 1091 } 1092 if len(got) != len(expectedSymbols) { 1093 t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(got)) 1094 return 1095 } 1096 symbols := make([]protocol.DocumentSymbol, len(got)) 1097 for i, s := range got { 1098 s, ok := s.(protocol.DocumentSymbol) 1099 if !ok { 1100 t.Fatalf("%v: wanted []DocumentSymbols but got %v", uri, got) 1101 } 1102 symbols[i] = s 1103 } 1104 if diff := tests.DiffSymbols(t, uri, expectedSymbols, symbols); diff != "" { 1105 t.Error(diff) 1106 } 1107 } 1108 1109 func (r *runner) WorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) { 1110 r.callWorkspaceSymbols(t, uri, query, typ) 1111 } 1112 1113 func (r *runner) callWorkspaceSymbols(t *testing.T, uri span.URI, query string, typ tests.WorkspaceSymbolsTestType) { 1114 t.Helper() 1115 1116 matcher := tests.WorkspaceSymbolsTestTypeToMatcher(typ) 1117 1118 original := r.server.session.Options() 1119 modified := original 1120 modified.SymbolMatcher = matcher 1121 r.server.session.SetOptions(modified) 1122 defer r.server.session.SetOptions(original) 1123 1124 params := &protocol.WorkspaceSymbolParams{ 1125 Query: query, 1126 } 1127 gotSymbols, err := r.server.Symbol(r.ctx, params) 1128 if err != nil { 1129 t.Fatal(err) 1130 } 1131 got, err := tests.WorkspaceSymbolsString(r.ctx, r.data, uri, gotSymbols) 1132 if err != nil { 1133 t.Fatal(err) 1134 } 1135 got = filepath.ToSlash(tests.Normalize(got, r.normalizers)) 1136 want := string(r.data.Golden(fmt.Sprintf("workspace_symbol-%s-%s", strings.ToLower(string(matcher)), query), uri.Filename(), func() ([]byte, error) { 1137 return []byte(got), nil 1138 })) 1139 if diff := tests.Diff(t, want, got); diff != "" { 1140 t.Error(diff) 1141 } 1142 } 1143 1144 func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) { 1145 m, err := r.data.Mapper(spn.URI()) 1146 if err != nil { 1147 t.Fatal(err) 1148 } 1149 loc, err := m.Location(spn) 1150 if err != nil { 1151 t.Fatalf("failed for %v: %v", loc, err) 1152 } 1153 tdpp := protocol.TextDocumentPositionParams{ 1154 TextDocument: protocol.TextDocumentIdentifier{ 1155 URI: protocol.URIFromSpanURI(spn.URI()), 1156 }, 1157 Position: loc.Range.Start, 1158 } 1159 params := &protocol.SignatureHelpParams{ 1160 TextDocumentPositionParams: tdpp, 1161 } 1162 got, err := r.server.SignatureHelp(r.ctx, params) 1163 if err != nil { 1164 // Only fail if we got an error we did not expect. 1165 if want != nil { 1166 t.Fatal(err) 1167 } 1168 return 1169 } 1170 if want == nil { 1171 if got != nil { 1172 t.Errorf("expected no signature, got %v", got) 1173 } 1174 return 1175 } 1176 if got == nil { 1177 t.Fatalf("expected %v, got nil", want) 1178 } 1179 diff, err := tests.DiffSignatures(spn, want, got) 1180 if err != nil { 1181 t.Fatal(err) 1182 } 1183 if diff != "" { 1184 t.Error(diff) 1185 } 1186 } 1187 1188 func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) { 1189 m, err := r.data.Mapper(uri) 1190 if err != nil { 1191 t.Fatal(err) 1192 } 1193 got, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{ 1194 TextDocument: protocol.TextDocumentIdentifier{ 1195 URI: protocol.URIFromSpanURI(uri), 1196 }, 1197 }) 1198 if err != nil { 1199 t.Fatal(err) 1200 } 1201 if diff := tests.DiffLinks(m, wantLinks, got); diff != "" { 1202 t.Error(diff) 1203 } 1204 } 1205 1206 func (r *runner) AddImport(t *testing.T, uri span.URI, expectedImport string) { 1207 cmd, err := command.NewListKnownPackagesCommand("List Known Packages", command.URIArg{ 1208 URI: protocol.URIFromSpanURI(uri), 1209 }) 1210 if err != nil { 1211 t.Fatal(err) 1212 } 1213 resp, err := r.server.executeCommand(r.ctx, &protocol.ExecuteCommandParams{ 1214 Command: cmd.Command, 1215 Arguments: cmd.Arguments, 1216 }) 1217 if err != nil { 1218 t.Fatal(err) 1219 } 1220 res := resp.(command.ListKnownPackagesResult) 1221 var hasPkg bool 1222 for _, p := range res.Packages { 1223 if p == expectedImport { 1224 hasPkg = true 1225 break 1226 } 1227 } 1228 if !hasPkg { 1229 t.Fatalf("%s: got %v packages\nwant contains %q", command.ListKnownPackages, res.Packages, expectedImport) 1230 } 1231 cmd, err = command.NewAddImportCommand("Add Imports", command.AddImportArgs{ 1232 URI: protocol.URIFromSpanURI(uri), 1233 ImportPath: expectedImport, 1234 }) 1235 if err != nil { 1236 t.Fatal(err) 1237 } 1238 _, err = r.server.executeCommand(r.ctx, &protocol.ExecuteCommandParams{ 1239 Command: cmd.Command, 1240 Arguments: cmd.Arguments, 1241 }) 1242 if err != nil { 1243 t.Fatal(err) 1244 } 1245 got := (<-r.editRecv)[uri] 1246 want := r.data.Golden("addimport", uri.Filename(), func() ([]byte, error) { 1247 return []byte(got), nil 1248 }) 1249 if want == nil { 1250 t.Fatalf("golden file %q not found", uri.Filename()) 1251 } 1252 if diff := tests.Diff(t, got, string(want)); diff != "" { 1253 t.Errorf("%s mismatch\n%s", command.AddImport, diff) 1254 } 1255 } 1256 1257 func TestBytesOffset(t *testing.T) { 1258 tests := []struct { 1259 text string 1260 pos protocol.Position 1261 want int 1262 }{ 1263 {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 0}, want: 0}, 1264 {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 1}, want: 1}, 1265 {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 2}, want: 1}, 1266 {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 3}, want: 5}, 1267 {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 4}, want: 6}, 1268 {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 5}, want: -1}, 1269 {text: "aaa\nbbb\n", pos: protocol.Position{Line: 0, Character: 3}, want: 3}, 1270 {text: "aaa\nbbb\n", pos: protocol.Position{Line: 0, Character: 4}, want: 3}, 1271 {text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 0}, want: 4}, 1272 {text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 3}, want: 7}, 1273 {text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 4}, want: 7}, 1274 {text: "aaa\nbbb\n", pos: protocol.Position{Line: 2, Character: 0}, want: 8}, 1275 {text: "aaa\nbbb\n", pos: protocol.Position{Line: 2, Character: 1}, want: -1}, 1276 {text: "aaa\nbbb\n\n", pos: protocol.Position{Line: 2, Character: 0}, want: 8}, 1277 } 1278 1279 for i, test := range tests { 1280 fname := fmt.Sprintf("test %d", i) 1281 fset := token.NewFileSet() 1282 f := fset.AddFile(fname, -1, len(test.text)) 1283 f.SetLinesForContent([]byte(test.text)) 1284 uri := span.URIFromPath(fname) 1285 converter := span.NewContentConverter(fname, []byte(test.text)) 1286 mapper := &protocol.ColumnMapper{ 1287 URI: uri, 1288 Converter: converter, 1289 Content: []byte(test.text), 1290 } 1291 got, err := mapper.Point(test.pos) 1292 if err != nil && test.want != -1 { 1293 t.Errorf("unexpected error: %v", err) 1294 } 1295 if err == nil && got.Offset() != test.want { 1296 t.Errorf("want %d for %q(Line:%d,Character:%d), but got %d", test.want, test.text, int(test.pos.Line), int(test.pos.Character), got.Offset()) 1297 } 1298 } 1299 } 1300 1301 func (r *runner) collectDiagnostics(view source.View) { 1302 if r.diagnostics != nil { 1303 return 1304 } 1305 r.diagnostics = make(map[span.URI][]*source.Diagnostic) 1306 1307 snapshot, release := view.Snapshot(r.ctx) 1308 defer release() 1309 1310 // Always run diagnostics with analysis. 1311 r.server.diagnose(r.ctx, snapshot, true) 1312 for uri, reports := range r.server.diagnostics { 1313 for _, report := range reports.reports { 1314 for _, d := range report.diags { 1315 r.diagnostics[uri] = append(r.diagnostics[uri], d) 1316 } 1317 } 1318 } 1319 }