2019-05-08 14:04:29 -06: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 source_test
import (
"context"
"fmt"
2019-08-30 10:02:01 -06:00
"os"
2019-05-08 14:04:29 -06:00
"os/exec"
2019-07-08 19:53:01 -06:00
"path/filepath"
2019-05-08 14:04:29 -06:00
"sort"
"strings"
"testing"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/lsp/cache"
"golang.org/x/tools/internal/lsp/diff"
2020-04-14 21:45:24 -06:00
"golang.org/x/tools/internal/lsp/diff/myers"
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
"golang.org/x/tools/internal/lsp/fuzzy"
2019-08-16 15:05:40 -06:00
"golang.org/x/tools/internal/lsp/protocol"
2019-05-08 14:04:29 -06:00
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/tests"
"golang.org/x/tools/internal/span"
2019-08-30 10:02:01 -06:00
"golang.org/x/tools/internal/testenv"
2019-09-17 16:52:19 -06:00
errors "golang.org/x/xerrors"
2019-05-08 14:04:29 -06:00
)
2019-08-30 10:02:01 -06:00
func TestMain ( m * testing . M ) {
testenv . ExitIfSmallMachine ( )
os . Exit ( m . Run ( ) )
}
2019-05-08 14:04:29 -06:00
func TestSource ( t * testing . T ) {
packagestest . TestAll ( t , testSource )
}
type runner struct {
2020-07-02 16:34:10 -06:00
snapshot source . Snapshot
view source . View
data * tests . Data
ctx context . Context
2019-05-08 14:04:29 -06:00
}
func testSource ( t * testing . T , exporter packagestest . Exporter ) {
2019-07-10 19:11:23 -06:00
ctx := tests . Context ( t )
2019-05-08 14:04:29 -06:00
data := tests . Load ( t , exporter , "../testdata" )
2020-02-03 09:12:57 -07:00
for _ , datum := range data {
defer datum . Exported . Cleanup ( )
2019-05-08 14:04:29 -06:00
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 ( )
options . Env = datum . Config . Env
2020-07-02 16:34:10 -06:00
view , _ , release , err := session . NewView ( ctx , "source_test" , span . URIFromPath ( datum . Config . Dir ) , options )
release ( )
2020-02-03 09:12:57 -07:00
if err != nil {
t . Fatal ( err )
}
2020-06-11 22:24:37 -06:00
defer view . Shutdown ( ctx )
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.
2020-07-02 16:34:10 -06:00
tests . EnableAllAnalyzers ( view , & options )
2020-04-01 21:22:23 -06:00
view . SetOptions ( ctx , options )
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" ,
} )
}
2020-07-02 16:34:10 -06:00
if err := session . ModifyFiles ( ctx , modifications ) ; err != nil {
2020-02-03 09:12:57 -07:00
t . Fatal ( err )
}
2020-07-02 16:34:10 -06:00
snapshot , release := view . Snapshot ( )
defer release ( )
r := & runner {
view : view ,
snapshot : snapshot ,
data : datum ,
ctx : ctx ,
}
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-05-08 14:04:29 -06:00
}
2020-03-31 21:53:42 -06:00
func ( r * runner ) Diagnostics ( t * testing . T , uri span . URI , want [ ] * source . Diagnostic ) {
2020-07-02 16:34:10 -06:00
fileID , got , err := source . FileDiagnostics ( r . ctx , r . snapshot , uri )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
// 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 )
2019-05-08 14:04:29 -06:00
}
2019-09-26 11:56:23 -06:00
return
}
2019-11-21 13:14:48 -07:00
if diff := tests . DiffDiagnostics ( fileID . URI , want , got ) ; diff != "" {
2019-09-26 11:56:23 -06:00
t . Error ( diff )
2019-05-08 14:04:29 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) Completion ( t * testing . T , src span . Span , test tests . Completion , items tests . CompletionItems ) {
var want [ ] protocol . CompletionItem
for _ , pos := range test . CompletionItems {
want = append ( want , tests . ToProtocolCompletionItem ( * items [ pos ] ) )
}
2019-12-27 09:57:52 -07:00
_ , got := r . callCompletion ( t , src , func ( opts * source . Options ) {
opts . Matcher = source . CaseInsensitive
2019-12-29 00:22:12 -07:00
opts . DeepCompletion = false
2020-01-15 15:39:57 -07:00
opts . UnimportedCompletion = false
2020-03-01 15:33:21 -07:00
opts . InsertTextFormat = protocol . SnippetTextFormat
if ! strings . Contains ( string ( src . URI ( ) ) , "literal" ) {
opts . LiteralCompletions = false
2020-01-24 11:34:08 -07:00
}
2019-09-26 11:56:23 -06:00
} )
2020-02-02 22:17:44 -07:00
got = tests . FilterBuiltins ( src , got )
2019-09-26 11:56:23 -06:00
if diff := tests . DiffCompletionItems ( want , got ) ; diff != "" {
t . Errorf ( "%s: %s" , src , diff )
2019-05-08 14:04:29 -06:00
}
2019-09-17 09:10:48 -06:00
}
2019-08-27 17:41:48 -06:00
2019-09-26 11:56:23 -06:00
func ( r * runner ) CompletionSnippet ( t * testing . T , src span . Span , expected tests . CompletionSnippet , placeholders bool , items tests . CompletionItems ) {
2019-12-29 00:22:12 -07:00
_ , list := r . callCompletion ( t , src , func ( opts * source . Options ) {
opts . Placeholders = placeholders
opts . DeepCompletion = true
2020-04-06 22:03:22 -06:00
opts . UnimportedCompletion = false
2019-09-26 11:56:23 -06:00
} )
got := tests . FindItem ( list , * items [ expected . CompletionItem ] )
want := expected . PlainSnippet
if placeholders {
want = expected . PlaceholderSnippet
}
if diff := tests . DiffSnippets ( want , got ) ; diff != "" {
t . Errorf ( "%s: %s" , src , diff )
2019-05-08 14:04:29 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) UnimportedCompletion ( t * testing . T , src span . Span , test tests . Completion , items tests . CompletionItems ) {
var want [ ] protocol . CompletionItem
for _ , pos := range test . CompletionItems {
want = append ( want , tests . ToProtocolCompletionItem ( * items [ pos ] ) )
}
2020-01-15 15:39:57 -07:00
_ , got := r . callCompletion ( t , src , func ( opts * source . Options ) { } )
2020-02-02 22:17:44 -07:00
got = tests . FilterBuiltins ( src , got )
2019-11-15 17:14:15 -07:00
if diff := tests . CheckCompletionOrder ( want , got , false ) ; diff != "" {
2019-09-26 11:56:23 -06:00
t . Errorf ( "%s: %s" , src , diff )
2019-05-08 14:04:29 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) DeepCompletion ( t * testing . T , src span . Span , test tests . Completion , items tests . CompletionItems ) {
var want [ ] protocol . CompletionItem
for _ , pos := range test . CompletionItems {
want = append ( want , tests . ToProtocolCompletionItem ( * items [ pos ] ) )
}
2019-12-29 00:22:12 -07:00
prefix , list := r . callCompletion ( t , src , func ( opts * source . Options ) {
opts . DeepCompletion = true
opts . Matcher = source . CaseInsensitive
2020-01-15 15:39:57 -07:00
opts . UnimportedCompletion = false
2019-09-26 11:56:23 -06:00
} )
2020-02-02 22:17:44 -07:00
list = tests . FilterBuiltins ( src , list )
2019-10-21 22:07:21 -06:00
fuzzyMatcher := fuzzy . NewMatcher ( prefix )
2019-09-26 11:56:23 -06:00
var got [ ] protocol . CompletionItem
for _ , item := range list {
2019-09-17 15:59:27 -06:00
if fuzzyMatcher . Score ( item . Label ) <= 0 {
2019-09-26 11:56:23 -06:00
continue
2019-06-27 11:50:01 -06:00
}
2019-09-26 11:56:23 -06:00
got = append ( got , item )
}
if msg := tests . DiffCompletionItems ( want , got ) ; msg != "" {
t . Errorf ( "%s: %s" , src , msg )
2019-06-27 11:50:01 -06:00
}
2019-09-17 09:10:48 -06:00
}
2019-06-27 11:50:01 -06:00
2019-09-26 11:56:23 -06:00
func ( r * runner ) FuzzyCompletion ( t * testing . T , src span . Span , test tests . Completion , items tests . CompletionItems ) {
var want [ ] protocol . CompletionItem
for _ , pos := range test . CompletionItems {
want = append ( want , tests . ToProtocolCompletionItem ( * items [ pos ] ) )
}
2019-12-29 00:22:12 -07:00
_ , got := r . callCompletion ( t , src , func ( opts * source . Options ) {
opts . DeepCompletion = true
opts . Matcher = source . Fuzzy
2020-01-15 15:39:57 -07:00
opts . UnimportedCompletion = false
2019-09-26 11:56:23 -06:00
} )
2020-02-02 22:17:44 -07:00
got = tests . FilterBuiltins ( src , got )
2019-09-26 11:56:23 -06:00
if msg := tests . DiffCompletionItems ( want , got ) ; msg != "" {
t . Errorf ( "%s: %s" , src , msg )
2019-05-08 14:04:29 -06:00
}
2019-06-17 21:56:06 -06:00
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) CaseSensitiveCompletion ( t * testing . T , src span . Span , test tests . Completion , items tests . CompletionItems ) {
var want [ ] protocol . CompletionItem
for _ , pos := range test . CompletionItems {
want = append ( want , tests . ToProtocolCompletionItem ( * items [ pos ] ) )
}
2019-12-29 00:22:12 -07:00
_ , list := r . callCompletion ( t , src , func ( opts * source . Options ) {
opts . Matcher = source . CaseSensitive
2020-01-15 15:39:57 -07:00
opts . UnimportedCompletion = false
2019-09-26 11:56:23 -06:00
} )
2020-02-02 22:17:44 -07:00
list = tests . FilterBuiltins ( src , list )
2019-09-26 11:56:23 -06:00
if diff := tests . DiffCompletionItems ( want , list ) ; diff != "" {
t . Errorf ( "%s: %s" , src , diff )
2019-09-26 05:59:06 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) RankCompletion ( t * testing . T , src span . Span , test tests . Completion , items tests . CompletionItems ) {
var want [ ] protocol . CompletionItem
for _ , pos := range test . CompletionItems {
want = append ( want , tests . ToProtocolCompletionItem ( * items [ pos ] ) )
}
2019-12-29 00:22:12 -07:00
_ , got := r . callCompletion ( t , src , func ( opts * source . Options ) {
opts . DeepCompletion = true
opts . Matcher = source . Fuzzy
2019-09-26 11:56:23 -06:00
} )
2019-11-15 17:14:15 -07:00
if msg := tests . CheckCompletionOrder ( want , got , true ) ; msg != "" {
2019-09-26 11:56:23 -06:00
t . Errorf ( "%s: %s" , src , msg )
2019-06-17 21:56:06 -06:00
}
2019-05-08 14:04:29 -06:00
}
2019-12-29 00:22:12 -07:00
func ( r * runner ) callCompletion ( t * testing . T , src span . Span , options func ( * source . Options ) ) ( string , [ ] protocol . CompletionItem ) {
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , src . URI ( ) )
2019-09-17 09:10:48 -06:00
if err != nil {
t . Fatal ( err )
2019-05-08 14:04:29 -06:00
}
2019-12-29 00:22:12 -07:00
original := r . view . Options ( )
modified := original
options ( & modified )
2020-07-02 16:34:10 -06:00
newView , err := r . view . SetOptions ( r . ctx , modified )
if newView != r . view {
t . Fatalf ( "options change unexpectedly created new view" )
}
2019-12-29 00:22:12 -07:00
if err != nil {
t . Fatal ( err )
}
defer r . view . SetOptions ( r . ctx , original )
2020-07-02 16:34:10 -06:00
list , surrounding , err := source . Completion ( r . ctx , r . snapshot , fh , protocol . Position {
2019-09-17 09:10:48 -06:00
Line : float64 ( src . Start ( ) . Line ( ) - 1 ) ,
Character : float64 ( src . Start ( ) . Column ( ) - 1 ) ,
2019-12-29 00:22:12 -07:00
} )
2019-09-17 16:52:19 -06:00
if err != nil && ! errors . As ( err , & source . ErrIsDefinition { } ) {
2019-09-17 09:10:48 -06:00
t . Fatalf ( "failed for %v: %v" , src , err )
2019-05-08 14:04:29 -06:00
}
2019-09-17 09:10:48 -06:00
var prefix string
if surrounding != nil {
prefix = strings . ToLower ( surrounding . Prefix ( ) )
}
2020-01-16 16:00:13 -07:00
2019-09-17 09:10:48 -06:00
var numDeepCompletionsSeen int
var items [ ] source . CompletionItem
// Apply deep completion filtering.
for _ , item := range list {
if item . Depth > 0 {
2019-12-29 00:22:12 -07:00
if ! modified . DeepCompletion {
2019-09-17 09:10:48 -06:00
continue
}
if numDeepCompletionsSeen >= source . MaxDeepCompletions {
continue
}
numDeepCompletionsSeen ++
}
items = append ( items , item )
2019-05-08 14:04:29 -06:00
}
2019-09-17 09:10:48 -06:00
return prefix , tests . ToProtocolCompletionItems ( items )
2019-05-08 14:04:29 -06: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-08-28 19:48:29 -06:00
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , spn . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
data , err := fh . Read ( )
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
// Test all folding ranges.
2020-07-02 16:34:10 -06:00
ranges , err := source . FoldingRange ( r . ctx , r . snapshot , fh , false )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Error ( err )
return
}
r . foldingRanges ( t , "foldingRange" , uri , string ( data ) , ranges )
2019-08-29 17:03:23 -06:00
2019-09-26 11:56:23 -06:00
// Test folding ranges with lineFoldingOnly
2020-07-02 16:34:10 -06:00
ranges , err = source . FoldingRange ( r . ctx , r . snapshot , fh , true )
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 , string ( data ) , ranges )
2019-08-29 17:03:23 -06:00
}
2019-08-30 12:11:42 -06:00
func ( r * runner ) foldingRanges ( t * testing . T , prefix string , uri span . URI , data string , ranges [ ] * source . FoldingRangeInfo ) {
2019-08-29 17:03:23 -06:00
t . Helper ( )
// Fold all ranges.
2019-09-05 18:14:09 -06:00
nonOverlapping := nonOverlappingRanges ( t , ranges )
2019-08-29 17:03:23 -06:00
for i , rngs := range nonOverlapping {
got , err := foldRanges ( string ( data ) , rngs )
2019-08-28 19:48:29 -06:00
if err != nil {
t . Error ( err )
continue
}
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 )
}
}
// Filter by kind.
kinds := [ ] protocol . FoldingRangeKind { protocol . Imports , protocol . Comment }
for _ , kind := range kinds {
2019-08-30 12:11:42 -06:00
var kindOnly [ ] * source . FoldingRangeInfo
2019-08-29 17:03:23 -06:00
for _ , fRng := range ranges {
if fRng . Kind == kind {
kindOnly = append ( kindOnly , fRng )
}
}
2019-08-29 13:28:51 -06:00
2019-09-05 18:14:09 -06:00
nonOverlapping := nonOverlappingRanges ( t , kindOnly )
2019-08-29 13:28:51 -06:00
for i , rngs := range nonOverlapping {
got , err := foldRanges ( string ( data ) , 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: failed for %s, expected:\n%v\ngot:\n%v" , tag , uri . Filename ( ) , want , got )
2019-08-28 19:48:29 -06:00
}
}
}
}
2019-09-05 18:14:09 -06:00
func nonOverlappingRanges ( t * testing . T , ranges [ ] * source . FoldingRangeInfo ) ( res [ ] [ ] * source . FoldingRangeInfo ) {
2019-08-29 13:28:51 -06:00
for _ , fRng := range ranges {
setNum := len ( res )
for i := 0 ; i < len ( res ) ; i ++ {
canInsert := true
for _ , rng := range res [ i ] {
2019-09-05 18:14:09 -06:00
if conflict ( t , rng , fRng ) {
2019-08-29 13:28:51 -06:00
canInsert = false
break
}
}
if canInsert {
setNum = i
break
}
}
if setNum == len ( res ) {
2019-08-30 12:11:42 -06:00
res = append ( res , [ ] * source . FoldingRangeInfo { } )
2019-08-29 13:28:51 -06:00
}
res [ setNum ] = append ( res [ setNum ] , fRng )
}
return res
}
2019-09-05 18:14:09 -06:00
func conflict ( t * testing . T , a , b * source . FoldingRangeInfo ) bool {
arng , err := a . Range ( )
if err != nil {
t . Fatal ( err )
}
brng , err := b . Range ( )
if err != nil {
t . Fatal ( err )
}
2019-08-29 13:28:51 -06:00
// a start position is <= b start positions
2019-09-05 18:14:09 -06:00
return protocol . ComparePosition ( arng . Start , brng . Start ) <= 0 && protocol . ComparePosition ( arng . End , brng . Start ) > 0
2019-08-29 13:28:51 -06:00
}
2019-08-30 12:11:42 -06:00
func foldRanges ( contents string , ranges [ ] * source . FoldingRangeInfo ) ( string , error ) {
2019-08-28 19:48:29 -06:00
foldedText := "<>"
res := contents
// Apply the folds from the end of the file forward
// to preserve the offsets.
for i := len ( ranges ) - 1 ; i >= 0 ; i -- {
fRange := ranges [ i ]
2019-09-05 18:14:09 -06:00
spn , err := fRange . Span ( )
2019-08-28 19:48:29 -06:00
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 ) {
2019-12-17 16:57:54 -07:00
gofmted := string ( r . data . Golden ( "gofmt" , spn . URI ( ) . Filename ( ) , func ( ) ( [ ] byte , error ) {
cmd := exec . Command ( "gofmt" , spn . URI ( ) . Filename ( ) )
2019-09-26 11:56:23 -06:00
out , _ := cmd . Output ( ) // ignore error, sometimes we have intentionally ungofmt-able files
return out , nil
} ) )
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , spn . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2020-07-02 16:34:10 -06:00
edits , err := source . Format ( r . ctx , r . snapshot , fh )
2019-09-26 11:56:23 -06:00
if err != nil {
if gofmted != "" {
2019-09-05 14:58:50 -06:00
t . Error ( err )
}
2019-09-26 11:56:23 -06:00
return
}
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
data , err := fh . Read ( )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
2019-12-17 16:57:54 -07:00
m , err := r . data . Mapper ( spn . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
diffEdits , err := source . FromProtocolEdits ( m , edits )
if err != nil {
t . Error ( err )
}
got := diff . ApplyEdits ( string ( data ) , diffEdits )
if gofmted != got {
2019-12-17 16:57:54 -07:00
t . Errorf ( "format failed for %s, expected:\n%v\ngot:\n%v" , spn . URI ( ) . Filename ( ) , gofmted , got )
2019-05-08 14:04:29 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) Import ( t * testing . T , spn span . Span ) {
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , spn . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2020-07-02 16:34:10 -06:00
edits , _ , err := source . AllImportsFixes ( r . ctx , r . snapshot , fh )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-11-05 15:33:19 -07:00
t . Error ( err )
2019-09-26 11:56:23 -06:00
}
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
data , err := fh . Read ( )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
m , err := r . data . Mapper ( fh . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
diffEdits , err := source . FromProtocolEdits ( m , edits )
if err != nil {
t . Error ( err )
}
got := diff . ApplyEdits ( string ( data ) , diffEdits )
2019-12-17 16:57:54 -07:00
want := string ( r . data . Golden ( "goimports" , spn . URI ( ) . Filename ( ) , func ( ) ( [ ] byte , error ) {
2019-11-05 15:33:19 -07:00
return [ ] byte ( got ) , nil
} ) )
if want != got {
2020-04-14 21:45:24 -06:00
d := myers . ComputeEdits ( spn . URI ( ) , want , got )
t . Errorf ( "import failed for %s: %s" , spn . URI ( ) . Filename ( ) , diff . ToUnified ( "want" , "got" , want , d ) )
2019-05-31 20:42:59 -06:00
}
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) Definition ( t * testing . T , spn span . Span , d tests . Definition ) {
2019-12-17 16:57:54 -07:00
_ , srcRng , err := spanToRange ( r . data , d . Src )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , spn . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
2020-07-02 16:34:10 -06:00
ident , err := source . Identifier ( r . ctx , r . snapshot , fh , srcRng . Start )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatalf ( "failed for %v: %v" , d . Src , err )
}
2020-06-15 20:21:06 -06:00
h , err := source . HoverIdentifier ( r . ctx , ident )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatalf ( "failed for %v: %v" , d . Src , err )
}
2020-06-15 20:21:06 -06:00
hover , err := source . FormatHover ( h , r . view . Options ( ) )
2019-12-06 00:44:16 -07:00
if err != nil {
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2019-12-08 21:19:55 -07:00
rng , err := ident . Declaration . MappedRange [ 0 ] . Range ( )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
if d . IsType {
rng , err = ident . Type . Range ( )
2019-07-11 19:05:55 -06:00
if err != nil {
2019-08-26 22:26:45 -06:00
t . Fatal ( err )
2019-07-11 19:05:55 -06:00
}
2019-09-26 11:56:23 -06:00
hover = ""
}
2019-11-19 12:51:46 -07:00
didSomething := false
2019-09-26 11:56:23 -06:00
if hover != "" {
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 ) , nil
} ) )
if hover != expectHover {
2020-07-22 23:36:12 -06:00
t . Errorf ( "hover for %s failed:\n%s" , d . Src , tests . Diff ( expectHover , hover ) )
2019-08-12 14:59:23 -06:00
}
2019-11-19 12:51:46 -07:00
}
if ! d . OnlyHover {
didSomething = true
2019-09-26 11:56:23 -06:00
if _ , defRng , err := spanToRange ( r . data , d . Def ) ; err != nil {
2019-08-26 22:26:45 -06:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
} else if rng != defRng {
2019-11-19 12:51:46 -07:00
t . Errorf ( "for %v got %v want %v" , d . Src , rng , defRng )
2019-08-26 22:26:45 -06: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-05-08 14:04:29 -06:00
}
}
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
}
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , spn . URI ( ) )
2019-12-17 16:57:54 -07:00
if err != nil {
t . Fatal ( err )
}
2020-07-02 16:34:10 -06:00
locs , err := source . Implementation ( r . ctx , r . snapshot , fh , loc . Range . Start )
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
}
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-07-10 19:11:23 -06:00
ctx := r . ctx
2019-09-26 11:56:23 -06:00
m , srcRng , err := spanToRange ( r . data , src )
if err != nil {
t . Fatal ( err )
}
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , src . URI ( ) )
2019-11-20 14:38:43 -07:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-11-20 14:38:43 -07:00
}
2020-07-02 16:34:10 -06:00
highlights , err := source . Highlight ( ctx , r . snapshot , fh , srcRng . Start )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Errorf ( "highlight failed for %s: %v" , src . URI ( ) , err )
}
if len ( highlights ) != len ( locations ) {
2020-06-03 09:27:22 -06: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
for i := range highlights {
h , err := m . RangeSpan ( highlights [ i ] )
2019-07-11 19:05:55 -06:00
if err != nil {
2019-11-21 17:26:14 -07:00
t . Fatalf ( "failed for %v: %v" , highlights [ i ] , err )
2019-07-11 19:05:55 -06:00
}
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-05-08 14:04:29 -06:00
}
}
}
2019-10-19 15:26:56 -06:00
func ( r * runner ) References ( t * testing . T , src span . Span , itemList [ ] span . Span ) {
2019-07-10 19:11:23 -06:00
ctx := r . ctx
2019-12-17 16:57:54 -07:00
_ , srcRng , err := spanToRange ( r . data , src )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2020-07-02 16:34:10 -06:00
snapshot := r . snapshot
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
fh , err := snapshot . GetFile ( r . ctx , src . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( 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 [ span . Span ] 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
}
want [ pos ] = true
}
refs , err := source . References ( ctx , snapshot , fh , srcRng . Start , includeDeclaration )
if err != nil {
t . Fatalf ( "failed for %s: %v" , src , err )
}
got := make ( map [ span . Span ] bool )
for _ , refInfo := range refs {
refSpan , err := refInfo . Span ( )
if err != nil {
t . Fatal ( err )
}
got [ refSpan ] = true
}
if len ( got ) != len ( want ) {
t . Errorf ( "references failed: different lengths got %v want %v" , len ( got ) , len ( want ) )
}
for spn := range got {
if ! want [ spn ] {
t . Errorf ( "references failed: incorrect references got %v want locations %v" , got , want )
}
}
} )
2019-06-07 08:04:22 -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-07-02 16:35:35 -06:00
2019-12-17 16:57:54 -07:00
_ , srcRng , err := spanToRange ( r . data , spn )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , spn . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
2020-07-02 16:34:10 -06:00
changes , err := source . Rename ( r . ctx , r . snapshot , fh , srcRng . Start , newText )
2019-09-26 11:56:23 -06:00
if err != nil {
renamed := string ( r . data . Golden ( tag , spn . URI ( ) . 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
}
var res [ ] string
2019-12-17 16:57:54 -07:00
for editURI , edits := range changes {
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , editURI )
2019-06-18 08:23:37 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-06-18 08:23:37 -06:00
}
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
data , err := fh . Read ( )
2019-07-11 19:05:55 -06:00
if err != nil {
2019-08-26 22:26:45 -06:00
t . Fatal ( err )
2019-07-11 19:05:55 -06:00
}
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
m , err := r . data . Mapper ( fh . URI ( ) )
2019-06-21 15:00:02 -06:00
if err != nil {
2019-09-26 11:56:23 -06:00
t . Fatal ( err )
2019-06-21 15:00:02 -06:00
}
2019-09-26 11:56:23 -06:00
diffEdits , err := source . FromProtocolEdits ( m , edits )
2019-06-18 08:23:37 -06:00
if err != nil {
2019-09-26 11:56:23 -06:00
t . Fatal ( err )
2019-06-18 08:23:37 -06:00
}
2019-09-26 11:56:23 -06:00
contents := applyEdits ( string ( data ) , diffEdits )
if len ( changes ) > 1 {
2019-12-17 16:57:54 -07:00
filename := filepath . Base ( editURI . Filename ( ) )
2019-09-26 11:56:23 -06:00
contents = fmt . Sprintf ( "%s:\n%s" , filename , contents )
2019-06-18 08:23:37 -06:00
}
2019-09-26 11:56:23 -06:00
res = append ( res , contents )
}
2019-07-08 19:53:01 -06:00
2019-09-26 11:56:23 -06:00
// Sort on filename
sort . Strings ( res )
2019-07-08 19:53:01 -06:00
2019-09-26 11:56:23 -06:00
var got string
for i , val := range res {
if i != 0 {
got += "\n"
2019-06-18 08:23:37 -06:00
}
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 , spn . URI ( ) . 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-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-09-26 11:56:23 -06:00
func ( r * runner ) PrepareRename ( t * testing . T , src span . Span , want * source . PrepareItem ) {
_ , srcRng , err := spanToRange ( r . data , src )
if err != nil {
t . Fatal ( err )
}
// Find the identifier at the position.
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , src . URI ( ) )
2019-12-17 16:57:54 -07:00
if err != nil {
t . Fatal ( err )
}
2020-07-02 16:34:10 -06:00
item , err := source . PrepareRename ( r . ctx , r . snapshot , fh , srcRng . Start )
2019-09-26 11:56:23 -06:00
if err != nil {
if want . Text != "" { // expected an ident.
t . Errorf ( "prepare rename failed for %v: got error: %v" , src , err )
2019-08-22 11:31:03 -06:00
}
2019-09-26 11:56:23 -06:00
return
}
if item == nil {
if want . Text != "" {
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-01-30 17:24:33 -07:00
if want . Text == "" {
2019-09-26 11:56:23 -06:00
t . Errorf ( "prepare rename failed for %v: expected nil, got %v" , src , item )
return
}
2019-11-19 12:51:46 -07:00
if item . Range . Start == item . Range . End {
// Special case for 0-length ranges. Marks can't specify a 0-length range,
// so just compare the start.
if item . Range . Start != want . Range . Start {
t . Errorf ( "prepare rename failed: incorrect point, got %v want %v" , item . Range . Start , want . Range . Start )
}
} else {
if protocol . CompareRange ( item . Range , want . Range ) != 0 {
t . Errorf ( "prepare rename failed: incorrect range got %v want %v" , item . Range , want . Range )
}
2019-08-22 11:31:03 -06:00
}
}
2019-10-27 11:41:48 -06:00
func ( r * runner ) Symbols ( t * testing . T , uri span . URI , expectedSymbols [ ] protocol . DocumentSymbol ) {
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , uri )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2020-07-02 16:34:10 -06:00
symbols , err := source . DocumentSymbols ( r . ctx , r . snapshot , fh )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Errorf ( "symbols failed for %s: %v" , uri , err )
}
if len ( symbols ) != len ( expectedSymbols ) {
t . Errorf ( "want %d top-level symbols in %v, got %d" , len ( expectedSymbols ) , uri , len ( symbols ) )
return
}
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 )
2019-05-08 14:04:29 -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-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-04-19 10:07:45 -06: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-04-19 10:07:45 -06:00
t . Helper ( )
2020-06-07 19:50:35 -06:00
got , err := source . WorkspaceSymbols ( r . ctx , matcher , source . PackageQualifiedSymbols , [ ] source . View { r . view } , query )
2020-04-19 10:07:45 -06:00
if err != nil {
t . Fatal ( err )
}
2020-02-09 05:36:49 -07:00
got = tests . FilterWorkspaceSymbols ( got , dirs )
2020-01-05 03:46:20 -07:00
if diff := tests . DiffWorkspaceSymbols ( expectedSymbols , got ) ; diff != "" {
t . Error ( diff )
}
}
2020-02-04 20:44:33 -07:00
func ( r * runner ) SignatureHelp ( t * testing . T , spn span . Span , want * protocol . SignatureHelp ) {
2019-12-17 16:57:54 -07:00
_ , rng , err := spanToRange ( r . data , spn )
2019-09-26 11:56:23 -06:00
if err != nil {
2019-12-17 16:57:54 -07:00
t . Fatal ( err )
2019-09-26 11:56:23 -06:00
}
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , spn . URI ( ) )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
2020-07-02 16:34:10 -06:00
gotSignature , gotActiveParameter , err := source . SignatureHelp ( r . ctx , r . snapshot , fh , rng . Start )
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-08 14:04:29 -06:00
t . Fatalf ( "failed for %v: %v" , spn , err )
}
2020-02-04 20:44:33 -07:00
return
2019-09-26 11:56:23 -06:00
}
2020-02-04 20:44:33 -07:00
if gotSignature == nil {
if want != nil {
t . Fatalf ( "got nil signature, but expected %v" , want )
2019-05-08 14:04:29 -06:00
}
2019-09-26 11:56:23 -06:00
return
}
2020-02-04 20:44:33 -07:00
got := & protocol . SignatureHelp {
Signatures : [ ] protocol . SignatureInformation { * gotSignature } ,
ActiveParameter : float64 ( gotActiveParameter ) ,
2019-05-08 14:04:29 -06:00
}
2020-02-25 21:28:00 -07:00
if diff := tests . DiffSignatures ( spn , want , got ) ; diff != "" {
2020-02-04 20:44:33 -07:00
t . Error ( diff )
2019-05-08 14:04:29 -06:00
}
}
2020-07-13 17:36:26 -06:00
// These are pure LSP features, no source level functionality to be tested.
func ( r * runner ) Link ( t * testing . T , uri span . URI , wantLinks [ ] tests . Link ) { }
func ( r * runner ) SuggestedFix ( t * testing . T , spn span . Span , actionKinds [ ] string ) { }
func ( r * runner ) FunctionExtraction ( t * testing . T , start span . Span , end span . Span ) { }
2019-08-26 22:26:45 -06:00
2020-02-26 12:11:30 -07:00
func ( r * runner ) CodeLens ( t * testing . T , uri span . URI , want [ ] protocol . CodeLens ) {
2020-07-02 16:34:10 -06:00
fh , err := r . snapshot . GetFile ( r . ctx , uri )
2020-03-10 22:49:10 -06:00
if err != nil {
t . Fatal ( err )
}
2020-07-02 16:34:10 -06:00
got , err := source . CodeLens ( r . ctx , r . snapshot , fh )
2020-03-10 22:49:10 -06:00
if err != nil {
t . Fatal ( err )
}
if diff := tests . DiffCodeLens ( uri , want , got ) ; diff != "" {
t . Error ( diff )
}
2020-02-06 12:50:26 -07:00
}
2019-09-09 22:36:39 -06:00
func spanToRange ( data * tests . Data , spn span . Span ) ( * protocol . ColumnMapper , protocol . Range , error ) {
m , err := data . Mapper ( spn . URI ( ) )
2019-08-26 22:26:45 -06:00
if err != nil {
2019-09-05 16:54:05 -06:00
return nil , protocol . Range { } , err
2019-08-26 22:26:45 -06:00
}
2019-09-09 22:36:39 -06:00
srcRng , err := m . Range ( spn )
2019-08-26 22:26:45 -06:00
if err != nil {
2019-09-05 16:54:05 -06:00
return nil , protocol . Range { } , err
2019-08-26 22:26:45 -06:00
}
2019-09-05 16:54:05 -06:00
return m , srcRng , nil
2019-08-26 22:26:45 -06:00
}