| // Copyright 2019 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/doc" |
| "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, markdownSupported, wantComments 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) { |
| if !wantComments { |
| c = nil |
| } |
| return writeHover(x, i.File.GetFileSet(ctx), &b, c, markdownSupported, qf) |
| } |
| obj := i.Declaration.Object |
| 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 len(node.Specs) > 1 { |
| // If multiple types are declared in the same block. |
| 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 < len(fieldList.List); 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 { |
| // TODO(rstambler): Improve conversion from Go docs to markdown. |
| b.WriteString(doc.Synopsis(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 |
| } |