internal/lsp: support definitions and hover for builtins
This change adds support for definitions and hover for builtin types and functions. It also includes some small (non-logic) changes to the import spec definition function. Additionally, there are some resulting changes in diagnostics to ignore the builtin file but also use it for definitions (Ian, you were right with your comment on my earlier review...). Fixes golang/go#31696 Change-Id: I52d43d010a5ca8359b539c33e40782877eb730d0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/177517 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
1a8f2608bd
commit
bffc5affc6
|
@ -174,12 +174,12 @@ func (f *goFile) GetActiveReverseDeps(ctx context.Context) []source.GoFile {
|
||||||
results := make(map[*goFile]struct{})
|
results := make(map[*goFile]struct{})
|
||||||
f.view.reverseDeps(ctx, seen, results, pkg.PkgPath())
|
f.view.reverseDeps(ctx, seen, results, pkg.PkgPath())
|
||||||
|
|
||||||
files := make([]source.GoFile, 0, len(results))
|
var files []source.GoFile
|
||||||
for rd := range results {
|
for rd := range results {
|
||||||
if rd == nil {
|
if rd == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Don't return any of the active file's in this package.
|
// Don't return any of the active files in this package.
|
||||||
if rd.pkg != nil && rd.pkg == pkg {
|
if rd.pkg != nil && rd.pkg == pkg {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,10 @@ func (s *session) NewView(name string, folder span.URI, config *packages.Config)
|
||||||
pcache: &packageCache{
|
pcache: &packageCache{
|
||||||
packages: make(map[string]*entry),
|
packages: make(map[string]*entry),
|
||||||
},
|
},
|
||||||
|
ignoredURIs: make(map[span.URI]struct{}),
|
||||||
|
}
|
||||||
|
for filename := range v.builtinPkg.Files {
|
||||||
|
v.ignoredURIs[span.NewURI(filename)] = struct{}{}
|
||||||
}
|
}
|
||||||
s.views = append(s.views, v)
|
s.views = append(s.views, v)
|
||||||
// we always need to drop the view map
|
// we always need to drop the view map
|
||||||
|
|
|
@ -6,7 +6,6 @@ package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
@ -67,6 +66,9 @@ type view struct {
|
||||||
|
|
||||||
// builtinPkg is the AST package used to resolve builtin types.
|
// builtinPkg is the AST package used to resolve builtin types.
|
||||||
builtinPkg *ast.Package
|
builtinPkg *ast.Package
|
||||||
|
|
||||||
|
// ignoredURIs is the set of URIs of files that we ignore.
|
||||||
|
ignoredURIs map[span.URI]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type metadataCache struct {
|
type metadataCache struct {
|
||||||
|
@ -290,18 +292,15 @@ func (v *view) GetFile(ctx context.Context, uri span.URI) (source.File, error) {
|
||||||
|
|
||||||
// getFile is the unlocked internal implementation of GetFile.
|
// getFile is the unlocked internal implementation of GetFile.
|
||||||
func (v *view) getFile(uri span.URI) (viewFile, error) {
|
func (v *view) getFile(uri span.URI) (viewFile, error) {
|
||||||
filename, err := uri.Filename()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if v.isIgnored(filename) {
|
|
||||||
return nil, fmt.Errorf("%s is ignored", filename)
|
|
||||||
}
|
|
||||||
if f, err := v.findFile(uri); err != nil {
|
if f, err := v.findFile(uri); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if f != nil {
|
} else if f != nil {
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
filename, err := uri.Filename()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
f := &goFile{
|
f := &goFile{
|
||||||
fileBase: fileBase{
|
fileBase: fileBase{
|
||||||
view: v,
|
view: v,
|
||||||
|
@ -312,18 +311,11 @@ func (v *view) getFile(uri span.URI) (viewFile, error) {
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isIgnored checks if the given filename is a file we ignore.
|
// Ignore checks if the given URI is a URI we ignore.
|
||||||
// As of right now, we only ignore files in the "builtin" package.
|
// As of right now, we only ignore files in the "builtin" package.
|
||||||
func (v *view) isIgnored(filename string) bool {
|
func (v *view) Ignore(uri span.URI) bool {
|
||||||
bpkg := v.BuiltinPackage()
|
_, ok := v.ignoredURIs[uri]
|
||||||
if bpkg != nil {
|
return ok
|
||||||
for builtinFilename := range bpkg.Files {
|
|
||||||
if filename == builtinFilename {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findFile checks the cache for any file matching the given uri.
|
// findFile checks the cache for any file matching the given uri.
|
||||||
|
|
|
@ -64,8 +64,8 @@ func TestDefinitionHelpExample(t *testing.T) {
|
||||||
|
|
||||||
func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
||||||
for _, d := range data {
|
for _, d := range data {
|
||||||
if d.IsType {
|
if d.IsType || d.OnlyHover {
|
||||||
// TODO: support type definition queries
|
// TODO: support type definition, hover queries
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d.Src = span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{})
|
d.Src = span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{})
|
||||||
|
|
|
@ -312,7 +312,10 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
|
||||||
|
|
||||||
func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
||||||
for _, d := range data {
|
for _, d := range data {
|
||||||
sm := r.mapper(d.Src.URI())
|
sm, err := r.mapper(d.Src.URI())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
loc, err := sm.Location(d.Src)
|
loc, err := sm.Location(d.Src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed for %v: %v", d.Src, err)
|
t.Fatalf("failed for %v: %v", d.Src, err)
|
||||||
|
@ -338,13 +341,6 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
||||||
if len(locs) != 1 {
|
if len(locs) != 1 {
|
||||||
t.Errorf("got %d locations for definition, expected 1", len(locs))
|
t.Errorf("got %d locations for definition, expected 1", len(locs))
|
||||||
}
|
}
|
||||||
locURI := span.NewURI(locs[0].URI)
|
|
||||||
lm := r.mapper(locURI)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
if hover != nil {
|
if hover != nil {
|
||||||
tag := fmt.Sprintf("%s-hover", d.Name)
|
tag := fmt.Sprintf("%s-hover", d.Name)
|
||||||
filename, err := d.Src.URI().Filename()
|
filename, err := d.Src.URI().Filename()
|
||||||
|
@ -357,13 +353,29 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
||||||
if hover.Contents.Value != expectHover {
|
if hover.Contents.Value != expectHover {
|
||||||
t.Errorf("for %v got %q want %q", d.Src, hover.Contents.Value, expectHover)
|
t.Errorf("for %v got %q want %q", d.Src, hover.Contents.Value, expectHover)
|
||||||
}
|
}
|
||||||
|
} else if !d.OnlyHover {
|
||||||
|
locURI := span.NewURI(locs[0].URI)
|
||||||
|
lm, err := r.mapper(locURI)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("no tests ran for %s", d.Src.URI())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
|
func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
|
||||||
for name, locations := range data {
|
for name, locations := range data {
|
||||||
m := r.mapper(locations[0].URI())
|
m, err := r.mapper(locations[0].URI())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
loc, err := m.Location(locations[0])
|
loc, err := m.Location(locations[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed for %v: %v", locations[0], err)
|
t.Fatalf("failed for %v: %v", locations[0], err)
|
||||||
|
@ -405,16 +417,19 @@ func (r *runner) Symbol(t *testing.T, data tests.Symbols) {
|
||||||
t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols))
|
t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if diff := r.diffSymbols(uri, expectedSymbols, symbols); diff != "" {
|
if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
|
||||||
t.Error(diff)
|
t.Error(diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) diffSymbols(uri span.URI, want []source.Symbol, got []protocol.DocumentSymbol) string {
|
func (r *runner) diffSymbols(t *testing.T, uri span.URI, want []source.Symbol, got []protocol.DocumentSymbol) string {
|
||||||
sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name })
|
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 })
|
sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name })
|
||||||
m := r.mapper(uri)
|
m, err := r.mapper(uri)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
if len(got) != len(want) {
|
if len(got) != len(want) {
|
||||||
return summarizeSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
|
return summarizeSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
|
||||||
}
|
}
|
||||||
|
@ -433,7 +448,7 @@ func (r *runner) diffSymbols(uri span.URI, want []source.Symbol, got []protocol.
|
||||||
if w.SelectionSpan != spn {
|
if w.SelectionSpan != spn {
|
||||||
return summarizeSymbols(i, want, got, "incorrect span got %v want %v", spn, w.SelectionSpan)
|
return summarizeSymbols(i, want, got, "incorrect span got %v want %v", spn, w.SelectionSpan)
|
||||||
}
|
}
|
||||||
if msg := r.diffSymbols(uri, w.Children, g.Children); msg != "" {
|
if msg := r.diffSymbols(t, uri, w.Children, g.Children); msg != "" {
|
||||||
return fmt.Sprintf("children of %s: %s", w.Name, msg)
|
return fmt.Sprintf("children of %s: %s", w.Name, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -461,7 +476,10 @@ func summarizeSymbols(i int, want []source.Symbol, got []protocol.DocumentSymbol
|
||||||
|
|
||||||
func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
|
func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
|
||||||
for spn, expectedSignatures := range data {
|
for spn, expectedSignatures := range data {
|
||||||
m := r.mapper(spn.URI())
|
m, err := r.mapper(spn.URI())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
loc, err := m.Location(spn)
|
loc, err := m.Location(spn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed for %v: %v", loc, err)
|
t.Fatalf("failed for %v: %v", loc, err)
|
||||||
|
@ -519,7 +537,10 @@ func diffSignatures(spn span.Span, want source.SignatureInformation, got *protoc
|
||||||
|
|
||||||
func (r *runner) Link(t *testing.T, data tests.Links) {
|
func (r *runner) Link(t *testing.T, data tests.Links) {
|
||||||
for uri, wantLinks := range data {
|
for uri, wantLinks := range data {
|
||||||
m := r.mapper(uri)
|
m, err := r.mapper(uri)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
gotLinks, err := r.server.DocumentLink(context.Background(), &protocol.DocumentLinkParams{
|
gotLinks, err := r.server.DocumentLink(context.Background(), &protocol.DocumentLinkParams{
|
||||||
TextDocument: protocol.TextDocumentIdentifier{
|
TextDocument: protocol.TextDocumentIdentifier{
|
||||||
URI: protocol.NewURI(uri),
|
URI: protocol.NewURI(uri),
|
||||||
|
@ -552,28 +573,28 @@ func (r *runner) Link(t *testing.T, data tests.Links) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) mapper(uri span.URI) *protocol.ColumnMapper {
|
func (r *runner) mapper(uri span.URI) (*protocol.ColumnMapper, error) {
|
||||||
fname, err := uri.Filename()
|
filename, err := uri.Filename()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
fset := r.data.Exported.ExpectFileSet
|
fset := r.data.Exported.ExpectFileSet
|
||||||
var f *token.File
|
var f *token.File
|
||||||
fset.Iterate(func(check *token.File) bool {
|
fset.Iterate(func(check *token.File) bool {
|
||||||
if check.Name() == fname {
|
if check.Name() == filename {
|
||||||
f = check
|
f = check
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return nil
|
return nil, fmt.Errorf("no token.File for %s", uri)
|
||||||
}
|
}
|
||||||
content, err := r.data.Exported.FileContents(f.Name())
|
content, err := r.data.Exported.FileContents(f.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
return protocol.NewColumnMapper(uri, fset, f, content)
|
return protocol.NewColumnMapper(uri, fset, f, content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBytesOffset(t *testing.T) {
|
func TestBytesOffset(t *testing.T) {
|
||||||
|
|
|
@ -107,8 +107,8 @@ func (c *completer) formatBuiltin(obj types.Object, score float64) CompletionIte
|
||||||
item.Kind = ConstantCompletionItem
|
item.Kind = ConstantCompletionItem
|
||||||
case *types.Builtin:
|
case *types.Builtin:
|
||||||
item.Kind = FunctionCompletionItem
|
item.Kind = FunctionCompletionItem
|
||||||
decl := lookupBuiltin(c.view, obj.Name())
|
decl, ok := lookupBuiltinDecl(c.view, obj.Name()).(*ast.FuncDecl)
|
||||||
if decl == nil {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
params, _ := formatFieldList(c.ctx, c.view, decl.Type.Params)
|
params, _ := formatFieldList(c.ctx, c.view, decl.Type.Params)
|
||||||
|
@ -127,22 +127,6 @@ func (c *completer) formatBuiltin(obj types.Object, score float64) CompletionIte
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupBuiltin(v View, name string) *ast.FuncDecl {
|
|
||||||
builtinPkg := v.BuiltinPackage()
|
|
||||||
if builtinPkg == nil || builtinPkg.Scope == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
fn := builtinPkg.Scope.Lookup(name)
|
|
||||||
if fn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
decl, ok := fn.Decl.(*ast.FuncDecl)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
var replacer = strings.NewReplacer(
|
var replacer = strings.NewReplacer(
|
||||||
`ComplexType`, `complex128`,
|
`ComplexType`, `complex128`,
|
||||||
`FloatType`, `float64`,
|
`FloatType`, `float64`,
|
||||||
|
|
|
@ -66,17 +66,25 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag
|
||||||
// Prepare the reports we will send for this package.
|
// Prepare the reports we will send for this package.
|
||||||
reports := make(map[span.URI][]Diagnostic)
|
reports := make(map[span.URI][]Diagnostic)
|
||||||
for _, filename := range pkg.GetFilenames() {
|
for _, filename := range pkg.GetFilenames() {
|
||||||
reports[span.FileURI(filename)] = []Diagnostic{}
|
uri := span.FileURI(filename)
|
||||||
|
if v.Ignore(uri) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
reports[uri] = []Diagnostic{}
|
||||||
}
|
}
|
||||||
// Run diagnostics for the package that this URI belongs to.
|
// Run diagnostics for the package that this URI belongs to.
|
||||||
if !diagnostics(ctx, v, pkg, reports) {
|
if !diagnostics(ctx, v, pkg, reports) {
|
||||||
// If we don't have any list, parse, or type errors, run analyses.
|
// If we don't have any list, parse, or type errors, run analyses.
|
||||||
if err := analyses(ctx, v, pkg, reports); err != nil {
|
if err := analyses(ctx, v, pkg, reports); err != nil {
|
||||||
return singleDiagnostic(uri, "failed to run analyses for %s", uri), nil
|
return singleDiagnostic(uri, "failed to run analyses for %s: %v", uri, err), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Updates to the diagnostics for this package may need to be propagated.
|
// Updates to the diagnostics for this package may need to be propagated.
|
||||||
for _, f := range gof.GetActiveReverseDeps(ctx) {
|
for _, f := range gof.GetActiveReverseDeps(ctx) {
|
||||||
|
if f == nil {
|
||||||
|
v.Session().Logger().Errorf(ctx, "nil file in reverse active dependencies for %s", f.URI())
|
||||||
|
continue
|
||||||
|
}
|
||||||
pkg := f.GetPackage(ctx)
|
pkg := f.GetPackage(ctx)
|
||||||
if pkg == nil {
|
if pkg == nil {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -38,9 +38,19 @@ func (i *IdentifierInfo) Hover(ctx context.Context, qf types.Qualifier, markdown
|
||||||
case *types.TypeName, *types.Var, *types.Const, *types.Func:
|
case *types.TypeName, *types.Var, *types.Const, *types.Func:
|
||||||
return formatGenDecl(node, obj, obj.Type(), f)
|
return formatGenDecl(node, obj, obj.Type(), f)
|
||||||
}
|
}
|
||||||
|
case *ast.TypeSpec:
|
||||||
|
if obj.Parent() == types.Universe {
|
||||||
|
if obj.Name() == "error" {
|
||||||
|
return f(node, nil)
|
||||||
|
}
|
||||||
|
return f(node.Name, nil) // comments not needed for builtins
|
||||||
|
}
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
if _, ok := obj.(*types.Func); ok {
|
switch obj.(type) {
|
||||||
|
case *types.Func:
|
||||||
return f(obj, node.Doc)
|
return f(obj, node.Doc)
|
||||||
|
case *types.Builtin:
|
||||||
|
return f(node.Type, node.Doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f(obj, nil)
|
return f(obj, nil)
|
||||||
|
|
|
@ -27,7 +27,7 @@ type IdentifierInfo struct {
|
||||||
}
|
}
|
||||||
Declaration struct {
|
Declaration struct {
|
||||||
Range span.Range
|
Range span.Range
|
||||||
Node ast.Decl
|
Node ast.Node
|
||||||
Object types.Object
|
Object types.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,15 +64,12 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
|
||||||
return nil, fmt.Errorf("can't find node enclosing position")
|
return nil, fmt.Errorf("can't find node enclosing position")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle import specs first because they can contain *ast.Idents, and
|
// Handle import specs separately, as there is no formal position for a package declaration.
|
||||||
// we don't want the default *ast.Ident behavior below.
|
if result, err := importSpec(f, fAST, pkg, pos); result != nil || err != nil {
|
||||||
if result, err := checkImportSpec(f, fAST, pkg, pos); result != nil || err != nil {
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &IdentifierInfo{
|
result := &IdentifierInfo{File: f}
|
||||||
File: f,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch node := path[0].(type) {
|
switch node := path[0].(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
|
@ -95,6 +92,22 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
|
||||||
if result.Declaration.Object == nil {
|
if result.Declaration.Object == nil {
|
||||||
return nil, fmt.Errorf("no object for ident %v", result.Name)
|
return nil, fmt.Errorf("no object for ident %v", result.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Handle builtins separately.
|
||||||
|
if result.Declaration.Object.Parent() == types.Universe {
|
||||||
|
decl, ok := lookupBuiltinDecl(f.View(), result.Name).(ast.Node)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no declaration for %s", result.Name)
|
||||||
|
}
|
||||||
|
result.Declaration.Node = decl
|
||||||
|
if result.Declaration.Range, err = posToRange(ctx, v.FileSet(), result.Name, decl.Pos()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
if result.wasEmbeddedField {
|
if result.wasEmbeddedField {
|
||||||
// The original position was on the embedded field declaration, so we
|
// The original position was on the embedded field declaration, so we
|
||||||
// try to dig out the type and jump to that instead.
|
// try to dig out the type and jump to that instead.
|
||||||
|
@ -104,8 +117,8 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
if result.Declaration.Range, err = objToRange(ctx, v, result.Declaration.Object); err != nil {
|
if result.Declaration.Range, err = objToRange(ctx, v.FileSet(), result.Declaration.Object); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if result.Declaration.Node, err = objToNode(ctx, v, result.Declaration.Object, result.Declaration.Range); err != nil {
|
if result.Declaration.Node, err = objToNode(ctx, v, result.Declaration.Object, result.Declaration.Range); err != nil {
|
||||||
|
@ -118,68 +131,16 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
|
||||||
result.Type.Object = typeToObject(typ)
|
result.Type.Object = typeToObject(typ)
|
||||||
if result.Type.Object != nil {
|
if result.Type.Object != nil {
|
||||||
// Identifiers with the type "error" are a special case with no position.
|
// Identifiers with the type "error" are a special case with no position.
|
||||||
if types.IsInterface(result.Type.Object.Type()) && result.Type.Object.Pkg() == nil && result.Type.Object.Name() == "error" {
|
if hasErrorType(result.Type.Object) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
if result.Type.Range, err = objToRange(ctx, v, result.Type.Object); err != nil {
|
if result.Type.Range, err = objToRange(ctx, v.FileSet(), result.Type.Object); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkImportSpec(f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
|
|
||||||
// Check if pos is in an *ast.ImportSpec.
|
|
||||||
for _, imp := range fAST.Imports {
|
|
||||||
if imp.Pos() <= pos && pos < imp.End() {
|
|
||||||
pkgPath, err := strconv.Unquote(imp.Path.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &IdentifierInfo{
|
|
||||||
File: f,
|
|
||||||
Name: pkgPath,
|
|
||||||
Range: span.NewRange(f.View().FileSet(), imp.Pos(), imp.End()),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consider the definition of an import spec to be the imported package.
|
|
||||||
result.Declaration.Range, err = importedPkg(f.View(), pkg, pkgPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func importedPkg(v View, pkg Package, importPath string) (span.Range, error) {
|
|
||||||
otherPkg := pkg.GetImport(importPath)
|
|
||||||
if otherPkg == nil {
|
|
||||||
return span.Range{}, fmt.Errorf("no import for %q", importPath)
|
|
||||||
}
|
|
||||||
if otherPkg.GetSyntax() == nil {
|
|
||||||
return span.Range{}, fmt.Errorf("no syntax for for %q", importPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Heuristic: Jump to the longest file of the package, assuming it's the most "interesting."
|
|
||||||
// TODO: Consider alternative approaches, if necessary.
|
|
||||||
var longest *ast.File
|
|
||||||
for _, astFile := range otherPkg.GetSyntax() {
|
|
||||||
if longest == nil || astFile.End()-astFile.Pos() > longest.End()-longest.Pos() {
|
|
||||||
longest = astFile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if longest == nil {
|
|
||||||
return span.Range{}, fmt.Errorf("package %q has no files", importPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return span.NewRange(v.FileSet(), longest.Name.Pos(), longest.Name.End()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeToObject(typ types.Type) types.Object {
|
func typeToObject(typ types.Type) types.Object {
|
||||||
switch typ := typ.(type) {
|
switch typ := typ.(type) {
|
||||||
case *types.Named:
|
case *types.Named:
|
||||||
|
@ -191,12 +152,19 @@ func typeToObject(typ types.Type) types.Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func objToRange(ctx context.Context, v View, obj types.Object) (span.Range, error) {
|
func hasErrorType(obj types.Object) bool {
|
||||||
p := obj.Pos()
|
return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error"
|
||||||
if !p.IsValid() {
|
}
|
||||||
return span.Range{}, fmt.Errorf("invalid position for %v", obj.Name())
|
|
||||||
|
func objToRange(ctx context.Context, fset *token.FileSet, obj types.Object) (span.Range, error) {
|
||||||
|
return posToRange(ctx, fset, obj.Name(), obj.Pos())
|
||||||
|
}
|
||||||
|
|
||||||
|
func posToRange(ctx context.Context, fset *token.FileSet, name string, pos token.Pos) (span.Range, error) {
|
||||||
|
if !pos.IsValid() {
|
||||||
|
return span.Range{}, fmt.Errorf("invalid position for %v", name)
|
||||||
}
|
}
|
||||||
return span.NewRange(v.FileSet(), p, p+token.Pos(len(obj.Name()))), nil
|
return span.NewRange(fset, pos, pos+token.Pos(len(name))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func objToNode(ctx context.Context, v View, obj types.Object, rng span.Range) (ast.Decl, error) {
|
func objToNode(ctx context.Context, v View, obj types.Object, rng span.Range) (ast.Decl, error) {
|
||||||
|
@ -234,3 +202,42 @@ func objToNode(ctx context.Context, v View, obj types.Object, rng span.Range) (a
|
||||||
}
|
}
|
||||||
return nil, nil // didn't find a node, but don't fail
|
return nil, nil // didn't find a node, but don't fail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// importSpec handles positions inside of an *ast.ImportSpec.
|
||||||
|
func importSpec(f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
|
||||||
|
for _, imp := range fAST.Imports {
|
||||||
|
if !(imp.Pos() <= pos && pos < imp.End()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
importPath, err := strconv.Unquote(imp.Path.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
|
||||||
|
}
|
||||||
|
result := &IdentifierInfo{
|
||||||
|
File: f,
|
||||||
|
Name: importPath,
|
||||||
|
Range: span.NewRange(f.View().FileSet(), imp.Pos(), imp.End()),
|
||||||
|
}
|
||||||
|
// Consider the "declaration" of an import spec to be the imported package.
|
||||||
|
importedPkg := pkg.GetImport(importPath)
|
||||||
|
if importedPkg == nil {
|
||||||
|
return nil, fmt.Errorf("no import for %q", importPath)
|
||||||
|
}
|
||||||
|
if importedPkg.GetSyntax() == nil {
|
||||||
|
return nil, fmt.Errorf("no syntax for for %q", importPath)
|
||||||
|
}
|
||||||
|
// Heuristic: Jump to the longest (most "interesting") file of the package.
|
||||||
|
var dest *ast.File
|
||||||
|
for _, f := range importedPkg.GetSyntax() {
|
||||||
|
if dest == nil || f.End()-f.Pos() > dest.End()-dest.Pos() {
|
||||||
|
dest = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dest == nil {
|
||||||
|
return nil, fmt.Errorf("package %q has no files", importPath)
|
||||||
|
}
|
||||||
|
result.Declaration.Range = span.NewRange(f.View().FileSet(), dest.Name.Pos(), dest.Name.End())
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
@ -89,8 +89,8 @@ func SignatureHelp(ctx context.Context, f GoFile, pos token.Pos) (*SignatureInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*SignatureInformation, error) {
|
func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*SignatureInformation, error) {
|
||||||
decl := lookupBuiltin(v, name)
|
decl, ok := lookupBuiltinDecl(v, name).(*ast.FuncDecl)
|
||||||
if decl == nil {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no function declaration for builtin: %s", name)
|
return nil, fmt.Errorf("no function declaration for builtin: %s", name)
|
||||||
}
|
}
|
||||||
params, _ := formatFieldList(ctx, v, decl.Type.Params)
|
params, _ := formatFieldList(ctx, v, decl.Type.Params)
|
||||||
|
|
|
@ -310,11 +310,6 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
||||||
rng = ident.Type.Range
|
rng = ident.Type.Range
|
||||||
hover = ""
|
hover = ""
|
||||||
}
|
}
|
||||||
if def, err := rng.Span(); err != nil {
|
|
||||||
t.Fatalf("failed for %v: %v", rng, err)
|
|
||||||
} else if def != d.Def {
|
|
||||||
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
|
|
||||||
}
|
|
||||||
if hover != "" {
|
if hover != "" {
|
||||||
tag := fmt.Sprintf("%s-hover", d.Name)
|
tag := fmt.Sprintf("%s-hover", d.Name)
|
||||||
filename, err := d.Src.URI().Filename()
|
filename, err := d.Src.URI().Filename()
|
||||||
|
@ -327,6 +322,14 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
||||||
if hover != expectHover {
|
if hover != expectHover {
|
||||||
t.Errorf("for %v got %q want %q", d.Src, hover, expectHover)
|
t.Errorf("for %v got %q want %q", d.Src, hover, expectHover)
|
||||||
}
|
}
|
||||||
|
} else if !d.OnlyHover {
|
||||||
|
if def, err := rng.Span(); err != nil {
|
||||||
|
t.Fatalf("failed for %v: %v", rng, err)
|
||||||
|
} else if def != d.Def {
|
||||||
|
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("no tests ran for %s", d.Src.URI())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,18 @@ func resolveInvalid(obj types.Object, node ast.Node, info *types.Info) types.Obj
|
||||||
return formatResult(resultExpr)
|
return formatResult(resultExpr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lookupBuiltinDecl(v View, name string) interface{} {
|
||||||
|
builtinPkg := v.BuiltinPackage()
|
||||||
|
if builtinPkg == nil || builtinPkg.Scope == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
obj := builtinPkg.Scope.Lookup(name)
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return obj.Decl
|
||||||
|
}
|
||||||
|
|
||||||
func isPointer(T types.Type) bool {
|
func isPointer(T types.Type) bool {
|
||||||
_, ok := T.(*types.Pointer)
|
_, ok := T.(*types.Pointer)
|
||||||
return ok
|
return ok
|
||||||
|
|
|
@ -67,6 +67,7 @@ type View interface {
|
||||||
Config() packages.Config
|
Config() packages.Config
|
||||||
SetEnv([]string)
|
SetEnv([]string)
|
||||||
Shutdown(ctx context.Context)
|
Shutdown(ctx context.Context)
|
||||||
|
Ignore(span.URI) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// File represents a source file of any type.
|
// File represents a source file of any type.
|
||||||
|
|
|
@ -13,4 +13,7 @@ func Stuff() { //@Stuff
|
||||||
|
|
||||||
var err error //@err
|
var err error //@err
|
||||||
fmt.Printf("%v", err) //@godef("err", err)
|
fmt.Printf("%v", err) //@godef("err", err)
|
||||||
|
|
||||||
|
var y string //@string,hover("string", string)
|
||||||
|
_ = make([]int, 0) //@make,hover("make", make)
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,3 +67,8 @@ godef/a/a.go:14:6-9: defined here as var err error
|
||||||
|
|
||||||
-- err-hover --
|
-- err-hover --
|
||||||
var err error
|
var err error
|
||||||
|
-- string-hover --
|
||||||
|
string
|
||||||
|
-- make-hover --
|
||||||
|
The make built-in function allocates and initializes an object of type slice, map, or chan (only).
|
||||||
|
func(t Type, size ...IntegerType) Type
|
||||||
|
|
|
@ -32,7 +32,7 @@ const (
|
||||||
ExpectedCompletionSnippetCount = 13
|
ExpectedCompletionSnippetCount = 13
|
||||||
ExpectedDiagnosticsCount = 17
|
ExpectedDiagnosticsCount = 17
|
||||||
ExpectedFormatCount = 5
|
ExpectedFormatCount = 5
|
||||||
ExpectedDefinitionsCount = 33
|
ExpectedDefinitionsCount = 35
|
||||||
ExpectedTypeDefinitionsCount = 2
|
ExpectedTypeDefinitionsCount = 2
|
||||||
ExpectedHighlightsCount = 2
|
ExpectedHighlightsCount = 2
|
||||||
ExpectedSymbolsCount = 1
|
ExpectedSymbolsCount = 1
|
||||||
|
@ -94,10 +94,11 @@ type Tests interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Definition struct {
|
type Definition struct {
|
||||||
Name string
|
Name string
|
||||||
Src span.Span
|
Src span.Span
|
||||||
IsType bool
|
IsType bool
|
||||||
Def span.Span
|
OnlyHover bool
|
||||||
|
Def span.Span
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompletionSnippet struct {
|
type CompletionSnippet struct {
|
||||||
|
@ -203,6 +204,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||||
"format": data.collectFormats,
|
"format": data.collectFormats,
|
||||||
"godef": data.collectDefinitions,
|
"godef": data.collectDefinitions,
|
||||||
"typdef": data.collectTypeDefinitions,
|
"typdef": data.collectTypeDefinitions,
|
||||||
|
"hover": data.collectHoverDefinitions,
|
||||||
"highlight": data.collectHighlights,
|
"highlight": data.collectHighlights,
|
||||||
"symbol": data.collectSymbols,
|
"symbol": data.collectSymbols,
|
||||||
"signature": data.collectSignatures,
|
"signature": data.collectSignatures,
|
||||||
|
@ -217,9 +219,10 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||||
symbols[i].Children = children
|
symbols[i].Children = children
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// run a second pass to collect names for some entries.
|
// Collect names for the entries that require golden files.
|
||||||
if err := data.Exported.Expect(map[string]interface{}{
|
if err := data.Exported.Expect(map[string]interface{}{
|
||||||
"godef": data.collectDefinitionNames,
|
"godef": data.collectDefinitionNames,
|
||||||
|
"hover": data.collectDefinitionNames,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -420,6 +423,14 @@ func (data *Data) collectDefinitions(src, target span.Span) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (data *Data) collectHoverDefinitions(src, target span.Span) {
|
||||||
|
data.Definitions[src] = Definition{
|
||||||
|
Src: src,
|
||||||
|
Def: target,
|
||||||
|
OnlyHover: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (data *Data) collectTypeDefinitions(src, target span.Span) {
|
func (data *Data) collectTypeDefinitions(src, target span.Span) {
|
||||||
data.Definitions[src] = Definition{
|
data.Definitions[src] = Definition{
|
||||||
Src: src,
|
Src: src,
|
||||||
|
|
Loading…
Reference in New Issue