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