// 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.FileSet(), &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.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: switch obj.(type) { case *types.Func: return f(obj, node.Doc) case *types.Builtin: return f(node.Type, 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 }