mirror of
https://github.com/golang/go
synced 2024-11-19 02:14:43 -07:00
63859f3815
Now the "type" of a *ast.PkgName is the package it points to. Of course, a package is not a real types.Type, but we can still jump you there. We have to pick one of the package's files, so we choose the longest one, hoping it is the most interesting. Similarly, the "definition" of an *ast.ImportSpec is the package being imported. I also added a nil check for the package in SignatureHelp. This panics for me occasionally. Change-Id: Ide4640530a28bcec9da6de36723eb7f0e4cc941c GitHub-Last-Rev: 8190baa0b908065db5b53f236de03d2f3bff39b5 GitHub-Pull-Request: golang/tools#92 Reviewed-on: https://go-review.googlesource.com/c/tools/+/174081 Run-TryBot: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
137 lines
3.7 KiB
Go
137 lines
3.7 KiB
Go
// Copyright 201p The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package source
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/format"
|
|
"go/token"
|
|
"go/types"
|
|
"strings"
|
|
)
|
|
|
|
// formatter returns the a hover value formatted with its documentation.
|
|
type formatter func(interface{}, *ast.CommentGroup) (string, error)
|
|
|
|
func (i *IdentifierInfo) Hover(ctx context.Context, qf types.Qualifier, enhancedHover, markdownSupported bool) (string, error) {
|
|
file := i.File.GetAST(ctx)
|
|
if qf == nil {
|
|
pkg := i.File.GetPackage(ctx)
|
|
qf = qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
|
|
}
|
|
var b strings.Builder
|
|
f := func(x interface{}, c *ast.CommentGroup) (string, error) {
|
|
return writeHover(x, i.File.GetFileSet(ctx), &b, c, markdownSupported, qf)
|
|
}
|
|
obj := i.Declaration.Object
|
|
// TODO(rstambler): Remove this configuration when hover behavior is stable.
|
|
if enhancedHover {
|
|
switch node := i.Declaration.Node.(type) {
|
|
case *ast.GenDecl:
|
|
switch obj := obj.(type) {
|
|
case *types.TypeName, *types.Var, *types.Const, *types.Func:
|
|
return formatGenDecl(node, obj, obj.Type(), f)
|
|
}
|
|
case *ast.FuncDecl:
|
|
if _, ok := obj.(*types.Func); ok {
|
|
return f(obj, node.Doc)
|
|
}
|
|
}
|
|
}
|
|
return f(obj, nil)
|
|
}
|
|
|
|
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type, f formatter) (string, error) {
|
|
if _, ok := typ.(*types.Named); ok {
|
|
switch typ.Underlying().(type) {
|
|
case *types.Interface, *types.Struct:
|
|
return formatGenDecl(node, obj, typ.Underlying(), f)
|
|
}
|
|
}
|
|
var spec ast.Spec
|
|
for _, s := range node.Specs {
|
|
if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() {
|
|
spec = s
|
|
break
|
|
}
|
|
}
|
|
if spec == nil {
|
|
return "", fmt.Errorf("no spec for node %v at position %v", node, obj.Pos())
|
|
}
|
|
// If we have a field or method.
|
|
switch obj.(type) {
|
|
case *types.Var, *types.Const, *types.Func:
|
|
return formatVar(spec, obj, f)
|
|
}
|
|
// Handle types.
|
|
switch spec := spec.(type) {
|
|
case *ast.TypeSpec:
|
|
// If multiple types are declared in the same block.
|
|
if len(node.Specs) > 1 {
|
|
return f(spec.Type, spec.Doc)
|
|
} else {
|
|
return f(spec, node.Doc)
|
|
}
|
|
case *ast.ValueSpec:
|
|
return f(spec, spec.Doc)
|
|
case *ast.ImportSpec:
|
|
return f(spec, spec.Doc)
|
|
}
|
|
return "", fmt.Errorf("unable to format spec %v (%T)", spec, spec)
|
|
}
|
|
|
|
func formatVar(node ast.Spec, obj types.Object, f formatter) (string, error) {
|
|
var fieldList *ast.FieldList
|
|
if spec, ok := node.(*ast.TypeSpec); ok {
|
|
switch t := spec.Type.(type) {
|
|
case *ast.StructType:
|
|
fieldList = t.Fields
|
|
case *ast.InterfaceType:
|
|
fieldList = t.Methods
|
|
}
|
|
}
|
|
// If we have a struct or interface declaration,
|
|
// we need to match the object to the corresponding field or method.
|
|
if fieldList != nil {
|
|
for i := 0; i < fieldList.NumFields(); i++ {
|
|
field := fieldList.List[i]
|
|
if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
|
|
if field.Doc.Text() != "" {
|
|
return f(obj, field.Doc)
|
|
} else if field.Comment.Text() != "" {
|
|
return f(obj, field.Comment)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If we weren't able to find documentation for the object.
|
|
return f(obj, nil)
|
|
}
|
|
|
|
// writeHover writes the hover for a given node and its documentation.
|
|
func writeHover(x interface{}, fset *token.FileSet, b *strings.Builder, c *ast.CommentGroup, markdownSupported bool, qf types.Qualifier) (string, error) {
|
|
if c != nil {
|
|
b.WriteString(c.Text())
|
|
b.WriteRune('\n')
|
|
}
|
|
if markdownSupported {
|
|
b.WriteString("```go\n")
|
|
}
|
|
switch x := x.(type) {
|
|
case ast.Node:
|
|
if err := format.Node(b, fset, x); err != nil {
|
|
return "", err
|
|
}
|
|
case types.Object:
|
|
b.WriteString(types.ObjectString(x, qf))
|
|
}
|
|
if markdownSupported {
|
|
b.WriteString("\n```")
|
|
}
|
|
return b.String(), nil
|
|
}
|