1// Copyright 2019 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package source 6 7import ( 8 "context" 9 "fmt" 10 "go/ast" 11 "go/doc" 12 "go/format" 13 "go/token" 14 "go/types" 15 "strings" 16) 17 18type documentation struct { 19 source interface{} 20 comment *ast.CommentGroup 21} 22 23func (i *IdentifierInfo) Hover(ctx context.Context, markdownSupported, wantComments bool) (string, error) { 24 h, err := i.decl.hover(ctx) 25 if err != nil { 26 return "", err 27 } 28 c := h.comment 29 if !wantComments { 30 c = nil 31 } 32 var b strings.Builder 33 return writeHover(h.source, i.File.FileSet(), &b, c, markdownSupported, i.qf) 34} 35 36func (d declaration) hover(ctx context.Context) (*documentation, error) { 37 obj := d.obj 38 switch node := d.node.(type) { 39 case *ast.GenDecl: 40 switch obj := obj.(type) { 41 case *types.TypeName, *types.Var, *types.Const, *types.Func: 42 return formatGenDecl(node, obj, obj.Type()) 43 } 44 case *ast.TypeSpec: 45 if obj.Parent() == types.Universe { 46 if obj.Name() == "error" { 47 return &documentation{node, nil}, nil 48 } 49 return &documentation{node.Name, nil}, nil // comments not needed for builtins 50 } 51 case *ast.FuncDecl: 52 switch obj.(type) { 53 case *types.Func: 54 return &documentation{obj, node.Doc}, nil 55 case *types.Builtin: 56 return &documentation{node.Type, node.Doc}, nil 57 } 58 } 59 return &documentation{obj, nil}, nil 60} 61 62func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*documentation, error) { 63 if _, ok := typ.(*types.Named); ok { 64 switch typ.Underlying().(type) { 65 case *types.Interface, *types.Struct: 66 return formatGenDecl(node, obj, typ.Underlying()) 67 } 68 } 69 var spec ast.Spec 70 for _, s := range node.Specs { 71 if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() { 72 spec = s 73 break 74 } 75 } 76 if spec == nil { 77 return nil, fmt.Errorf("no spec for node %v at position %v", node, obj.Pos()) 78 } 79 // If we have a field or method. 80 switch obj.(type) { 81 case *types.Var, *types.Const, *types.Func: 82 return formatVar(spec, obj) 83 } 84 // Handle types. 85 switch spec := spec.(type) { 86 case *ast.TypeSpec: 87 if len(node.Specs) > 1 { 88 // If multiple types are declared in the same block. 89 return &documentation{spec.Type, spec.Doc}, nil 90 } else { 91 return &documentation{spec, node.Doc}, nil 92 } 93 case *ast.ValueSpec: 94 return &documentation{spec, spec.Doc}, nil 95 case *ast.ImportSpec: 96 return &documentation{spec, spec.Doc}, nil 97 } 98 return nil, fmt.Errorf("unable to format spec %v (%T)", spec, spec) 99} 100 101func formatVar(node ast.Spec, obj types.Object) (*documentation, error) { 102 var fieldList *ast.FieldList 103 if spec, ok := node.(*ast.TypeSpec); ok { 104 switch t := spec.Type.(type) { 105 case *ast.StructType: 106 fieldList = t.Fields 107 case *ast.InterfaceType: 108 fieldList = t.Methods 109 } 110 } 111 // If we have a struct or interface declaration, 112 // we need to match the object to the corresponding field or method. 113 if fieldList != nil { 114 for i := 0; i < len(fieldList.List); i++ { 115 field := fieldList.List[i] 116 if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() { 117 if field.Doc.Text() != "" { 118 return &documentation{obj, field.Doc}, nil 119 } else if field.Comment.Text() != "" { 120 return &documentation{obj, field.Comment}, nil 121 } 122 } 123 } 124 } 125 // If we weren't able to find documentation for the object. 126 return &documentation{obj, nil}, nil 127} 128 129// writeHover writes the hover for a given node and its documentation. 130func writeHover(x interface{}, fset *token.FileSet, b *strings.Builder, c *ast.CommentGroup, markdownSupported bool, qf types.Qualifier) (string, error) { 131 if c != nil { 132 // TODO(rstambler): Improve conversion from Go docs to markdown. 133 b.WriteString(formatDocumentation(c)) 134 b.WriteRune('\n') 135 } 136 if markdownSupported { 137 b.WriteString("```go\n") 138 } 139 switch x := x.(type) { 140 case ast.Node: 141 if err := format.Node(b, fset, x); err != nil { 142 return "", err 143 } 144 case types.Object: 145 b.WriteString(types.ObjectString(x, qf)) 146 } 147 if markdownSupported { 148 b.WriteString("\n```") 149 } 150 return b.String(), nil 151} 152 153func formatDocumentation(c *ast.CommentGroup) string { 154 if c == nil { 155 return "" 156 } 157 return doc.Synopsis(c.Text()) 158} 159