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 (
2018-11-14 18:42:30 -07:00
"bytes"
2018-11-07 13:21:31 -07:00
"context"
2018-11-16 16:15:25 -07:00
"fmt"
2018-11-07 13:21:31 -07:00
"go/token"
2019-12-17 14:13:33 -07:00
"io/ioutil"
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-12-17 14:13:33 -07:00
"runtime"
2019-03-27 18:00:20 -06:00
"sort"
2018-11-07 13:21:31 -07:00
"strings"
"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"
2019-12-17 14:13:33 -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
diagnostics map [ span . URI ] [ ] source . Diagnostic
ctx context . Context
2019-04-16 13:47:48 -06:00
}
2019-05-15 10:24:49 -06:00
const viewName = "lsp_test"
2019-04-16 14:19:03 -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" )
defer data . Exported . Cleanup ( )
2018-11-07 13:21:31 -07:00
2019-10-10 18:48:16 -06:00
cache := cache . New ( nil )
2020-01-21 14:01:59 -07:00
session := cache . NewSession ( )
2019-09-17 09:10:48 -06:00
options := tests . DefaultOptions ( )
2019-09-05 22:17:36 -06:00
session . SetOptions ( options )
2019-09-11 11:13:44 -06:00
options . Env = data . Config . Env
2020-01-09 23:45:57 -07:00
if _ , _ , err := session . NewView ( ctx , viewName , span . FileURI ( data . Config . Dir ) , options ) ; err != nil {
2019-11-01 14:45:32 -06:00
t . Fatal ( err )
}
2020-01-22 19:58:50 -07:00
var modifications [ ] source . FileModification
2019-09-09 11:04:12 -06:00
for filename , content := range data . Config . Overlay {
2019-12-10 10:29:37 -07:00
kind := source . DetectLanguage ( "" , filename )
if kind != source . Go {
continue
}
2020-01-22 19:58:50 -07:00
modifications = append ( modifications , source . FileModification {
URI : span . FileURI ( filename ) ,
Action : source . Open ,
Version : - 1 ,
Text : content ,
LanguageID : "go" ,
} )
}
if _ , err := session . DidModifyFiles ( ctx , modifications ) ; err != nil {
t . Fatal ( err )
2019-09-09 11:04:12 -06:00
}
2019-04-16 13:47:48 -06:00
r := & runner {
server : & Server {
2019-11-20 23:24:43 -07:00
session : session ,
delivered : map [ span . URI ] sentDiagnostics { } ,
2018-11-14 17:25:49 -07:00
} ,
2019-04-16 13:47:48 -06:00
data : data ,
2019-07-10 19:11:23 -06:00
ctx : ctx ,
2019-04-16 13:47:48 -06:00
}
tests . Run ( t , r , data )
2019-03-30 14:18:22 -06:00
}
2018-11-14 22:05:30 -07:00
2019-09-26 11:56:23 -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 {
r . diagnostics = make ( map [ span . URI ] [ ] source . Diagnostic )
v := r . server . session . View ( viewName )
// Always run diagnostics with analysis.
reports := r . server . diagnose ( r . ctx , v . Snapshot ( ) , true )
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 {
URI : protocol . NewURI ( uri ) ,
} ,
} )
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 {
URI : protocol . NewURI ( uri ) ,
} ,
} )
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 {
URI : protocol . NewURI ( uri ) ,
} ,
} )
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 {
URI : protocol . NewURI ( uri ) ,
} ,
} )
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 {
t . Errorf ( "import failed for %s, expected:\n%v\ngot:\n%v" , filename , want , got )
2019-09-26 11:56:23 -06:00
}
2019-05-31 20:42:59 -06:00
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) SuggestedFix ( t * testing . T , spn span . Span ) {
uri := spn . URI ( )
filename := uri . Filename ( )
2019-11-15 10:43:45 -07:00
view , err := r . server . session . ViewOf ( uri )
if err != nil {
t . Fatal ( err )
}
2019-12-17 16:57:54 -07:00
snapshot := view . Snapshot ( )
2020-01-11 21:59:57 -07:00
_ , diagnostics , err := source . FileDiagnostics ( r . ctx , snapshot , uri )
2019-09-26 11:56:23 -06:00
if err != nil {
t . Fatal ( err )
}
actions , err := r . server . CodeAction ( r . ctx , & protocol . CodeActionParams {
TextDocument : protocol . TextDocumentIdentifier {
URI : protocol . NewURI ( uri ) ,
} ,
Context : protocol . CodeActionContext {
Only : [ ] protocol . CodeActionKind { protocol . QuickFix } ,
2020-01-11 21:59:57 -07:00
Diagnostics : toProtocolDiagnostics ( diagnostics ) ,
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
if len ( actions ) > 1 {
2019-11-12 15:58:37 -07:00
t . Fatal ( "expected only 1 code action" )
2019-09-04 11:16:09 -06:00
}
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
}
2019-11-12 15:58:37 -07:00
got := res [ uri ]
2019-09-26 11:56:23 -06:00
fixed := string ( r . data . Golden ( "suggestedfix" , filename , func ( ) ( [ ] byte , error ) {
return [ ] byte ( got ) , nil
} ) )
if fixed != got {
t . Errorf ( "suggested fixes failed for %s, expected:\n%v\ngot:\n%v" , filename , fixed , got )
}
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 {
t . Errorf ( "for %v got %q want %q" , d . Src , hover . Contents . Value , expectHover )
2018-12-03 15:14:30 -07:00
}
2019-11-19 12:51:46 -07:00
}
if ! d . OnlyHover {
didSomething = true
2019-09-26 11:56:23 -06:00
locURI := span . NewURI ( locs [ 0 ] . URI )
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 {
locURI := span . NewURI ( locs [ i ] . URI )
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 {
URI : protocol . NewURI ( uri ) ,
} ,
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-01-30 14:39:33 -07:00
uri := span . NewURI ( 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
if got == nil || got . ( * protocol . Range ) == 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
}
2019-11-17 12:29:15 -07:00
xx , ok := got . ( * protocol . Range )
if ! ok {
t . Fatalf ( "got %T, wanted Range" , got )
}
2019-11-19 12:51:46 -07:00
if xx . Start == xx . End {
// Special case for 0-length ranges. Marks can't specify a 0-length range,
// so just compare the start.
if xx . Start != want . Range . Start {
t . Errorf ( "prepare rename failed: incorrect point, got %v want %v" , xx . Start , want . Range . Start )
}
} else {
if protocol . CompareRange ( * xx , want . Range ) != 0 {
t . Errorf ( "prepare rename failed: incorrect range got %v want %v" , * xx , want . Range )
}
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-01-30 14:39:33 -07:00
uri := span . NewURI ( docEdits . TextDocument . URI )
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 {
URI : string ( uri ) ,
} ,
}
symbols , err := r . server . DocumentSymbol ( r . ctx , 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 ) )
return
}
if diff := r . diffSymbols ( t , uri , expectedSymbols , symbols ) ; diff != "" {
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
}
}
2019-09-05 16:54:05 -06:00
func ( r * runner ) diffSymbols ( t * testing . T , uri span . URI , want [ ] protocol . DocumentSymbol , got [ ] protocol . DocumentSymbol ) string {
2019-04-16 14:47:01 -06:00
sort . Slice ( want , func ( i , j int ) bool { return want [ i ] . Name < want [ j ] . Name } )
sort . Slice ( got , func ( i , j int ) bool { return got [ i ] . Name < got [ j ] . Name } )
2019-03-30 14:18:22 -06:00
if len ( got ) != len ( want ) {
2019-09-05 16:54:05 -06:00
return summarizeSymbols ( t , - 1 , want , got , "different lengths got %v want %v" , len ( got ) , len ( want ) )
2019-03-30 14:18:22 -06:00
}
for i , w := range want {
g := got [ i ]
if w . Name != g . Name {
2019-09-05 16:54:05 -06:00
return summarizeSymbols ( t , i , want , got , "incorrect name got %v want %v" , g . Name , w . Name )
2019-03-30 14:18:22 -06:00
}
2019-09-05 16:54:05 -06:00
if w . Kind != g . Kind {
return summarizeSymbols ( t , i , want , got , "incorrect kind got %v want %v" , g . Kind , w . Kind )
2019-04-16 13:47:48 -06:00
}
2019-09-05 16:54:05 -06:00
if protocol . CompareRange ( g . SelectionRange , w . SelectionRange ) != 0 {
return summarizeSymbols ( t , i , want , got , "incorrect span got %v want %v" , g . SelectionRange , w . SelectionRange )
2019-03-30 14:18:22 -06:00
}
2019-05-15 15:58:16 -06:00
if msg := r . diffSymbols ( t , uri , w . Children , g . Children ) ; msg != "" {
2019-03-30 14:18:22 -06:00
return fmt . Sprintf ( "children of %s: %s" , w . Name , msg )
}
}
return ""
2019-04-16 14:47:01 -06:00
}
2019-03-30 14:18:22 -06:00
2019-09-17 09:10:48 -06:00
func summarizeSymbols ( t * testing . T , i int , want , got [ ] protocol . DocumentSymbol , reason string , args ... interface { } ) string {
2019-03-30 14:18:22 -06:00
msg := & bytes . Buffer { }
2019-04-16 14:47:01 -06:00
fmt . Fprint ( msg , "document symbols failed" )
if i >= 0 {
fmt . Fprintf ( msg , " at %d" , i )
}
fmt . Fprint ( msg , " because of " )
fmt . Fprintf ( msg , reason , args ... )
fmt . Fprint ( msg , ":\nexpected:\n" )
2019-03-30 14:18:22 -06:00
for _ , s := range want {
2019-09-05 16:54:05 -06:00
fmt . Fprintf ( msg , " %v %v %v\n" , s . Name , s . Kind , s . SelectionRange )
2019-03-30 14:18:22 -06:00
}
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 ( )
}
2019-09-26 11:56:23 -06:00
func ( r * runner ) SignatureHelp ( t * testing . T , spn span . Span , expectedSignature * source . SignatureInformation ) {
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 {
URI : protocol . NewURI ( spn . URI ( ) ) ,
} ,
Position : loc . Range . Start ,
}
params := & protocol . SignatureHelpParams {
TextDocumentPositionParams : tdpp ,
}
gotSignatures , err := r . server . SignatureHelp ( r . ctx , params )
if err != nil {
// Only fail if we got an error we did not expect.
if expectedSignature != nil {
2019-05-15 15:58:16 -06:00
t . Fatal ( err )
}
2019-09-26 11:56:23 -06:00
return
}
if expectedSignature == nil {
if gotSignatures != nil {
t . Errorf ( "expected no signature, got %v" , gotSignatures )
2019-04-16 13:47:48 -06:00
}
2019-09-26 11:56:23 -06:00
return
}
if gotSignatures == nil {
t . Fatalf ( "expected %v, got nil" , expectedSignature )
}
if diff := diffSignatures ( spn , expectedSignature , gotSignatures ) ; diff != "" {
t . Error ( diff )
2019-04-16 13:47:48 -06:00
}
}
2019-06-28 19:27:41 -06:00
func diffSignatures ( spn span . Span , want * source . SignatureInformation , got * protocol . SignatureHelp ) string {
2019-04-16 13:47:48 -06:00
decorate := func ( f string , args ... interface { } ) string {
return fmt . Sprintf ( "Invalid signature at %s: %s" , spn , fmt . Sprintf ( f , args ... ) )
}
if len ( got . Signatures ) != 1 {
return decorate ( "wanted 1 signature, got %d" , len ( got . Signatures ) )
}
if got . ActiveSignature != 0 {
2020-01-25 16:22:03 -07:00
return decorate ( "wanted active signature of 0, got %d" , got . ActiveSignature )
2019-04-16 13:47:48 -06:00
}
if want . ActiveParameter != int ( got . ActiveParameter ) {
2020-01-25 16:22:03 -07:00
return decorate ( "wanted active parameter of %d, got %d" , want . ActiveParameter , got . ActiveParameter )
2019-04-16 13:47:48 -06:00
}
gotSig := got . Signatures [ int ( got . ActiveSignature ) ]
if want . Label != gotSig . Label {
return decorate ( "wanted label %q, got %q" , want . Label , gotSig . Label )
}
var paramParts [ ] string
for _ , p := range gotSig . Parameters {
paramParts = append ( paramParts , p . Label )
}
paramsStr := strings . Join ( paramParts , ", " )
if ! strings . Contains ( gotSig . Label , paramsStr ) {
return decorate ( "expected signature %q to contain params %q" , gotSig . Label , paramsStr )
}
return ""
}
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 {
URI : protocol . NewURI ( uri ) ,
} ,
} )
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 ) )
2019-09-09 22:36:39 -06:00
uri := span . FileURI ( fname )
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
}
}
}
2019-12-17 14:13:33 -07:00
// TODO(golang/go#36091): This function can be refactored to look like the rest of this file
// when marker support gets added for go.mod files.
func TestModfileSuggestedFixes ( t * testing . T ) {
if runtime . GOOS == "android" {
2020-01-16 12:32:09 -07:00
t . Skip ( "this test cannot find mod/testdata files" )
2019-12-17 14:13:33 -07:00
}
ctx := tests . Context ( t )
cache := cache . New ( nil )
2020-01-21 14:01:59 -07:00
session := cache . NewSession ( )
2019-12-17 14:13:33 -07:00
options := tests . DefaultOptions ( )
options . TempModfile = true
options . Env = append ( os . Environ ( ) , "GOPACKAGESDRIVER=off" , "GOROOT=" )
server := Server {
session : session ,
delivered : map [ span . URI ] sentDiagnostics { } ,
}
for _ , tt := range [ ] string { "indirect" , "unused" } {
t . Run ( tt , func ( t * testing . T ) {
folder , err := tests . CopyFolderToTempDir ( filepath . Join ( "mod" , "testdata" , tt ) )
if err != nil {
t . Fatal ( err )
}
defer os . RemoveAll ( folder )
_ , snapshot , err := session . NewView ( ctx , "suggested_fix_test" , span . FileURI ( folder ) , options )
if err != nil {
t . Fatal ( err )
}
2020-01-16 12:32:09 -07:00
2020-01-23 14:54:21 -07:00
realURI , tempURI := snapshot . View ( ) . ModFiles ( )
2019-12-17 14:13:33 -07:00
// TODO: Add testing for when the -modfile flag is turned off and we still get diagnostics.
2020-01-16 12:32:09 -07:00
if tempURI == "" {
2019-12-17 14:13:33 -07:00
return
}
2020-01-16 12:32:09 -07:00
realfh , err := snapshot . GetFile ( realURI )
if err != nil {
t . Fatal ( err )
}
2020-01-24 08:14:25 -07:00
reports , _ , err := mod . Diagnostics ( ctx , snapshot )
2019-12-17 14:13:33 -07:00
if err != nil {
t . Fatal ( err )
}
if len ( reports ) != 1 {
t . Errorf ( "expected 1 fileHandle, got %d" , len ( reports ) )
}
2020-01-16 12:32:09 -07:00
2020-01-14 14:53:48 -07:00
_ , m , _ , _ , err := snapshot . ModTidyHandle ( ctx , realfh ) . Tidy ( ctx )
2020-01-16 12:32:09 -07:00
if err != nil {
t . Fatal ( err )
}
2019-12-17 14:13:33 -07:00
for fh , diags := range reports {
actions , err := server . CodeAction ( ctx , & protocol . CodeActionParams {
TextDocument : protocol . TextDocumentIdentifier {
URI : protocol . NewURI ( fh . URI ) ,
} ,
Context : protocol . CodeActionContext {
Only : [ ] protocol . CodeActionKind { protocol . SourceOrganizeImports } ,
2020-01-10 10:29:37 -07:00
Diagnostics : toProtocolDiagnostics ( diags ) ,
2019-12-17 14:13:33 -07:00
} ,
} )
if err != nil {
t . Fatal ( err )
}
if len ( actions ) == 0 {
t . Fatal ( "no code actions returned" )
}
if len ( actions ) > 1 {
t . Fatal ( "expected only 1 code action" )
}
res := map [ span . URI ] string { }
for _ , docEdits := range actions [ 0 ] . Edit . DocumentChanges {
2020-01-30 14:39:33 -07:00
uri := span . NewURI ( docEdits . TextDocument . URI )
2019-12-17 14:13:33 -07:00
content , err := ioutil . ReadFile ( uri . Filename ( ) )
if err != nil {
t . Fatal ( err )
}
res [ uri ] = string ( content )
2020-01-16 12:32:09 -07:00
sedits , err := source . FromProtocolEdits ( m , docEdits . Edits )
if err != nil {
t . Fatal ( err )
2019-12-17 14:13:33 -07:00
}
2020-01-16 12:32:09 -07:00
res [ uri ] = applyEdits ( res [ uri ] , sedits )
2019-12-17 14:13:33 -07:00
}
2020-01-14 14:53:48 -07:00
got := res [ realfh . Identity ( ) . URI ]
2020-01-16 12:32:09 -07:00
contents , err := ioutil . ReadFile ( filepath . Join ( folder , "go.mod.golden" ) )
2019-12-17 14:13:33 -07:00
if err != nil {
t . Fatal ( err )
}
want := string ( contents )
if want != got {
t . Errorf ( "suggested fixes failed for %s, expected:\n%s\ngot:\n%s" , fh . URI . Filename ( ) , want , got )
}
}
} )
}
}