2018-11-07 13:21:31 -07:00
// 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 (
"context"
2018-11-16 16:15:25 -07:00
"fmt"
2018-11-07 13:21:31 -07:00
"go/token"
2019-08-29 13:24:41 -06:00
"os"
2019-04-22 16:15:39 -06:00
"os/exec"
2019-07-08 19:53:01 -06:00
"path/filepath"
2019-03-27 18:00:20 -06:00
"sort"
2020-02-06 12:27:47 -07:00
"strings"
2018-11-07 13:21:31 -07:00
"testing"
"golang.org/x/tools/go/packages/packagestest"
2018-12-05 15:00:36 -07:00
"golang.org/x/tools/internal/lsp/cache"
2019-04-08 07:22:58 -06:00
"golang.org/x/tools/internal/lsp/diff"
2020-04-14 21:45:24 -06:00
"golang.org/x/tools/internal/lsp/diff/myers"
2020-02-06 12:50:26 -07:00
"golang.org/x/tools/internal/lsp/mod"
2018-11-07 13:21:31 -07:00
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
2019-04-16 13:47:48 -06:00
"golang.org/x/tools/internal/lsp/tests"
2019-02-19 19:11:15 -07:00
"golang.org/x/tools/internal/span"
2019-08-30 10:02:01 -06:00
"golang.org/x/tools/internal/testenv"
2018-11-07 13:21:31 -07:00
)
2019-08-29 13:24:41 -06:00
func TestMain ( m * testing . M ) {
2019-08-30 10:02:01 -06:00
testenv . ExitIfSmallMachine ( )
2019-08-29 13:24:41 -06:00
os . Exit ( m . Run ( ) )
}
2018-11-07 13:21:31 -07:00
func TestLSP ( t * testing . T ) {
packagestest . TestAll ( t , testLSP )
}
2019-04-16 13:47:48 -06:00
type runner struct {
2020-01-31 15:27:08 -07:00
server * Server
data * tests . Data
2020-03-31 21:53:42 -06:00
diagnostics map [ span . URI ] [ ] * source . Diagnostic
2020-01-31 15:27:08 -07:00
ctx context . Context
2019-04-16 13:47:48 -06:00
}
2019-05-15 10:24:49 -06:00
func testLSP ( t * testing . T , exporter packagestest . Exporter ) {
2019-07-10 19:11:23 -06:00
ctx := tests . Context ( t )
2019-04-16 13:47:48 -06:00
data := tests . Load ( t , exporter , "testdata" )
2018-11-07 13:21:31 -07:00
2020-02-03 09:12:57 -07:00
for _ , datum := range data {
defer datum . Exported . Cleanup ( )
2020-02-28 08:30:03 -07:00
cache := cache . New ( ctx , nil )
session := cache . NewSession ( ctx )
2020-02-03 09:12:57 -07:00
options := tests . DefaultOptions ( )
session . SetOptions ( options )
options . Env = datum . Config . Env
2020-04-01 21:22:23 -06:00
v , snapshot , err := session . NewView ( ctx , datum . Config . Dir , span . URIFromPath ( datum . Config . Dir ) , options )
2020-01-29 09:16:19 -07:00
if err != nil {
2020-02-03 09:12:57 -07:00
t . Fatal ( err )
2019-12-10 10:29:37 -07:00
}
2020-04-01 21:22:23 -06:00
// Enable type error analyses for tests.
// TODO(golang/go#38212): Delete this once they are enabled by default.
tests . EnableAllAnalyzers ( snapshot , & options )
v . SetOptions ( ctx , options )
2020-01-29 09:16:19 -07:00
// Check to see if the -modfile flag is available, this is basically a check
// to see if the go version >= 1.14. Otherwise, the modfile specific tests
// will always fail if this flag is not available.
2020-02-06 15:49:19 -07:00
for _ , flag := range v . Snapshot ( ) . Config ( ctx ) . BuildFlags {
2020-01-29 09:16:19 -07:00
if strings . Contains ( flag , "-modfile=" ) {
datum . ModfileFlagAvailable = true
break
}
}
2020-02-03 09:12:57 -07:00
var modifications [ ] source . FileModification
for filename , content := range datum . Config . Overlay {
kind := source . DetectLanguage ( "" , filename )
if kind != source . Go {
continue
}
modifications = append ( modifications , source . FileModification {
2020-02-12 14:36:46 -07:00
URI : span . URIFromPath ( filename ) ,
2020-02-03 09:12:57 -07:00
Action : source . Open ,
Version : - 1 ,
Text : content ,
LanguageID : "go" ,
} )
}
if _ , err := session . DidModifyFiles ( ctx , modifications ) ; err != nil {
t . Fatal ( err )
}
r := & runner {
2020-02-18 14:44:12 -07:00
server : NewServer ( session , nil ) ,
data : datum ,
ctx : ctx ,
2020-02-03 09:12:57 -07:00
}
2020-03-30 06:58:57 -06:00
t . Run ( tests . FormatFolderName ( datum . Folder ) , func ( t * testing . T ) {
2020-02-03 09:12:57 -07:00
t . Helper ( )
tests . Run ( t , r , datum )
2020-01-22 19:58:50 -07:00
} )
}
2019-03-30 14:18:22 -06:00
}
2018-11-14 22:05:30 -07:00
2020-02-26 12:11:30 -07:00
func ( r * runner ) CodeLens ( t * testing . T , uri span . URI , want [ ] protocol . CodeLens ) {
if source . DetectLanguage ( "" , uri . Filename ( ) ) != source . Mod {
2020-02-06 12:50:26 -07:00
return
}
2020-02-26 12:11:30 -07:00
v , err := r . server . session . ViewOf ( uri )
2020-02-06 12:50:26 -07:00
if err != nil {
t . Fatal ( err )
}
2020-02-26 12:11:30 -07:00
got , err := mod . CodeLens ( r . ctx , v . Snapshot ( ) , uri )
2020-02-06 12:50:26 -07:00
if err != nil {
t . Fatal ( err )
}
2020-02-26 12:11:30 -07:00
if diff := tests . DiffCodeLens ( uri , want , got ) ; diff != "" {
2020-02-06 12:50:26 -07:00
t . Error ( diff )
}
}
2020-03-31 21:53:42 -06:00
func ( r * runner ) Diagnostics ( t * testing . T , uri span . URI , want [ ] * source . Diagnostic ) {
2020-01-31 15:27:08 -07:00
// Get the diagnostics for this view if we have not done it before.
if r . diagnostics == nil {
2020-03-31 21:53:42 -06:00
r . diagnostics = make ( map [ span . URI ] [ ] * source . Diagnostic )
2020-02-03 09:12:57 -07:00
v := r . server . session . View ( r . data . Config . Dir )
2020-01-31 15:27:08 -07:00
// Always run diagnostics with analysis.
2020-04-29 11:33:43 -06:00
reports , _ := r . server . diagnose ( r . ctx , v . Snapshot ( ) , true )
2020-01-31 15:27:08 -07:00
for key , diags := range reports {
r . diagnostics [ key . id . URI ] = diags
}
2019-09-26 11:56:23 -06:00
}
2020-01-31 15:27:08 -07:00
got := r . diagnostics [ uri ]
2019-09-26 11:56:23 -06:00
// A special case to test that there are no diagnostics for a file.
if len ( want ) == 1 && want [ 0 ] . Source == "no_diagnostics" {
if len ( got ) != 0 {
t . Errorf ( "expected no diagnostics for %s, got %v" , uri , got )
2018-11-20 14:05:10 -07:00
}
2019-09-26 11:56:23 -06:00
return
}
2019-11-21 13:14:48 -07:00
if diff := tests . DiffDiagnostics ( uri , want , got ) ; diff != "" {
2019-09-26 11:56:23 -06:00
t . Error ( diff )
2018-11-20 14:05:10 -07:00
}
}
2019-11-10 07:43:06 -07:00
func ( r * runner ) FoldingRanges ( t * testing . T , spn span . Span ) {
2019-09-26 11:56:23 -06:00
uri := spn . URI ( )
2019-11-15 10:43:45 -07:00
view , err := r . server . session . ViewOf ( uri )
if err != nil {
t . Fatal ( err )
}
2019-09-26 11:56:23 -06:00
original := view . Options ( )
modified := original
// Test all folding ranges.
modified . LineFoldingOnly = false
2019-11-15 10:43:45 -07:00
view , err = view . SetOptions ( r . ctx , modified )
2019-11-08 11:25:29 -07:00
if err != nil {
t . Error ( err )
return
}
2019-09-26 11:56:23 -06:00
ranges , err := r . server . FoldingRange ( r . ctx , & protocol . FoldingRangeParams {
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( uri ) ,
2019-09-26 11:56:23 -06:00
} ,
} )
if err != nil {
t . Error ( err )
return
}
r . foldingRanges ( t , "foldingRange" , uri , ranges )
2019-08-28 19:48:29 -06:00
2019-09-26 11:56:23 -06:00
// Test folding ranges with lineFoldingOnly = true.
modified . LineFoldingOnly = true
2019-11-08 11:25:29 -07:00
view , err = view . SetOptions ( r . ctx , modified )
if err != nil {
t . Error ( err )
return
}
2019-09-26 11:56:23 -06:00
ranges , err = r . server . FoldingRange ( r . ctx , & protocol . FoldingRangeParams {
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( uri ) ,
2019-09-26 11:56:23 -06:00
} ,
} )
if err != nil {
t . Error ( err )
return
2019-08-29 17:03:23 -06:00
}
2019-09-26 11:56:23 -06:00
r . foldingRanges ( t , "foldingRange-lineFolding" , uri , ranges )
2019-11-08 11:25:29 -07:00
view . SetOptions ( r . ctx , original )
2019-08-29 17:03:23 -06:00
}
func ( r * runner ) foldingRanges ( t * testing . T , prefix string , uri span . URI , ranges [ ] protocol . FoldingRange ) {
2019-09-09 22:36:39 -06:00
m , err := r . data . Mapper ( uri )
2019-08-29 17:03:23 -06:00
if err != nil {
t . Fatal ( err )
}
// Fold all ranges.
nonOverlapping := nonOverlappingRanges ( ranges )
for i , rngs := range nonOverlapping {
got , err := foldRanges ( m , string ( m . Content ) , rngs )
2019-08-28 19:48:29 -06:00
if err != nil {
2019-08-29 17:03:23 -06:00
t . Error ( err )
continue
2019-08-28 19:48:29 -06:00
}
2019-08-29 17:03:23 -06:00
tag := fmt . Sprintf ( "%s-%d" , prefix , i )
want := string ( r . data . Golden ( tag , uri . Filename ( ) , func ( ) ( [ ] byte , error ) {
return [ ] byte ( got ) , nil
} ) )
if want != got {
t . Errorf ( "%s: foldingRanges failed for %s, expected:\n%v\ngot:\n%v" , tag , uri . Filename ( ) , want , got )
}
}
2019-08-28 19:48:29 -06:00
2019-08-29 17:03:23 -06:00
// Filter by kind.
kinds := [ ] protocol . FoldingRangeKind { protocol . Imports , protocol . Comment }
for _ , kind := range kinds {
var kindOnly [ ] protocol . FoldingRange
for _ , fRng := range ranges {
if fRng . Kind == string ( kind ) {
kindOnly = append ( kindOnly , fRng )
}
}
nonOverlapping := nonOverlappingRanges ( kindOnly )
2019-08-29 13:28:51 -06:00
for i , rngs := range nonOverlapping {
got , err := foldRanges ( m , string ( m . Content ) , rngs )
if err != nil {
t . Error ( err )
continue
}
2019-08-29 17:03:23 -06:00
tag := fmt . Sprintf ( "%s-%s-%d" , prefix , kind , i )
want := string ( r . data . Golden ( tag , uri . Filename ( ) , func ( ) ( [ ] byte , error ) {
2019-08-29 13:28:51 -06:00
return [ ] byte ( got ) , nil
} ) )
2019-08-28 19:48:29 -06:00
2019-08-29 13:28:51 -06:00
if want != got {
2019-08-29 17:03:23 -06:00
t . Errorf ( "%s: foldingRanges failed for %s, expected:\n%v\ngot:\n%v" , tag , uri . Filename ( ) , want , got )
2019-08-28 19:48:29 -06:00
}
}
}
}
2019-08-29 13:28:51 -06:00
func nonOverlappingRanges ( ranges [ ] protocol . FoldingRange ) ( res [ ] [ ] protocol . FoldingRange ) {
for _ , fRng := range ranges {
setNum := len ( res )
for i := 0 ; i < len ( res ) ; i ++ {
canInsert := true
for _ , rng := range res [ i ] {
if conflict ( rng , fRng ) {
canInsert = false
break
}
}
if canInsert {
setNum = i
break
}
}
if setNum == len ( res ) {
res = append ( res , [ ] protocol . FoldingRange { } )
}
res [ setNum ] = append ( res [ setNum ] , fRng )
}
return res
}
func conflict ( a , b protocol . FoldingRange ) bool {
// a start position is <= b start positions
return ( a . StartLine < b . StartLine || ( a . StartLine == b . StartLine && a . StartCharacter <= b . StartCharacter ) ) &&
( a . EndLine > b . StartLine || ( a . EndLine == b . StartLine && a . EndCharacter > b . StartCharacter ) )
}
2019-08-28 19:48:29 -06:00
func foldRanges ( m * protocol . ColumnMapper , contents string , ranges [ ] protocol . FoldingRange ) ( string , error ) {
foldedText := "<>"
res := contents
// Apply the edits from the end of the file forward
// to preserve the offsets
for i := len ( ranges ) - 1 ; i >= 0 ; i -- {
fRange := ranges [ i ]
spn , err := m . RangeSpan ( protocol . Range {
Start : protocol . Position {
Line : fRange . StartLine ,
Character : fRange . StartCharacter ,
} ,
End : protocol . Position {
Line : fRange . EndLine ,
Character : fRange . EndCharacter ,
} ,
} )
if err != nil {
return "" , err
}
start := spn . Start ( ) . Offset ( )
end := spn . End ( ) . Offset ( )
tmp := res [ 0 : start ] + foldedText
res = tmp + res [ end : ]
}
return res , nil
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) Format ( t * testing . T , spn span . Span ) {
uri := spn . URI ( )
filename := uri . Filename ( )
gofmted := string ( r . data . Golden ( "gofmt" , filename , func ( ) ( [ ] byte , error ) {
cmd := exec . Command ( "gofmt" , filename )
out , _ := cmd . Output ( ) // ignore error, sometimes we have intentionally ungofmt-able files
return out , nil
} ) )
edits , err := r . server . Formatting ( r . ctx , & protocol . DocumentFormattingParams {
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( uri ) ,
2019-09-26 11:56:23 -06:00
} ,
} )
if err != nil {
if gofmted != "" {
2019-03-11 14:41:00 -06:00
t . Error ( err )
2019-01-17 09:59:05 -07:00
}
2019-09-26 11:56:23 -06:00
return
}
m , err := r . data . Mapper ( uri )
if err != nil {
t . Fatal ( err )
}
sedits , err := source . FromProtocolEdits ( m , edits )
if err != nil {
t . Error ( err )
}
got := diff . ApplyEdits ( string ( m . Content ) , sedits )
if gofmted != got {
t . Errorf ( "format failed for %s, expected:\n%v\ngot:\n%v" , filename , gofmted , got )
2018-11-14 18:42:30 -07:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) Import ( t * testing . T , spn span . Span ) {
uri := spn . URI ( )
filename := uri . Filename ( )
actions , err := r . server . CodeAction ( r . ctx , & protocol . CodeActionParams {
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( uri ) ,
2019-09-26 11:56:23 -06:00
} ,
} )
if err != nil {
2019-12-04 18:39:06 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
m , err := r . data . Mapper ( uri )
if err != nil {
t . Fatal ( err )
}
2019-11-12 15:58:37 -07:00
got := string ( m . Content )
2019-11-19 06:42:45 -07:00
if len ( actions ) > 0 {
res , err := applyWorkspaceEdits ( r , actions [ 0 ] . Edit )
2019-11-12 15:58:37 -07:00
if err != nil {
t . Fatal ( err )
2019-05-31 20:42:59 -06:00
}
2019-11-12 15:58:37 -07:00
got = res [ uri ]
2019-05-31 20:42:59 -06:00
}
2019-12-04 18:39:06 -07:00
want := string ( r . data . Golden ( "goimports" , filename , func ( ) ( [ ] byte , error ) {
return [ ] byte ( got ) , nil
} ) )
if want != got {
2020-04-14 21:45:24 -06:00
d := myers . ComputeEdits ( uri , want , got )
t . Errorf ( "import failed for %s: %s" , filename , diff . ToUnified ( "want" , "got" , want , d ) )
2019-09-26 11:56:23 -06:00
}
2019-05-31 20:42:59 -06:00
}
2020-03-05 07:42:47 -07:00
func ( r * runner ) SuggestedFix ( t * testing . T , spn span . Span , actionKinds [ ] string ) {
2019-09-26 11:56:23 -06:00
uri := spn . URI ( )
2019-11-15 10:43:45 -07:00
view , err := r . server . session . ViewOf ( uri )
if err != nil {
t . Fatal ( err )
}
2020-01-22 12:23:12 -07:00
m , err := r . data . Mapper ( uri )
if err != nil {
t . Fatal ( err )
}
rng , err := m . Range ( spn )
if err != nil {
t . Fatal ( err )
}
2020-02-05 08:18:40 -07:00
// Get the diagnostics for this view if we have not done it before.
if r . diagnostics == nil {
2020-03-31 21:53:42 -06:00
r . diagnostics = make ( map [ span . URI ] [ ] * source . Diagnostic )
2020-02-05 08:18:40 -07:00
// Always run diagnostics with analysis.
2020-04-29 11:33:43 -06:00
reports , _ := r . server . diagnose ( r . ctx , view . Snapshot ( ) , true )
2020-02-05 08:18:40 -07:00
for key , diags := range reports {
r . diagnostics [ key . id . URI ] = diags
}
2019-09-26 11:56:23 -06:00
}
2020-01-22 12:23:12 -07:00
var diag * source . Diagnostic
for _ , d := range r . diagnostics [ uri ] {
// Compare the start positions rather than the entire range because
// some diagnostics have a range with the same start and end position (8:1-8:1).
// The current marker functionality prevents us from having a range of 0 length.
if protocol . ComparePosition ( d . Range . Start , rng . Start ) == 0 {
2020-03-31 21:53:42 -06:00
diag = d
2020-01-22 12:23:12 -07:00
break
}
}
if diag == nil {
t . Fatalf ( "could not get any suggested fixes for %v" , spn )
}
2020-03-05 07:42:47 -07:00
codeActionKinds := [ ] protocol . CodeActionKind { }
for _ , k := range actionKinds {
codeActionKinds = append ( codeActionKinds , protocol . CodeActionKind ( k ) )
}
2019-09-26 11:56:23 -06:00
actions , err := r . server . CodeAction ( r . ctx , & protocol . CodeActionParams {
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( uri ) ,
2019-09-26 11:56:23 -06:00
} ,
Context : protocol . CodeActionContext {
2020-03-05 07:42:47 -07:00
Only : codeActionKinds ,
2020-03-31 21:53:42 -06:00
Diagnostics : toProtocolDiagnostics ( [ ] * source . Diagnostic { diag } ) ,
2019-09-26 11:56:23 -06:00
} ,
} )
if err != nil {
t . Fatal ( err )
}
2019-11-12 15:58:37 -07:00
// TODO: This test should probably be able to handle multiple code actions.
2019-11-20 15:57:05 -07:00
if len ( actions ) == 0 {
t . Fatal ( "no code actions returned" )
}
2019-11-19 06:42:45 -07:00
res , err := applyWorkspaceEdits ( r , actions [ 0 ] . Edit )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-11-12 15:58:37 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2020-01-22 12:23:12 -07:00
for u , got := range res {
fixed := string ( r . data . Golden ( "suggestedfix_" + tests . SpanName ( spn ) , u . Filename ( ) , func ( ) ( [ ] byte , error ) {
return [ ] byte ( got ) , nil
} ) )
if fixed != got {
t . Errorf ( "suggested fixes failed for %s, expected:\n%#v\ngot:\n%#v" , u . Filename ( ) , fixed , got )
}
2019-09-26 11:56:23 -06:00
}
2019-09-04 11:16:09 -06:00
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) Definition ( t * testing . T , spn span . Span , d tests . Definition ) {
sm , err := r . data . Mapper ( d . Src . URI ( ) )
if err != nil {
t . Fatal ( err )
}
loc , err := sm . Location ( d . Src )
if err != nil {
t . Fatalf ( "failed for %v: %v" , d . Src , err )
}
tdpp := protocol . TextDocumentPositionParams {
TextDocument : protocol . TextDocumentIdentifier { URI : loc . URI } ,
Position : loc . Range . Start ,
}
var locs [ ] protocol . Location
var hover * protocol . Hover
if d . IsType {
params := & protocol . TypeDefinitionParams {
TextDocumentPositionParams : tdpp ,
2019-05-15 15:58:16 -06:00
}
2019-09-26 11:56:23 -06:00
locs , err = r . server . TypeDefinition ( r . ctx , params )
} else {
params := & protocol . DefinitionParams {
TextDocumentPositionParams : tdpp ,
}
locs , err = r . server . Definition ( r . ctx , params )
2019-04-16 13:47:48 -06:00
if err != nil {
2019-09-26 11:56:23 -06:00
t . Fatalf ( "failed for %v: %+v" , d . Src , err )
2019-04-16 13:47:48 -06:00
}
2019-09-26 11:56:23 -06:00
v := & protocol . HoverParams {
TextDocumentPositionParams : tdpp ,
2018-12-03 15:14:30 -07:00
}
2019-09-26 11:56:23 -06:00
hover , err = r . server . Hover ( r . ctx , v )
}
if err != nil {
t . Fatalf ( "failed for %v: %v" , d . Src , err )
}
if len ( locs ) != 1 {
t . Errorf ( "got %d locations for definition, expected 1" , len ( locs ) )
}
2019-11-19 12:51:46 -07:00
didSomething := false
2019-09-26 11:56:23 -06:00
if hover != nil {
2019-11-19 12:51:46 -07:00
didSomething = true
2019-09-26 11:56:23 -06:00
tag := fmt . Sprintf ( "%s-hover" , d . Name )
expectHover := string ( r . data . Golden ( tag , d . Src . URI ( ) . Filename ( ) , func ( ) ( [ ] byte , error ) {
return [ ] byte ( hover . Contents . Value ) , nil
} ) )
if hover . Contents . Value != expectHover {
2020-04-27 10:55:45 -06:00
t . Errorf ( "%s:\n%s" , d . Src , tests . Diff ( expectHover , hover . Contents . Value ) )
2018-12-03 15:14:30 -07:00
}
2019-11-19 12:51:46 -07:00
}
if ! d . OnlyHover {
didSomething = true
2020-02-12 14:36:46 -07:00
locURI := locs [ 0 ] . URI . SpanURI ( )
2019-09-26 11:56:23 -06:00
lm , err := r . data . Mapper ( locURI )
2018-11-14 19:11:16 -07:00
if err != nil {
2019-09-26 11:56:23 -06:00
t . Fatal ( err )
2018-11-14 19:11:16 -07:00
}
2019-09-26 11:56:23 -06:00
if def , err := lm . Span ( locs [ 0 ] ) ; err != nil {
t . Fatalf ( "failed for %v: %v" , locs [ 0 ] , err )
} else if def != d . Def {
t . Errorf ( "for %v got %v want %v" , d . Src , def , d . Def )
2018-11-14 19:11:16 -07:00
}
2019-11-19 12:51:46 -07:00
}
if ! didSomething {
2019-09-26 11:56:23 -06:00
t . Errorf ( "no tests ran for %s" , d . Src . URI ( ) )
}
}
2019-12-05 20:27:32 -07:00
func ( r * runner ) Implementation ( t * testing . T , spn span . Span , impls [ ] span . Span ) {
sm , err := r . data . Mapper ( spn . URI ( ) )
2019-10-28 13:16:55 -06:00
if err != nil {
t . Fatal ( err )
}
2019-12-05 20:27:32 -07:00
loc , err := sm . Location ( spn )
2019-10-28 13:16:55 -06:00
if err != nil {
2019-12-05 20:27:32 -07:00
t . Fatalf ( "failed for %v: %v" , spn , err )
2019-10-28 13:16:55 -06:00
}
tdpp := protocol . TextDocumentPositionParams {
TextDocument : protocol . TextDocumentIdentifier { URI : loc . URI } ,
Position : loc . Range . Start ,
}
var locs [ ] protocol . Location
params := & protocol . ImplementationParams {
TextDocumentPositionParams : tdpp ,
}
locs , err = r . server . Implementation ( r . ctx , params )
if err != nil {
2019-12-05 20:27:32 -07:00
t . Fatalf ( "failed for %v: %v" , spn , err )
2019-10-28 13:16:55 -06:00
}
2019-12-05 20:27:32 -07:00
if len ( locs ) != len ( impls ) {
t . Fatalf ( "got %d locations for implementation, expected %d" , len ( locs ) , len ( impls ) )
2019-10-28 13:16:55 -06:00
}
2019-11-25 12:40:42 -07:00
var results [ ] span . Span
2019-10-28 13:16:55 -06:00
for i := range locs {
2020-02-12 14:36:46 -07:00
locURI := locs [ i ] . URI . SpanURI ( )
2019-10-28 13:16:55 -06:00
lm , err := r . data . Mapper ( locURI )
if err != nil {
t . Fatal ( err )
}
2019-11-25 12:40:42 -07:00
imp , err := lm . Span ( locs [ i ] )
if err != nil {
2019-10-28 13:16:55 -06:00
t . Fatalf ( "failed for %v: %v" , locs [ i ] , err )
2019-11-25 12:40:42 -07:00
}
results = append ( results , imp )
}
// Sort results and expected to make tests deterministic.
sort . SliceStable ( results , func ( i , j int ) bool {
return span . Compare ( results [ i ] , results [ j ] ) == - 1
} )
2019-12-05 20:27:32 -07:00
sort . SliceStable ( impls , func ( i , j int ) bool {
return span . Compare ( impls [ i ] , impls [ j ] ) == - 1
2019-11-25 12:40:42 -07:00
} )
for i := range results {
2019-12-05 20:27:32 -07:00
if results [ i ] != impls [ i ] {
t . Errorf ( "for %dth implementation of %v got %v want %v" , i , spn , results [ i ] , impls [ i ] )
2019-10-28 13:16:55 -06:00
}
}
}
2019-11-19 12:18:53 -07:00
func ( r * runner ) Highlight ( t * testing . T , src span . Span , locations [ ] span . Span ) {
2019-11-21 17:26:14 -07:00
m , err := r . data . Mapper ( src . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
2019-11-21 17:26:14 -07:00
loc , err := m . Location ( src )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatalf ( "failed for %v: %v" , locations [ 0 ] , err )
}
tdpp := protocol . TextDocumentPositionParams {
TextDocument : protocol . TextDocumentIdentifier { URI : loc . URI } ,
Position : loc . Range . Start ,
}
params := & protocol . DocumentHighlightParams {
TextDocumentPositionParams : tdpp ,
}
highlights , err := r . server . DocumentHighlight ( r . ctx , params )
if err != nil {
t . Fatal ( err )
}
if len ( highlights ) != len ( locations ) {
2019-11-19 12:18:53 -07:00
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 ) )
2019-09-26 11:56:23 -06:00
}
2019-11-21 17:26:14 -07:00
// Check to make sure highlights have a valid range.
var results [ ] span . Span
2019-09-26 11:56:23 -06:00
for i := range highlights {
2019-11-21 17:26:14 -07:00
h , err := m . RangeSpan ( highlights [ i ] . Range )
if err != nil {
2019-09-26 11:56:23 -06:00
t . Fatalf ( "failed for %v: %v" , highlights [ i ] , err )
2019-11-21 17:26:14 -07:00
}
results = append ( results , h )
}
// Sort results to make tests deterministic since DocumentHighlight uses a map.
sort . SliceStable ( results , func ( i , j int ) bool {
return span . Compare ( results [ i ] , results [ j ] ) == - 1
} )
// Check to make sure all the expected highlights are found.
for i := range results {
if results [ i ] != locations [ i ] {
t . Errorf ( "want %v, got %v\n" , locations [ i ] , results [ i ] )
2019-04-29 18:58:12 -06:00
}
2018-11-14 19:11:16 -07:00
}
}
2019-10-19 15:26:56 -06:00
func ( r * runner ) References ( t * testing . T , src span . Span , itemList [ ] span . Span ) {
2019-09-26 11:56:23 -06:00
sm , err := r . data . Mapper ( src . URI ( ) )
if err != nil {
t . Fatal ( err )
}
loc , err := sm . Location ( src )
if err != nil {
t . Fatalf ( "failed for %v: %v" , src , err )
}
2020-01-21 23:44:33 -07:00
for _ , includeDeclaration := range [ ] bool { true , false } {
t . Run ( fmt . Sprintf ( "refs-declaration-%v" , includeDeclaration ) , func ( t * testing . T ) {
want := make ( map [ protocol . Location ] bool )
for i , pos := range itemList {
// We don't want the first result if we aren't including the declaration.
if i == 0 && ! includeDeclaration {
continue
}
m , err := r . data . Mapper ( pos . URI ( ) )
if err != nil {
t . Fatal ( err )
}
loc , err := m . Location ( pos )
if err != nil {
t . Fatalf ( "failed for %v: %v" , src , err )
}
want [ loc ] = true
}
params := & protocol . ReferenceParams {
TextDocumentPositionParams : protocol . TextDocumentPositionParams {
TextDocument : protocol . TextDocumentIdentifier { URI : loc . URI } ,
Position : loc . Range . Start ,
} ,
Context : protocol . ReferenceContext {
IncludeDeclaration : includeDeclaration ,
} ,
}
got , err := r . server . References ( r . ctx , params )
if err != nil {
t . Fatalf ( "failed for %v: %v" , src , err )
}
if len ( got ) != len ( want ) {
t . Errorf ( "references failed: different lengths got %v want %v" , len ( got ) , len ( want ) )
}
for _ , loc := range got {
if ! want [ loc ] {
t . Errorf ( "references failed: incorrect references got %v want %v" , loc , want )
}
}
} )
2019-03-25 18:56:05 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) Rename ( t * testing . T , spn span . Span , newText string ) {
tag := fmt . Sprintf ( "%s-rename" , newText )
2019-06-07 08:04:22 -06:00
2019-09-26 11:56:23 -06:00
uri := spn . URI ( )
filename := uri . Filename ( )
sm , err := r . data . Mapper ( uri )
if err != nil {
t . Fatal ( err )
}
loc , err := sm . Location ( spn )
if err != nil {
t . Fatalf ( "failed for %v: %v" , spn , err )
2019-06-07 08:04:22 -06:00
}
2019-11-12 15:58:37 -07:00
wedit , err := r . server . Rename ( r . ctx , & protocol . RenameParams {
2019-09-26 11:56:23 -06:00
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( uri ) ,
2019-09-26 11:56:23 -06:00
} ,
Position : loc . Range . Start ,
NewName : newText ,
} )
if err != nil {
renamed := string ( r . data . Golden ( tag , filename , func ( ) ( [ ] byte , error ) {
return [ ] byte ( err . Error ( ) ) , nil
} ) )
if err . Error ( ) != renamed {
t . Errorf ( "rename failed for %s, expected:\n%v\ngot:\n%v\n" , newText , renamed , err )
}
return
}
2019-11-17 12:29:15 -07:00
res , err := applyWorkspaceEdits ( r , * wedit )
2019-11-12 15:58:37 -07:00
if err != nil {
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2019-11-12 15:58:37 -07:00
var orderedURIs [ ] string
for uri := range res {
orderedURIs = append ( orderedURIs , string ( uri ) )
}
sort . Strings ( orderedURIs )
2019-07-08 19:53:01 -06:00
2019-09-26 11:56:23 -06:00
var got string
2019-11-12 15:58:37 -07:00
for i := 0 ; i < len ( res ) ; i ++ {
2019-09-26 11:56:23 -06:00
if i != 0 {
got += "\n"
2019-07-08 19:53:01 -06:00
}
2020-02-12 14:36:46 -07:00
uri := span . URIFromURI ( orderedURIs [ i ] )
2019-11-12 15:58:37 -07:00
if len ( res ) > 1 {
got += filepath . Base ( uri . Filename ( ) ) + ":\n"
}
val := res [ uri ]
2019-09-26 11:56:23 -06:00
got += val
}
2019-06-18 08:23:37 -06:00
2019-09-26 11:56:23 -06:00
renamed := string ( r . data . Golden ( tag , filename , func ( ) ( [ ] byte , error ) {
return [ ] byte ( got ) , nil
} ) )
2019-06-18 08:23:37 -06:00
2019-09-26 11:56:23 -06:00
if renamed != got {
t . Errorf ( "rename failed for %s, expected:\n%v\ngot:\n%v" , newText , renamed , got )
2019-06-18 08:23:37 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) PrepareRename ( t * testing . T , src span . Span , want * source . PrepareItem ) {
m , err := r . data . Mapper ( src . URI ( ) )
if err != nil {
t . Fatal ( err )
}
loc , err := m . Location ( src )
if err != nil {
t . Fatalf ( "failed for %v: %v" , src , err )
}
tdpp := protocol . TextDocumentPositionParams {
TextDocument : protocol . TextDocumentIdentifier { URI : loc . URI } ,
Position : loc . Range . Start ,
}
params := & protocol . PrepareRenameParams {
TextDocumentPositionParams : tdpp ,
}
got , err := r . server . PrepareRename ( context . Background ( ) , params )
if err != nil {
t . Errorf ( "prepare rename failed for %v: got error: %v" , src , err )
return
}
2019-11-17 12:29:15 -07:00
// we all love typed nils
2020-02-11 08:22:19 -07:00
if got == nil {
2019-09-26 11:56:23 -06:00
if want . Text != "" { // expected an ident.
t . Errorf ( "prepare rename failed for %v: got nil" , src )
2019-08-22 11:31:03 -06:00
}
2019-09-26 11:56:23 -06:00
return
}
2020-02-13 13:09:23 -07:00
if got . Start == got . End {
2019-11-19 12:51:46 -07:00
// Special case for 0-length ranges. Marks can't specify a 0-length range,
// so just compare the start.
2020-02-13 13:09:23 -07:00
if got . Start != want . Range . Start {
t . Errorf ( "prepare rename failed: incorrect point, got %v want %v" , got . Start , want . Range . Start )
2019-11-19 12:51:46 -07:00
}
} else {
2020-02-13 13:09:23 -07:00
if protocol . CompareRange ( * got , want . Range ) != 0 {
t . Errorf ( "prepare rename failed: incorrect range got %v want %v" , * got , want . Range )
2019-11-19 12:51:46 -07:00
}
2019-08-22 11:31:03 -06:00
}
}
2019-11-17 12:29:15 -07:00
func applyWorkspaceEdits ( r * runner , wedit protocol . WorkspaceEdit ) ( map [ span . URI ] string , error ) {
2019-11-12 15:58:37 -07:00
res := map [ span . URI ] string { }
for _ , docEdits := range wedit . DocumentChanges {
2020-02-12 14:36:46 -07:00
uri := docEdits . TextDocument . URI . SpanURI ( )
2019-11-12 15:58:37 -07:00
m , err := r . data . Mapper ( uri )
if err != nil {
return nil , err
}
res [ uri ] = string ( m . Content )
sedits , err := source . FromProtocolEdits ( m , docEdits . Edits )
if err != nil {
return nil , err
}
res [ uri ] = applyEdits ( res [ uri ] , sedits )
}
return res , nil
}
2019-08-19 17:28:08 -06:00
func applyEdits ( contents string , edits [ ] diff . TextEdit ) string {
2019-06-18 08:23:37 -06:00
res := contents
// Apply the edits from the end of the file forward
// to preserve the offsets
for i := len ( edits ) - 1 ; i >= 0 ; i -- {
edit := edits [ i ]
start := edit . Span . Start ( ) . Offset ( )
end := edit . Span . End ( ) . Offset ( )
tmp := res [ 0 : start ] + edit . NewText
res = tmp + res [ end : ]
}
return res
}
2019-10-27 11:41:48 -06:00
func ( r * runner ) Symbols ( t * testing . T , uri span . URI , expectedSymbols [ ] protocol . DocumentSymbol ) {
2019-09-26 11:56:23 -06:00
params := & protocol . DocumentSymbolParams {
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( uri ) ,
2019-09-26 11:56:23 -06:00
} ,
}
2020-03-02 15:18:50 -07:00
got , err := r . server . DocumentSymbol ( r . ctx , params )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
2020-03-02 15:18:50 -07:00
if len ( got ) != len ( expectedSymbols ) {
t . Errorf ( "want %d top-level symbols in %v, got %d" , len ( expectedSymbols ) , uri , len ( got ) )
2019-09-26 11:56:23 -06:00
return
}
2020-03-02 15:18:50 -07:00
symbols := make ( [ ] protocol . DocumentSymbol , len ( got ) )
for i , s := range got {
s , ok := s . ( protocol . DocumentSymbol )
if ! ok {
t . Fatalf ( "%v: wanted []DocumentSymbols but got %v" , uri , got )
}
symbols [ i ] = s
}
2020-02-06 02:51:53 -07:00
if diff := tests . DiffSymbols ( t , uri , expectedSymbols , symbols ) ; diff != "" {
2019-09-26 11:56:23 -06:00
t . Error ( diff )
internal/lsp: improve signatureHelp in various cases
- show signature for function calls whose function expression is not
an object (e.g. the second call in foo()()). since the function name
is not available, we use the generic "func"
- only provide signature help when the position is on or within the
call expression parens. this is consistent with the one other lsp
server i tried (java). this improves the gopls experience in emacs
where lsp-mode is constantly calling "hover" and
"signatureHelp" ("hover" should be preferred unless you are inside
the function params list)
- use the entire signature type string as the label since that includes
the return values, which are useful to see
- don't qualify the function name with its package. it looks funny to
see "bytes.Cap()" as the help when you are in a call
to (*bytes.Buffer).Cap(). it could be useful to include invocant
type info, but leave it out for now since signature help is meant to
focus on the function parameters.
- don't turn variadic args "foo ...int" into "foo []int" for the
parameter information (i.e. maintain it as "foo ...int")
- when determining active parameter, count the space before a
parameter name as being part of that parameter (e.g. the space
before "b" in "func(a int, b int)")
- handle variadic params when determining the active param (i.e.
highlight "foo(a int, *b ...string*)" on signature help for final
param in `foo(123, "a", "b", "c")`
- don't generate an extra space in formatParams() for unnamed
arguments
I also tweaked the signatureHelp server log message to include the
error message itself, and populated the server's logger in lsp_test.go
to aid in development.
Fixes golang/go#31448
Change-Id: Iefe0e1e3c531d17197c0fa997b949174475a276c
GitHub-Last-Rev: 5c0b8ebd87a8c05d5d8f519ea096f94e89c77e2c
GitHub-Pull-Request: golang/tools#82
Reviewed-on: https://go-review.googlesource.com/c/tools/+/172439
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-04-16 22:54:13 -06:00
}
}
2020-01-05 03:46:20 -07:00
func ( r * runner ) WorkspaceSymbols ( t * testing . T , query string , expectedSymbols [ ] protocol . SymbolInformation , dirs map [ string ] struct { } ) {
2020-04-08 15:56:24 -06:00
r . callWorkspaceSymbols ( t , query , source . SymbolCaseInsensitive , dirs , expectedSymbols )
2020-02-09 05:36:49 -07:00
}
func ( r * runner ) FuzzyWorkspaceSymbols ( t * testing . T , query string , expectedSymbols [ ] protocol . SymbolInformation , dirs map [ string ] struct { } ) {
2020-04-08 15:56:24 -06:00
r . callWorkspaceSymbols ( t , query , source . SymbolFuzzy , dirs , expectedSymbols )
2020-01-05 03:46:20 -07:00
}
2020-02-09 05:36:49 -07:00
func ( r * runner ) CaseSensitiveWorkspaceSymbols ( t * testing . T , query string , expectedSymbols [ ] protocol . SymbolInformation , dirs map [ string ] struct { } ) {
2020-04-08 15:56:24 -06:00
r . callWorkspaceSymbols ( t , query , source . SymbolCaseSensitive , dirs , expectedSymbols )
2020-02-09 05:36:49 -07:00
}
2020-04-08 15:56:24 -06:00
func ( r * runner ) callWorkspaceSymbols ( t * testing . T , query string , matcher source . SymbolMatcher , dirs map [ string ] struct { } , expectedSymbols [ ] protocol . SymbolInformation ) {
2020-02-09 05:36:49 -07:00
t . Helper ( )
2020-04-19 10:07:45 -06:00
original := r . server . session . Options ( )
modified := original
2020-04-08 15:56:24 -06:00
modified . SymbolMatcher = matcher
2020-04-19 10:07:45 -06:00
r . server . session . SetOptions ( modified )
defer r . server . session . SetOptions ( original )
2020-02-09 05:36:49 -07:00
params := & protocol . WorkspaceSymbolParams {
Query : query ,
}
2020-04-19 10:07:45 -06:00
got , err := r . server . Symbol ( r . ctx , params )
2020-02-09 05:36:49 -07:00
if err != nil {
t . Fatal ( err )
}
2020-04-19 10:07:45 -06:00
got = tests . FilterWorkspaceSymbols ( got , dirs )
if len ( got ) != len ( expectedSymbols ) {
t . Errorf ( "want %d symbols, got %d" , len ( expectedSymbols ) , len ( got ) )
return
}
if diff := tests . DiffWorkspaceSymbols ( expectedSymbols , got ) ; diff != "" {
t . Error ( diff )
}
2020-02-09 05:36:49 -07:00
}
2020-02-04 20:44:33 -07:00
func ( r * runner ) SignatureHelp ( t * testing . T , spn span . Span , want * protocol . SignatureHelp ) {
2019-09-26 11:56:23 -06:00
m , err := r . data . Mapper ( spn . URI ( ) )
if err != nil {
t . Fatal ( err )
}
loc , err := m . Location ( spn )
if err != nil {
t . Fatalf ( "failed for %v: %v" , loc , err )
}
tdpp := protocol . TextDocumentPositionParams {
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( spn . URI ( ) ) ,
2019-09-26 11:56:23 -06:00
} ,
Position : loc . Range . Start ,
}
params := & protocol . SignatureHelpParams {
TextDocumentPositionParams : tdpp ,
}
2020-02-04 20:44:33 -07:00
got , err := r . server . SignatureHelp ( r . ctx , params )
2019-09-26 11:56:23 -06:00
if err != nil {
// Only fail if we got an error we did not expect.
2020-02-04 20:44:33 -07:00
if want != nil {
2019-05-15 15:58:16 -06:00
t . Fatal ( err )
}
2019-09-26 11:56:23 -06:00
return
}
2020-02-04 20:44:33 -07:00
if want == nil {
if got != nil {
t . Errorf ( "expected no signature, got %v" , got )
2019-04-16 13:47:48 -06:00
}
2019-09-26 11:56:23 -06:00
return
}
2020-02-04 20:44:33 -07:00
if got == nil {
t . Fatalf ( "expected %v, got nil" , want )
2019-09-26 11:56:23 -06:00
}
2020-02-04 20:44:33 -07:00
if diff := tests . DiffSignatures ( spn , want , got ) ; diff != "" {
2019-09-26 11:56:23 -06:00
t . Error ( diff )
2019-04-16 13:47:48 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) Link ( t * testing . T , uri span . URI , wantLinks [ ] tests . Link ) {
m , err := r . data . Mapper ( uri )
if err != nil {
t . Fatal ( err )
}
2019-11-01 00:48:57 -06:00
got , err := r . server . DocumentLink ( r . ctx , & protocol . DocumentLinkParams {
2019-09-26 11:56:23 -06:00
TextDocument : protocol . TextDocumentIdentifier {
2020-02-12 14:36:46 -07:00
URI : protocol . URIFromSpanURI ( uri ) ,
2019-09-26 11:56:23 -06:00
} ,
} )
if err != nil {
t . Fatal ( err )
}
2019-11-01 00:48:57 -06:00
if diff := tests . DiffLinks ( m , wantLinks , got ) ; diff != "" {
t . Error ( diff )
2019-04-24 09:33:45 -06:00
}
}
2019-02-17 23:00:10 -07:00
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 } ,
2019-03-11 14:42:38 -06:00
{ text : ` a𐐀b ` , pos : protocol . Position { Line : 0 , Character : 4 } , want : 6 } ,
{ text : ` a𐐀b ` , pos : protocol . Position { Line : 0 , Character : 5 } , want : - 1 } ,
2019-02-17 23:00:10 -07:00
{ text : "aaa\nbbb\n" , pos : protocol . Position { Line : 0 , Character : 3 } , want : 3 } ,
2019-07-08 09:07:47 -06:00
{ text : "aaa\nbbb\n" , pos : protocol . Position { Line : 0 , Character : 4 } , want : 3 } ,
2019-02-17 23:00:10 -07:00
{ 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 } ,
2019-07-08 09:07:47 -06:00
{ text : "aaa\nbbb\n" , pos : protocol . Position { Line : 1 , Character : 4 } , want : 7 } ,
2019-03-11 14:42:38 -06:00
{ 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 } ,
2019-02-17 23:00:10 -07:00
{ text : "aaa\nbbb\n\n" , pos : protocol . Position { Line : 2 , Character : 0 } , want : 8 } ,
}
2019-02-19 19:11:15 -07:00
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 ) )
2020-02-12 14:36:46 -07:00
uri := span . URIFromPath ( fname )
2019-09-09 22:36:39 -06:00
converter := span . NewContentConverter ( fname , [ ] byte ( test . text ) )
mapper := & protocol . ColumnMapper {
URI : uri ,
Converter : converter ,
Content : [ ] byte ( test . text ) ,
}
2019-03-15 11:19:43 -06:00
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 ( ) )
2019-02-17 23:00:10 -07:00
}
}
}