mirror of
https://github.com/golang/go
synced 2024-10-01 01:08:33 -06:00
744a51dd88
This allows us to use the diff.ApplyEdits in tests, saving us from a different implementation. It also prepares for command lines that need to use diff features based on the results of a protocol message. Splitting content into lines is too easy to get wrong, and needs to be done correctly or the diff results make no sense. This adds the SplitLines function to the diff pacakge to do it right and then uses it everwhere we we already doing it wrong. It also makes all the diff tests external black box tests. Change-Id: I698227d5769a2bfbfd22a64ea42906b1df9268d9 Reviewed-on: https://go-review.googlesource.com/c/tools/+/171027 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
665 lines
19 KiB
Go
665 lines
19 KiB
Go
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package lsp
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/tools/go/packages/packagestest"
|
|
"golang.org/x/tools/internal/lsp/cache"
|
|
"golang.org/x/tools/internal/lsp/diff"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/lsp/xlog"
|
|
"golang.org/x/tools/internal/span"
|
|
)
|
|
|
|
func TestLSP(t *testing.T) {
|
|
packagestest.TestAll(t, testLSP)
|
|
}
|
|
|
|
func testLSP(t *testing.T, exporter packagestest.Exporter) {
|
|
ctx := context.Background()
|
|
const dir = "testdata"
|
|
|
|
// We hardcode the expected number of test cases to ensure that all tests
|
|
// are being executed. If a test is added, this number must be changed.
|
|
const expectedCompletionsCount = 64
|
|
const expectedDiagnosticsCount = 16
|
|
const expectedFormatCount = 4
|
|
const expectedDefinitionsCount = 16
|
|
const expectedTypeDefinitionsCount = 2
|
|
const expectedHighlightsCount = 2
|
|
const expectedSymbolsCount = 1
|
|
|
|
files := packagestest.MustCopyFileTree(dir)
|
|
for fragment, operation := range files {
|
|
if trimmed := strings.TrimSuffix(fragment, ".in"); trimmed != fragment {
|
|
delete(files, fragment)
|
|
files[trimmed] = operation
|
|
}
|
|
}
|
|
modules := []packagestest.Module{
|
|
{
|
|
Name: "golang.org/x/tools/internal/lsp",
|
|
Files: files,
|
|
},
|
|
}
|
|
exported := packagestest.Export(t, exporter, modules)
|
|
defer exported.Cleanup()
|
|
|
|
// Merge the exported.Config with the view.Config.
|
|
cfg := *exported.Config
|
|
cfg.Fset = token.NewFileSet()
|
|
cfg.Context = context.Background()
|
|
cfg.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
|
|
return parser.ParseFile(fset, filename, src, parser.AllErrors|parser.ParseComments)
|
|
}
|
|
|
|
log := xlog.New(xlog.StdSink{})
|
|
s := &Server{
|
|
views: []*cache.View{cache.NewView(ctx, log, "lsp_test", span.FileURI(cfg.Dir), &cfg)},
|
|
undelivered: make(map[span.URI][]source.Diagnostic),
|
|
}
|
|
// Do a first pass to collect special markers for completion.
|
|
if err := exported.Expect(map[string]interface{}{
|
|
"item": func(name string, r packagestest.Range, _, _ string) {
|
|
exported.Mark(name, r)
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expectedDiagnostics := make(diagnostics)
|
|
completionItems := make(completionItems)
|
|
expectedCompletions := make(completions)
|
|
expectedFormat := make(formats)
|
|
expectedDefinitions := make(definitions)
|
|
expectedTypeDefinitions := make(definitions)
|
|
expectedHighlights := make(highlights)
|
|
expectedSymbols := &symbols{
|
|
m: make(map[span.URI][]protocol.DocumentSymbol),
|
|
children: make(map[string][]protocol.DocumentSymbol),
|
|
}
|
|
|
|
// Collect any data that needs to be used by subsequent tests.
|
|
if err := exported.Expect(map[string]interface{}{
|
|
"diag": expectedDiagnostics.collect,
|
|
"item": completionItems.collect,
|
|
"complete": expectedCompletions.collect,
|
|
"format": expectedFormat.collect,
|
|
"godef": expectedDefinitions.collect,
|
|
"typdef": expectedTypeDefinitions.collect,
|
|
"highlight": expectedHighlights.collect,
|
|
"symbol": expectedSymbols.collect,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Run("Completion", func(t *testing.T) {
|
|
t.Helper()
|
|
if len(expectedCompletions) != expectedCompletionsCount {
|
|
t.Errorf("got %v completions expected %v", len(expectedCompletions), expectedCompletionsCount)
|
|
}
|
|
expectedCompletions.test(t, exported, s, completionItems)
|
|
})
|
|
|
|
t.Run("Diagnostics", func(t *testing.T) {
|
|
t.Helper()
|
|
diagnosticsCount := expectedDiagnostics.test(t, s.views[0])
|
|
if diagnosticsCount != expectedDiagnosticsCount {
|
|
t.Errorf("got %v diagnostics expected %v", diagnosticsCount, expectedDiagnosticsCount)
|
|
}
|
|
})
|
|
|
|
t.Run("Format", func(t *testing.T) {
|
|
if _, err := exec.LookPath("gofmt"); err != nil {
|
|
switch runtime.GOOS {
|
|
case "android":
|
|
t.Skip("gofmt is not installed")
|
|
default:
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
t.Helper()
|
|
if len(expectedFormat) != expectedFormatCount {
|
|
t.Errorf("got %v formats expected %v", len(expectedFormat), expectedFormatCount)
|
|
}
|
|
expectedFormat.test(t, s)
|
|
})
|
|
|
|
t.Run("Definitions", func(t *testing.T) {
|
|
t.Helper()
|
|
if len(expectedDefinitions) != expectedDefinitionsCount {
|
|
t.Errorf("got %v definitions expected %v", len(expectedDefinitions), expectedDefinitionsCount)
|
|
}
|
|
expectedDefinitions.test(t, s, false)
|
|
})
|
|
|
|
t.Run("TypeDefinitions", func(t *testing.T) {
|
|
t.Helper()
|
|
if len(expectedTypeDefinitions) != expectedTypeDefinitionsCount {
|
|
t.Errorf("got %v type definitions expected %v", len(expectedTypeDefinitions), expectedTypeDefinitionsCount)
|
|
}
|
|
expectedTypeDefinitions.test(t, s, true)
|
|
})
|
|
|
|
t.Run("Highlights", func(t *testing.T) {
|
|
t.Helper()
|
|
if len(expectedHighlights) != expectedHighlightsCount {
|
|
t.Errorf("got %v highlights expected %v", len(expectedHighlights), expectedHighlightsCount)
|
|
}
|
|
expectedHighlights.test(t, s)
|
|
})
|
|
|
|
t.Run("Symbols", func(t *testing.T) {
|
|
t.Helper()
|
|
if len(expectedSymbols.m) != expectedSymbolsCount {
|
|
t.Errorf("got %v symbols expected %v", len(expectedSymbols.m), expectedSymbolsCount)
|
|
}
|
|
expectedSymbols.test(t, s)
|
|
})
|
|
}
|
|
|
|
type diagnostics map[span.URI][]protocol.Diagnostic
|
|
type completionItems map[token.Pos]*protocol.CompletionItem
|
|
type completions map[token.Position][]token.Pos
|
|
type formats map[string]string
|
|
type definitions map[protocol.Location]protocol.Location
|
|
type highlights map[string][]protocol.Location
|
|
type symbols struct {
|
|
m map[span.URI][]protocol.DocumentSymbol
|
|
children map[string][]protocol.DocumentSymbol
|
|
}
|
|
|
|
func (d diagnostics) test(t *testing.T, v source.View) int {
|
|
count := 0
|
|
ctx := context.Background()
|
|
for uri, want := range d {
|
|
sourceDiagnostics, err := source.Diagnostics(context.Background(), v, uri)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := toProtocolDiagnostics(ctx, v, sourceDiagnostics[uri])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sorted(got)
|
|
if diff := diffDiagnostics(uri, want, got); diff != "" {
|
|
t.Error(diff)
|
|
}
|
|
count += len(want)
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (d diagnostics) collect(e *packagestest.Exported, fset *token.FileSet, rng packagestest.Range, msgSource, msg string) {
|
|
spn, m := testLocation(e, fset, rng)
|
|
if _, ok := d[spn.URI()]; !ok {
|
|
d[spn.URI()] = []protocol.Diagnostic{}
|
|
}
|
|
// If a file has an empty diagnostic message, return. This allows us to
|
|
// avoid testing diagnostics in files that may have a lot of them.
|
|
if msg == "" {
|
|
return
|
|
}
|
|
severity := protocol.SeverityError
|
|
if strings.Contains(string(spn.URI()), "analyzer") {
|
|
severity = protocol.SeverityWarning
|
|
}
|
|
dRng, err := m.Range(spn)
|
|
if err != nil {
|
|
return
|
|
}
|
|
want := protocol.Diagnostic{
|
|
Range: dRng,
|
|
Severity: severity,
|
|
Source: msgSource,
|
|
Message: msg,
|
|
}
|
|
d[spn.URI()] = append(d[spn.URI()], want)
|
|
}
|
|
|
|
// diffDiagnostics prints the diff between expected and actual diagnostics test
|
|
// results.
|
|
func diffDiagnostics(uri span.URI, want, got []protocol.Diagnostic) string {
|
|
if len(got) != len(want) {
|
|
goto Failed
|
|
}
|
|
for i, w := range want {
|
|
g := got[i]
|
|
if w.Message != g.Message {
|
|
goto Failed
|
|
}
|
|
if w.Range.Start != g.Range.Start {
|
|
goto Failed
|
|
}
|
|
// Special case for diagnostics on parse errors.
|
|
if strings.Contains(string(uri), "noparse") {
|
|
if g.Range.Start != g.Range.End || w.Range.Start != g.Range.End {
|
|
goto Failed
|
|
}
|
|
} else if g.Range.End != g.Range.Start { // Accept any 'want' range if the diagnostic returns a zero-length range.
|
|
if w.Range.End != g.Range.End {
|
|
goto Failed
|
|
}
|
|
}
|
|
if w.Severity != g.Severity {
|
|
goto Failed
|
|
}
|
|
if w.Source != g.Source {
|
|
goto Failed
|
|
}
|
|
}
|
|
return ""
|
|
Failed:
|
|
msg := &bytes.Buffer{}
|
|
fmt.Fprintf(msg, "diagnostics failed for %s:\nexpected:\n", uri)
|
|
for _, d := range want {
|
|
fmt.Fprintf(msg, " %v\n", d)
|
|
}
|
|
fmt.Fprintf(msg, "got:\n")
|
|
for _, d := range got {
|
|
fmt.Fprintf(msg, " %v\n", d)
|
|
}
|
|
return msg.String()
|
|
}
|
|
|
|
func (c completions) test(t *testing.T, exported *packagestest.Exported, s *Server, items completionItems) {
|
|
for src, itemList := range c {
|
|
var want []protocol.CompletionItem
|
|
for _, pos := range itemList {
|
|
want = append(want, *items[pos])
|
|
}
|
|
list, err := s.Completion(context.Background(), &protocol.CompletionParams{
|
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
|
TextDocument: protocol.TextDocumentIdentifier{
|
|
URI: protocol.NewURI(span.FileURI(src.Filename)),
|
|
},
|
|
Position: protocol.Position{
|
|
Line: float64(src.Line - 1),
|
|
Character: float64(src.Column - 1),
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wantBuiltins := strings.Contains(src.Filename, "builtins")
|
|
var got []protocol.CompletionItem
|
|
for _, item := range list.Items {
|
|
if !wantBuiltins && isBuiltin(item) {
|
|
continue
|
|
}
|
|
got = append(got, item)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("completion failed for %s:%v:%v: %v", filepath.Base(src.Filename), src.Line, src.Column, err)
|
|
}
|
|
if diff := diffCompletionItems(t, src, want, got); diff != "" {
|
|
t.Errorf(diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func isBuiltin(item protocol.CompletionItem) bool {
|
|
// If a type has no detail, it is a builtin type.
|
|
if item.Detail == "" && item.Kind == protocol.TypeParameterCompletion {
|
|
return true
|
|
}
|
|
// Remaining builtin constants, variables, interfaces, and functions.
|
|
trimmed := item.Label
|
|
if i := strings.Index(trimmed, "("); i >= 0 {
|
|
trimmed = trimmed[:i]
|
|
}
|
|
switch trimmed {
|
|
case "append", "cap", "close", "complex", "copy", "delete",
|
|
"error", "false", "imag", "iota", "len", "make", "new",
|
|
"nil", "panic", "print", "println", "real", "recover", "true":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c completions) collect(src token.Position, expected []token.Pos) {
|
|
c[src] = expected
|
|
}
|
|
|
|
func (i completionItems) collect(pos token.Pos, label, detail, kind string) {
|
|
var k protocol.CompletionItemKind
|
|
switch kind {
|
|
case "struct":
|
|
k = protocol.StructCompletion
|
|
case "func":
|
|
k = protocol.FunctionCompletion
|
|
case "var":
|
|
k = protocol.VariableCompletion
|
|
case "type":
|
|
k = protocol.TypeParameterCompletion
|
|
case "field":
|
|
k = protocol.FieldCompletion
|
|
case "interface":
|
|
k = protocol.InterfaceCompletion
|
|
case "const":
|
|
k = protocol.ConstantCompletion
|
|
case "method":
|
|
k = protocol.MethodCompletion
|
|
case "package":
|
|
k = protocol.ModuleCompletion
|
|
}
|
|
i[pos] = &protocol.CompletionItem{
|
|
Label: label,
|
|
Detail: detail,
|
|
Kind: k,
|
|
}
|
|
}
|
|
|
|
// diffCompletionItems prints the diff between expected and actual completion
|
|
// test results.
|
|
func diffCompletionItems(t *testing.T, pos token.Position, want, got []protocol.CompletionItem) string {
|
|
if len(got) != len(want) {
|
|
goto Failed
|
|
}
|
|
for i, w := range want {
|
|
g := got[i]
|
|
if w.Label != g.Label {
|
|
goto Failed
|
|
}
|
|
if w.Detail != g.Detail {
|
|
goto Failed
|
|
}
|
|
if w.Kind != g.Kind {
|
|
goto Failed
|
|
}
|
|
}
|
|
return ""
|
|
Failed:
|
|
msg := &bytes.Buffer{}
|
|
fmt.Fprintf(msg, "completion failed for %s:%v:%v:\nexpected:\n", filepath.Base(pos.Filename), pos.Line, pos.Column)
|
|
for _, d := range want {
|
|
fmt.Fprintf(msg, " %v\n", d)
|
|
}
|
|
fmt.Fprintf(msg, "got:\n")
|
|
for _, d := range got {
|
|
fmt.Fprintf(msg, " %v\n", d)
|
|
}
|
|
return msg.String()
|
|
}
|
|
|
|
func (f formats) test(t *testing.T, s *Server) {
|
|
ctx := context.Background()
|
|
for filename, gofmted := range f {
|
|
uri := span.FileURI(filename)
|
|
edits, err := s.Formatting(context.Background(), &protocol.DocumentFormattingParams{
|
|
TextDocument: protocol.TextDocumentIdentifier{
|
|
URI: protocol.NewURI(uri),
|
|
},
|
|
})
|
|
if err != nil {
|
|
if gofmted != "" {
|
|
t.Error(err)
|
|
}
|
|
continue
|
|
}
|
|
_, m, err := newColumnMap(ctx, s.findView(ctx, uri), uri)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
sedits, err := FromProtocolEdits(m, edits)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
ops := source.EditsToDiff(sedits)
|
|
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(m.Content)), ops), "")
|
|
if gofmted != got {
|
|
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f formats) collect(pos token.Position) {
|
|
cmd := exec.Command("gofmt", pos.Filename)
|
|
stdout := bytes.NewBuffer(nil)
|
|
cmd.Stdout = stdout
|
|
cmd.Run() // ignore error, sometimes we have intentionally ungofmt-able files
|
|
f[pos.Filename] = stdout.String()
|
|
}
|
|
|
|
func (d definitions) test(t *testing.T, s *Server, typ bool) {
|
|
for src, target := range d {
|
|
params := &protocol.TextDocumentPositionParams{
|
|
TextDocument: protocol.TextDocumentIdentifier{
|
|
URI: src.URI,
|
|
},
|
|
Position: src.Range.Start,
|
|
}
|
|
var locs []protocol.Location
|
|
var err error
|
|
if typ {
|
|
locs, err = s.TypeDefinition(context.Background(), params)
|
|
} else {
|
|
locs, err = s.Definition(context.Background(), params)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("failed for %v: %v", src, err)
|
|
}
|
|
if len(locs) != 1 {
|
|
t.Errorf("got %d locations for definition, expected 1", len(locs))
|
|
}
|
|
if locs[0] != target {
|
|
t.Errorf("for %v got %v want %v", src, locs[0], target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d definitions) collect(e *packagestest.Exported, fset *token.FileSet, src, target packagestest.Range) {
|
|
sSrc, mSrc := testLocation(e, fset, src)
|
|
lSrc, err := mSrc.Location(sSrc)
|
|
if err != nil {
|
|
return
|
|
}
|
|
sTarget, mTarget := testLocation(e, fset, target)
|
|
lTarget, err := mTarget.Location(sTarget)
|
|
if err != nil {
|
|
return
|
|
}
|
|
d[lSrc] = lTarget
|
|
}
|
|
|
|
func (h highlights) collect(e *packagestest.Exported, fset *token.FileSet, name string, rng packagestest.Range) {
|
|
s, m := testLocation(e, fset, rng)
|
|
loc, err := m.Location(s)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
h[name] = append(h[name], loc)
|
|
}
|
|
|
|
func (h highlights) test(t *testing.T, s *Server) {
|
|
for name, locations := range h {
|
|
params := &protocol.TextDocumentPositionParams{
|
|
TextDocument: protocol.TextDocumentIdentifier{
|
|
URI: locations[0].URI,
|
|
},
|
|
Position: locations[0].Range.Start,
|
|
}
|
|
highlights, err := s.DocumentHighlight(context.Background(), params)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(highlights) != len(locations) {
|
|
t.Fatalf("got %d highlights for %s, expected %d", len(highlights), name, len(locations))
|
|
}
|
|
for i := range highlights {
|
|
if highlights[i].Range != locations[i].Range {
|
|
t.Errorf("want %v, got %v\n", locations[i].Range, highlights[i].Range)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s symbols) collect(e *packagestest.Exported, fset *token.FileSet, name string, rng span.Range, kind int64, parentName string) {
|
|
f := fset.File(rng.Start)
|
|
if f == nil {
|
|
return
|
|
}
|
|
|
|
content, err := e.FileContents(f.Name())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
spn, err := rng.Span()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
m := protocol.NewColumnMapper(spn.URI(), fset, f, content)
|
|
prng, err := m.Range(spn)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
sym := protocol.DocumentSymbol{
|
|
Name: name,
|
|
Kind: protocol.SymbolKind(kind),
|
|
SelectionRange: prng,
|
|
}
|
|
if parentName == "" {
|
|
s.m[spn.URI()] = append(s.m[spn.URI()], sym)
|
|
} else {
|
|
s.children[parentName] = append(s.children[parentName], sym)
|
|
}
|
|
}
|
|
|
|
func (s symbols) test(t *testing.T, server *Server) {
|
|
for uri, expectedSymbols := range s.m {
|
|
params := &protocol.DocumentSymbolParams{
|
|
TextDocument: protocol.TextDocumentIdentifier{
|
|
URI: string(uri),
|
|
},
|
|
}
|
|
symbols, err := server.DocumentSymbol(context.Background(), params)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(symbols) != len(expectedSymbols) {
|
|
t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols))
|
|
continue
|
|
}
|
|
|
|
sort.Slice(symbols, func(i, j int) bool { return symbols[i].Name < symbols[j].Name })
|
|
sort.Slice(expectedSymbols, func(i, j int) bool { return expectedSymbols[i].Name < expectedSymbols[j].Name })
|
|
for i := range expectedSymbols {
|
|
children := s.children[expectedSymbols[i].Name]
|
|
sort.Slice(children, func(i, j int) bool { return children[i].Name < children[j].Name })
|
|
expectedSymbols[i].Children = children
|
|
}
|
|
if diff := diffSymbols(uri, expectedSymbols, symbols); diff != "" {
|
|
t.Error(diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func diffSymbols(uri span.URI, want, got []protocol.DocumentSymbol) string {
|
|
if len(got) != len(want) {
|
|
goto Failed
|
|
}
|
|
for i, w := range want {
|
|
g := got[i]
|
|
if w.Name != g.Name {
|
|
goto Failed
|
|
}
|
|
if w.Kind != g.Kind {
|
|
goto Failed
|
|
}
|
|
if w.SelectionRange != g.SelectionRange {
|
|
goto Failed
|
|
}
|
|
sort.Slice(g.Children, func(i, j int) bool { return g.Children[i].Name < g.Children[j].Name })
|
|
if msg := diffSymbols(uri, w.Children, g.Children); msg != "" {
|
|
return fmt.Sprintf("children of %s: %s", w.Name, msg)
|
|
}
|
|
}
|
|
return ""
|
|
|
|
Failed:
|
|
msg := &bytes.Buffer{}
|
|
fmt.Fprintf(msg, "document symbols failed for %s:\nexpected:\n", uri)
|
|
for _, s := range want {
|
|
fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
|
|
}
|
|
fmt.Fprintf(msg, "got:\n")
|
|
for _, s := range got {
|
|
fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
|
|
}
|
|
return msg.String()
|
|
}
|
|
|
|
func testLocation(e *packagestest.Exported, fset *token.FileSet, rng packagestest.Range) (span.Span, *protocol.ColumnMapper) {
|
|
spn, err := span.NewRange(fset, rng.Start, rng.End).Span()
|
|
if err != nil {
|
|
return spn, nil
|
|
}
|
|
f := fset.File(rng.Start)
|
|
content, err := e.FileContents(f.Name())
|
|
if err != nil {
|
|
return spn, nil
|
|
}
|
|
m := protocol.NewColumnMapper(spn.URI(), fset, f, content)
|
|
return spn, m
|
|
}
|
|
|
|
func TestBytesOffset(t *testing.T) {
|
|
tests := []struct {
|
|
text string
|
|
pos protocol.Position
|
|
want int
|
|
}{
|
|
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 0}, want: 0},
|
|
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 1}, want: 1},
|
|
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 2}, want: 1},
|
|
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 3}, want: 5},
|
|
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 4}, want: 6},
|
|
{text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 5}, want: -1},
|
|
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 0, Character: 3}, want: 3},
|
|
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 0, Character: 4}, want: -1},
|
|
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 0}, want: 4},
|
|
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 3}, want: 7},
|
|
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 4}, want: -1},
|
|
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 2, Character: 0}, want: 8},
|
|
{text: "aaa\nbbb\n", pos: protocol.Position{Line: 2, Character: 1}, want: -1},
|
|
{text: "aaa\nbbb\n\n", pos: protocol.Position{Line: 2, Character: 0}, want: 8},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
fname := fmt.Sprintf("test %d", i)
|
|
fset := token.NewFileSet()
|
|
f := fset.AddFile(fname, -1, len(test.text))
|
|
f.SetLinesForContent([]byte(test.text))
|
|
mapper := protocol.NewColumnMapper(span.FileURI(fname), fset, f, []byte(test.text))
|
|
got, err := mapper.Point(test.pos)
|
|
if err != nil && test.want != -1 {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
if err == nil && got.Offset() != test.want {
|
|
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())
|
|
}
|
|
}
|
|
}
|