mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
This uses the new opencensus compatability layer to add telementry to some of the functions in the lsp, in order to allow us to understand their costs and call patterns. Change-Id: I7df820cd4eace7a4840ac6397d5df402369bf0a7 Reviewed-on: https://go-review.googlesource.com/c/tools/+/183419 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
168 lines
4.2 KiB
Go
168 lines
4.2 KiB
Go
// 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/lsp/telemetry/trace"
|
|
)
|
|
|
|
type documentation struct {
|
|
source interface{}
|
|
comment *ast.CommentGroup
|
|
}
|
|
|
|
type HoverKind int
|
|
|
|
const (
|
|
NoDocumentation = HoverKind(iota)
|
|
SynopsisDocumentation
|
|
FullDocumentation
|
|
|
|
// TODO: Support a single-line hover mode for clients like Vim.
|
|
singleLine
|
|
)
|
|
|
|
func (i *IdentifierInfo) Hover(ctx context.Context, markdownSupported bool, hoverKind HoverKind) (string, error) {
|
|
ctx, ts := trace.StartSpan(ctx, "source.Hover")
|
|
defer ts.End()
|
|
h, err := i.decl.hover(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var b strings.Builder
|
|
if comment := formatDocumentation(hoverKind, h.comment); comment != "" {
|
|
b.WriteString(comment)
|
|
b.WriteRune('\n')
|
|
}
|
|
if markdownSupported {
|
|
b.WriteString("```go\n")
|
|
}
|
|
switch x := h.source.(type) {
|
|
case ast.Node:
|
|
if err := format.Node(&b, i.File.FileSet(), x); err != nil {
|
|
return "", err
|
|
}
|
|
case types.Object:
|
|
b.WriteString(types.ObjectString(x, i.qf))
|
|
}
|
|
if markdownSupported {
|
|
b.WriteString("\n```")
|
|
}
|
|
return b.String(), nil
|
|
}
|
|
|
|
func formatDocumentation(hoverKind HoverKind, c *ast.CommentGroup) string {
|
|
switch hoverKind {
|
|
case SynopsisDocumentation:
|
|
return doc.Synopsis((c.Text()))
|
|
case FullDocumentation:
|
|
return c.Text()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d declaration) hover(ctx context.Context) (*documentation, error) {
|
|
ctx, ts := trace.StartSpan(ctx, "source.hover")
|
|
defer ts.End()
|
|
obj := d.obj
|
|
switch node := d.node.(type) {
|
|
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 &documentation{node, nil}, nil
|
|
}
|
|
return &documentation{node.Name, nil}, nil // comments not needed for builtins
|
|
}
|
|
case *ast.FuncDecl:
|
|
switch obj.(type) {
|
|
case *types.Func:
|
|
return &documentation{obj, node.Doc}, nil
|
|
case *types.Builtin:
|
|
return &documentation{node.Type, node.Doc}, nil
|
|
}
|
|
}
|
|
return &documentation{obj, nil}, nil
|
|
}
|
|
|
|
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*documentation, 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, 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)
|
|
}
|
|
// Handle types.
|
|
switch spec := spec.(type) {
|
|
case *ast.TypeSpec:
|
|
if len(node.Specs) > 1 {
|
|
// If multiple types are declared in the same block.
|
|
return &documentation{spec.Type, spec.Doc}, nil
|
|
} else {
|
|
return &documentation{spec, node.Doc}, nil
|
|
}
|
|
case *ast.ValueSpec:
|
|
return &documentation{spec, spec.Doc}, nil
|
|
case *ast.ImportSpec:
|
|
return &documentation{spec, spec.Doc}, nil
|
|
}
|
|
return nil, fmt.Errorf("unable to format spec %v (%T)", spec, spec)
|
|
}
|
|
|
|
func formatVar(node ast.Spec, obj types.Object) (*documentation, 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 &documentation{obj, field.Doc}, nil
|
|
} else if field.Comment.Text() != "" {
|
|
return &documentation{obj, field.Comment}, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If we weren't able to find documentation for the object.
|
|
return &documentation{obj, nil}, nil
|
|
}
|