mirror of
https://github.com/golang/go
synced 2024-11-18 16:04:44 -07:00
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
4
internal/lsp/cache/file.go
vendored
4
internal/lsp/cache/file.go
vendored
@ -174,12 +174,12 @@ func (f *goFile) GetActiveReverseDeps(ctx context.Context) []source.GoFile {
|
||||
results := make(map[*goFile]struct{})
|
||||
f.view.reverseDeps(ctx, seen, results, pkg.PkgPath())
|
||||
|
||||
files := make([]source.GoFile, 0, len(results))
|
||||
var files []source.GoFile
|
||||
for rd := range results {
|
||||
if rd == nil {
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
|
4
internal/lsp/cache/session.go
vendored
4
internal/lsp/cache/session.go
vendored
@ -63,6 +63,10 @@ func (s *session) NewView(name string, folder span.URI, config *packages.Config)
|
||||
pcache: &packageCache{
|
||||
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)
|
||||
// we always need to drop the view map
|
||||
|
30
internal/lsp/cache/view.go
vendored
30
internal/lsp/cache/view.go
vendored
@ -6,7 +6,6 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
@ -67,6 +66,9 @@ type view struct {
|
||||
|
||||
// builtinPkg is the AST package used to resolve builtin types.
|
||||
builtinPkg *ast.Package
|
||||
|
||||
// ignoredURIs is the set of URIs of files that we ignore.
|
||||
ignoredURIs map[span.URI]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.
|
||||
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 {
|
||||
return nil, err
|
||||
} else if f != nil {
|
||||
return f, nil
|
||||
}
|
||||
filename, err := uri.Filename()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := &goFile{
|
||||
fileBase: fileBase{
|
||||
view: v,
|
||||
@ -312,18 +311,11 @@ func (v *view) getFile(uri span.URI) (viewFile, error) {
|
||||
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.
|
||||
func (v *view) isIgnored(filename string) bool {
|
||||
bpkg := v.BuiltinPackage()
|
||||
if bpkg != nil {
|
||||
for builtinFilename := range bpkg.Files {
|
||||
if filename == builtinFilename {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
func (v *view) Ignore(uri span.URI) bool {
|
||||
_, ok := v.ignoredURIs[uri]
|
||||
return ok
|
||||
}
|
||||
|
||||
// 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) {
|
||||
for _, d := range data {
|
||||
if d.IsType {
|
||||
// TODO: support type definition queries
|
||||
if d.IsType || d.OnlyHover {
|
||||
// TODO: support type definition, hover queries
|
||||
continue
|
||||
}
|
||||
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) {
|
||||
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)
|
||||
if err != nil {
|
||||
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 {
|
||||
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 {
|
||||
tag := fmt.Sprintf("%s-hover", d.Name)
|
||||
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 {
|
||||
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) {
|
||||
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])
|
||||
if err != nil {
|
||||
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))
|
||||
continue
|
||||
}
|
||||
if diff := r.diffSymbols(uri, expectedSymbols, symbols); diff != "" {
|
||||
if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); 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(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) {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -461,7 +476,10 @@ func summarizeSymbols(i int, want []source.Symbol, got []protocol.DocumentSymbol
|
||||
|
||||
func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
|
||||
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)
|
||||
if err != nil {
|
||||
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) {
|
||||
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{
|
||||
TextDocument: protocol.TextDocumentIdentifier{
|
||||
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 {
|
||||
fname, err := uri.Filename()
|
||||
func (r *runner) mapper(uri span.URI) (*protocol.ColumnMapper, error) {
|
||||
filename, err := uri.Filename()
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fset := r.data.Exported.ExpectFileSet
|
||||
var f *token.File
|
||||
fset.Iterate(func(check *token.File) bool {
|
||||
if check.Name() == fname {
|
||||
if check.Name() == filename {
|
||||
f = check
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if f == nil {
|
||||
return nil
|
||||
return nil, fmt.Errorf("no token.File for %s", uri)
|
||||
}
|
||||
content, err := r.data.Exported.FileContents(f.Name())
|
||||
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) {
|
||||
|
@ -107,8 +107,8 @@ func (c *completer) formatBuiltin(obj types.Object, score float64) CompletionIte
|
||||
item.Kind = ConstantCompletionItem
|
||||
case *types.Builtin:
|
||||
item.Kind = FunctionCompletionItem
|
||||
decl := lookupBuiltin(c.view, obj.Name())
|
||||
if decl == nil {
|
||||
decl, ok := lookupBuiltinDecl(c.view, obj.Name()).(*ast.FuncDecl)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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(
|
||||
`ComplexType`, `complex128`,
|
||||
`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.
|
||||
reports := make(map[span.URI][]Diagnostic)
|
||||
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.
|
||||
if !diagnostics(ctx, v, pkg, reports) {
|
||||
// If we don't have any list, parse, or type errors, run analyses.
|
||||
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.
|
||||
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)
|
||||
if pkg == nil {
|
||||
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:
|
||||
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:
|
||||
if _, ok := obj.(*types.Func); ok {
|
||||
switch obj.(type) {
|
||||
case *types.Func:
|
||||
return f(obj, node.Doc)
|
||||
case *types.Builtin:
|
||||
return f(node.Type, node.Doc)
|
||||
}
|
||||
}
|
||||
return f(obj, nil)
|
||||
|
@ -27,7 +27,7 @@ type IdentifierInfo struct {
|
||||
}
|
||||
Declaration struct {
|
||||
Range span.Range
|
||||
Node ast.Decl
|
||||
Node ast.Node
|
||||
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")
|
||||
}
|
||||
|
||||
// Handle import specs first because they can contain *ast.Idents, and
|
||||
// we don't want the default *ast.Ident behavior below.
|
||||
if result, err := checkImportSpec(f, fAST, pkg, pos); result != nil || err != nil {
|
||||
// Handle import specs separately, as there is no formal position for a package declaration.
|
||||
if result, err := importSpec(f, fAST, pkg, pos); result != nil || err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result := &IdentifierInfo{
|
||||
File: f,
|
||||
}
|
||||
result := &IdentifierInfo{File: f}
|
||||
|
||||
switch node := path[0].(type) {
|
||||
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 {
|
||||
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 {
|
||||
// The original position was on the embedded field declaration, so we
|
||||
// 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
|
||||
}
|
||||
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)
|
||||
if result.Type.Object != nil {
|
||||
// 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
|
||||
}
|
||||
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 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 {
|
||||
switch typ := typ.(type) {
|
||||
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) {
|
||||
p := obj.Pos()
|
||||
if !p.IsValid() {
|
||||
return span.Range{}, fmt.Errorf("invalid position for %v", obj.Name())
|
||||
func hasErrorType(obj types.Object) bool {
|
||||
return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error"
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
decl := lookupBuiltin(v, name)
|
||||
if decl == nil {
|
||||
decl, ok := lookupBuiltinDecl(v, name).(*ast.FuncDecl)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no function declaration for builtin: %s", name)
|
||||
}
|
||||
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
|
||||
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 != "" {
|
||||
tag := fmt.Sprintf("%s-hover", d.Name)
|
||||
filename, err := d.Src.URI().Filename()
|
||||
@ -327,6 +322,14 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
||||
if 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
_, ok := T.(*types.Pointer)
|
||||
return ok
|
||||
|
@ -67,6 +67,7 @@ type View interface {
|
||||
Config() packages.Config
|
||||
SetEnv([]string)
|
||||
Shutdown(ctx context.Context)
|
||||
Ignore(span.URI) bool
|
||||
}
|
||||
|
||||
// File represents a source file of any type.
|
||||
|
3
internal/lsp/testdata/godef/a/a.go
vendored
3
internal/lsp/testdata/godef/a/a.go
vendored
@ -13,4 +13,7 @@ func Stuff() { //@Stuff
|
||||
|
||||
var err error //@err
|
||||
fmt.Printf("%v", err) //@godef("err", err)
|
||||
|
||||
var y string //@string,hover("string", string)
|
||||
_ = make([]int, 0) //@make,hover("make", make)
|
||||
}
|
||||
|
5
internal/lsp/testdata/godef/a/a.go.golden
vendored
5
internal/lsp/testdata/godef/a/a.go.golden
vendored
@ -67,3 +67,8 @@ godef/a/a.go:14:6-9: defined here as var err error
|
||||
|
||||
-- err-hover --
|
||||
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
|
||||
ExpectedDiagnosticsCount = 17
|
||||
ExpectedFormatCount = 5
|
||||
ExpectedDefinitionsCount = 33
|
||||
ExpectedDefinitionsCount = 35
|
||||
ExpectedTypeDefinitionsCount = 2
|
||||
ExpectedHighlightsCount = 2
|
||||
ExpectedSymbolsCount = 1
|
||||
@ -94,10 +94,11 @@ type Tests interface {
|
||||
}
|
||||
|
||||
type Definition struct {
|
||||
Name string
|
||||
Src span.Span
|
||||
IsType bool
|
||||
Def span.Span
|
||||
Name string
|
||||
Src span.Span
|
||||
IsType bool
|
||||
OnlyHover bool
|
||||
Def span.Span
|
||||
}
|
||||
|
||||
type CompletionSnippet struct {
|
||||
@ -203,6 +204,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||
"format": data.collectFormats,
|
||||
"godef": data.collectDefinitions,
|
||||
"typdef": data.collectTypeDefinitions,
|
||||
"hover": data.collectHoverDefinitions,
|
||||
"highlight": data.collectHighlights,
|
||||
"symbol": data.collectSymbols,
|
||||
"signature": data.collectSignatures,
|
||||
@ -217,9 +219,10 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
|
||||
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{}{
|
||||
"godef": data.collectDefinitionNames,
|
||||
"hover": data.collectDefinitionNames,
|
||||
}); err != nil {
|
||||
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) {
|
||||
data.Definitions[src] = Definition{
|
||||
Src: src,
|
||||
|
Loading…
Reference in New Issue
Block a user