2019-05-14 02:59:32 -06:00
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
2019-04-17 16:21:47 -06:00
|
|
|
// 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"
|
2019-04-27 20:45:06 -06:00
|
|
|
"go/doc"
|
2019-04-17 16:21:47 -06:00
|
|
|
"go/format"
|
|
|
|
"go/token"
|
|
|
|
"go/types"
|
2019-05-10 08:32:25 -06:00
|
|
|
"strings"
|
2019-04-17 16:21:47 -06:00
|
|
|
)
|
|
|
|
|
2019-06-04 23:16:23 -06:00
|
|
|
type documentation struct {
|
|
|
|
source interface{}
|
|
|
|
comment *ast.CommentGroup
|
|
|
|
}
|
2019-04-19 11:09:33 -06:00
|
|
|
|
2019-06-04 23:16:23 -06:00
|
|
|
func (i *IdentifierInfo) Hover(ctx context.Context, markdownSupported, wantComments bool) (string, error) {
|
|
|
|
h, err := i.decl.hover(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2019-04-19 11:09:33 -06:00
|
|
|
}
|
2019-06-04 23:16:23 -06:00
|
|
|
c := h.comment
|
|
|
|
if !wantComments {
|
|
|
|
c = nil
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
2019-06-04 23:16:23 -06:00
|
|
|
var b strings.Builder
|
|
|
|
return writeHover(h.source, i.File.FileSet(), &b, c, markdownSupported, i.qf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d declaration) hover(ctx context.Context) (*documentation, error) {
|
|
|
|
obj := d.obj
|
|
|
|
switch node := d.node.(type) {
|
2019-04-27 20:45:06 -06:00
|
|
|
case *ast.GenDecl:
|
|
|
|
switch obj := obj.(type) {
|
|
|
|
case *types.TypeName, *types.Var, *types.Const, *types.Func:
|
2019-06-04 23:16:23 -06:00
|
|
|
return formatGenDecl(node, obj, obj.Type())
|
2019-04-27 20:45:06 -06:00
|
|
|
}
|
2019-05-15 15:58:16 -06:00
|
|
|
case *ast.TypeSpec:
|
|
|
|
if obj.Parent() == types.Universe {
|
|
|
|
if obj.Name() == "error" {
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{node, nil}, nil
|
2019-05-15 15:58:16 -06:00
|
|
|
}
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{node.Name, nil}, nil // comments not needed for builtins
|
2019-05-15 15:58:16 -06:00
|
|
|
}
|
2019-04-27 20:45:06 -06:00
|
|
|
case *ast.FuncDecl:
|
2019-05-15 15:58:16 -06:00
|
|
|
switch obj.(type) {
|
|
|
|
case *types.Func:
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{obj, node.Doc}, nil
|
2019-05-15 15:58:16 -06:00
|
|
|
case *types.Builtin:
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{node.Type, node.Doc}, nil
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
|
|
|
}
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{obj, nil}, nil
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
|
|
|
|
2019-06-04 23:16:23 -06:00
|
|
|
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*documentation, error) {
|
2019-04-19 11:09:33 -06:00
|
|
|
if _, ok := typ.(*types.Named); ok {
|
|
|
|
switch typ.Underlying().(type) {
|
|
|
|
case *types.Interface, *types.Struct:
|
2019-06-04 23:16:23 -06:00
|
|
|
return formatGenDecl(node, obj, typ.Underlying())
|
2019-04-19 11:09:33 -06:00
|
|
|
}
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
2019-04-19 11:09:33 -06:00
|
|
|
var spec ast.Spec
|
|
|
|
for _, s := range node.Specs {
|
|
|
|
if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() {
|
|
|
|
spec = s
|
|
|
|
break
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
|
|
|
}
|
2019-04-19 11:09:33 -06:00
|
|
|
if spec == nil {
|
2019-06-04 23:16:23 -06:00
|
|
|
return nil, fmt.Errorf("no spec for node %v at position %v", node, obj.Pos())
|
2019-04-19 11:09:33 -06:00
|
|
|
}
|
|
|
|
// If we have a field or method.
|
|
|
|
switch obj.(type) {
|
|
|
|
case *types.Var, *types.Const, *types.Func:
|
2019-06-04 23:16:23 -06:00
|
|
|
return formatVar(spec, obj)
|
2019-04-19 11:09:33 -06:00
|
|
|
}
|
|
|
|
// Handle types.
|
|
|
|
switch spec := spec.(type) {
|
|
|
|
case *ast.TypeSpec:
|
|
|
|
if len(node.Specs) > 1 {
|
2019-04-27 20:45:06 -06:00
|
|
|
// If multiple types are declared in the same block.
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{spec.Type, spec.Doc}, nil
|
2019-04-19 11:09:33 -06:00
|
|
|
} else {
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{spec, node.Doc}, nil
|
2019-04-19 11:09:33 -06:00
|
|
|
}
|
|
|
|
case *ast.ValueSpec:
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{spec, spec.Doc}, nil
|
2019-04-19 11:09:33 -06:00
|
|
|
case *ast.ImportSpec:
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{spec, spec.Doc}, nil
|
2019-04-19 11:09:33 -06:00
|
|
|
}
|
2019-06-04 23:16:23 -06:00
|
|
|
return nil, fmt.Errorf("unable to format spec %v (%T)", spec, spec)
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
|
|
|
|
2019-06-04 23:16:23 -06:00
|
|
|
func formatVar(node ast.Spec, obj types.Object) (*documentation, error) {
|
2019-04-19 11:09:33 -06:00
|
|
|
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
|
|
|
|
}
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
2019-04-19 11:09:33 -06:00
|
|
|
// If we have a struct or interface declaration,
|
|
|
|
// we need to match the object to the corresponding field or method.
|
|
|
|
if fieldList != nil {
|
2019-04-27 20:45:06 -06:00
|
|
|
for i := 0; i < len(fieldList.List); i++ {
|
2019-04-19 11:09:33 -06:00
|
|
|
field := fieldList.List[i]
|
|
|
|
if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
|
|
|
|
if field.Doc.Text() != "" {
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{obj, field.Doc}, nil
|
2019-04-19 11:09:33 -06:00
|
|
|
} else if field.Comment.Text() != "" {
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{obj, field.Comment}, nil
|
2019-04-19 11:09:33 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
2019-04-19 11:09:33 -06:00
|
|
|
// If we weren't able to find documentation for the object.
|
2019-06-04 23:16:23 -06:00
|
|
|
return &documentation{obj, nil}, nil
|
2019-04-19 11:09:33 -06:00
|
|
|
}
|
2019-04-17 16:21:47 -06:00
|
|
|
|
2019-04-19 11:09:33 -06:00
|
|
|
// writeHover writes the hover for a given node and its documentation.
|
2019-05-10 08:32:25 -06:00
|
|
|
func writeHover(x interface{}, fset *token.FileSet, b *strings.Builder, c *ast.CommentGroup, markdownSupported bool, qf types.Qualifier) (string, error) {
|
2019-04-19 11:09:33 -06:00
|
|
|
if c != nil {
|
2019-04-27 20:45:06 -06:00
|
|
|
// TODO(rstambler): Improve conversion from Go docs to markdown.
|
2019-06-04 23:16:23 -06:00
|
|
|
b.WriteString(formatDocumentation(c))
|
2019-04-19 11:09:33 -06:00
|
|
|
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
|
2019-04-17 16:21:47 -06:00
|
|
|
}
|
2019-06-04 23:16:23 -06:00
|
|
|
|
|
|
|
func formatDocumentation(c *ast.CommentGroup) string {
|
|
|
|
if c == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return doc.Synopsis(c.Text())
|
|
|
|
}
|