// 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/types" "strings" "golang.org/x/tools/internal/telemetry/trace" errors "golang.org/x/xerrors" ) type HoverInformation struct { // Signature is the symbol's signature. Signature string `json:"signature"` // SingleLine is a single line describing the symbol. // This is recommended only for use in clients that show a single line for hover. SingleLine string `json:"singleLine"` // Synopsis is a single sentence synopsis of the symbol's documentation. Synopsis string `json:"synopsis"` // FullDocumentation is the symbol's full documentation. FullDocumentation string `json:"fullDocumentation"` // pkgPath holds the package path of the hovered symbol. pkgPath string // symbolName holds the symbol name without any package prefix. symbolName string source interface{} comment *ast.CommentGroup } func (h *HoverInformation) DocumentationLink(options Options) string { if h.symbolName == "" || h.pkgPath == "" || options.LinkTarget == "" { return "" } return fmt.Sprintf("[%s on %s](https://%s/%s#%s)", h.symbolName, options.LinkTarget, options.LinkTarget, h.pkgPath, h.symbolName) } func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) { ctx, done := trace.StartSpan(ctx, "source.Hover") defer done() h, err := i.Declaration.hover(ctx) if err != nil { return nil, err } // Determine the symbol's signature. switch x := h.source.(type) { case ast.Node: var b strings.Builder if err := format.Node(&b, i.Snapshot.View().Session().Cache().FileSet(), x); err != nil { return nil, err } h.Signature = b.String() case types.Object: h.Signature = objectString(x, i.qf) } if obj := i.Declaration.obj; obj != nil { // Only show the documentation links for symbols in the package scope. // TODO(https://golang.org/issue/34240): Handle other symbols. if obj.Exported() && obj.Parent() == obj.Pkg().Scope() { h.pkgPath = obj.Pkg().Path() h.symbolName = obj.Name() } // Set the documentation. h.SingleLine = objectString(obj, i.qf) } if h.comment != nil { h.FullDocumentation = h.comment.Text() h.Synopsis = doc.Synopsis(h.FullDocumentation) } return h, nil } // objectString is a wrapper around the types.ObjectString function. // It handles adding more information to the object string. func objectString(obj types.Object, qf types.Qualifier) string { str := types.ObjectString(obj, qf) switch obj := obj.(type) { case *types.Const: str = fmt.Sprintf("%s = %s", str, obj.Val()) } return str } func (d Declaration) hover(ctx context.Context) (*HoverInformation, error) { _, done := trace.StartSpan(ctx, "source.hover") defer done() obj := d.obj switch node := d.node.(type) { case *ast.ImportSpec: return &HoverInformation{source: node}, nil case *ast.GenDecl: switch obj := obj.(type) { case *types.TypeName, *types.Var, *types.Const, *types.Func: return formatGenDecl(node, obj, obj.Type()) } case *ast.TypeSpec: if obj.Parent() == types.Universe { if obj.Name() == "error" { return &HoverInformation{source: node}, nil } return &HoverInformation{source: node.Name}, nil // comments not needed for builtins } case *ast.FuncDecl: switch obj.(type) { case *types.Func: return &HoverInformation{source: obj, comment: node.Doc}, nil case *types.Builtin: return &HoverInformation{source: node.Type, comment: node.Doc}, nil } } return &HoverInformation{source: obj}, nil } func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*HoverInformation, error) { if _, ok := typ.(*types.Named); ok { switch typ.Underlying().(type) { case *types.Interface, *types.Struct: return formatGenDecl(node, obj, typ.Underlying()) } } 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 nil, errors.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, node), nil } // Handle types. switch spec := spec.(type) { case *ast.TypeSpec: if len(node.Specs) > 1 { // If multiple types are declared in the same block. return &HoverInformation{source: spec.Type, comment: spec.Doc}, nil } else { return &HoverInformation{source: spec, comment: node.Doc}, nil } case *ast.ValueSpec: return &HoverInformation{source: spec, comment: spec.Doc}, nil case *ast.ImportSpec: return &HoverInformation{source: spec, comment: spec.Doc}, nil } return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec) } func formatVar(node ast.Spec, obj types.Object, decl *ast.GenDecl) *HoverInformation { 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 &HoverInformation{source: obj, comment: field.Doc} } else if field.Comment.Text() != "" { return &HoverInformation{source: obj, comment: field.Comment} } } } } // If we have a package level variable that does have a // comment group attached to it but not in the ast.spec. if decl.Doc.Text() != "" { return &HoverInformation{source: obj, comment: decl.Doc} } // If we weren't able to find documentation for the object. return &HoverInformation{source: obj} }