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