mirror of
https://github.com/golang/go
synced 2024-11-19 02:04:42 -07:00
156eb2ae29
This is a straight move of some code with no changes. It splits the part of the telemetry code that will become a standalone library from the bit that belongs in the lsp. Change-Id: Icedb6bf1f3711da9251450531729984df6df7787 Reviewed-on: https://go-review.googlesource.com/c/tools/+/190403 Run-TryBot: Ian Cottrell <iancottrell@google.com> Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
313 lines
7.5 KiB
Go
313 lines
7.5 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/token"
|
|
"go/types"
|
|
|
|
"golang.org/x/tools/internal/span"
|
|
"golang.org/x/tools/internal/telemetry/trace"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
type SymbolKind int
|
|
|
|
const (
|
|
PackageSymbol SymbolKind = iota
|
|
StructSymbol
|
|
VariableSymbol
|
|
ConstantSymbol
|
|
FunctionSymbol
|
|
MethodSymbol
|
|
InterfaceSymbol
|
|
NumberSymbol
|
|
StringSymbol
|
|
BooleanSymbol
|
|
FieldSymbol
|
|
)
|
|
|
|
type Symbol struct {
|
|
Name string
|
|
Detail string
|
|
Span span.Span
|
|
SelectionSpan span.Span
|
|
Kind SymbolKind
|
|
Children []Symbol
|
|
}
|
|
|
|
func DocumentSymbols(ctx context.Context, f GoFile) ([]Symbol, error) {
|
|
ctx, done := trace.StartSpan(ctx, "source.DocumentSymbols")
|
|
defer done()
|
|
|
|
fset := f.FileSet()
|
|
file, err := f.GetAST(ctx, ParseFull)
|
|
if file == nil {
|
|
return nil, err
|
|
}
|
|
pkg, err := f.GetPackage(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
info := pkg.GetTypesInfo()
|
|
q := qualifier(file, pkg.GetTypes(), info)
|
|
|
|
methodsToReceiver := make(map[types.Type][]Symbol)
|
|
symbolsToReceiver := make(map[types.Type]int)
|
|
var symbols []Symbol
|
|
for _, decl := range file.Decls {
|
|
switch decl := decl.(type) {
|
|
case *ast.FuncDecl:
|
|
if obj := info.ObjectOf(decl.Name); obj != nil {
|
|
if fs := funcSymbol(decl, obj, fset, q); fs.Kind == MethodSymbol {
|
|
// Store methods separately, as we want them to appear as children
|
|
// of the corresponding type (which we may not have seen yet).
|
|
rtype := obj.Type().(*types.Signature).Recv().Type()
|
|
methodsToReceiver[rtype] = append(methodsToReceiver[rtype], fs)
|
|
} else {
|
|
symbols = append(symbols, fs)
|
|
}
|
|
}
|
|
case *ast.GenDecl:
|
|
for _, spec := range decl.Specs {
|
|
switch spec := spec.(type) {
|
|
case *ast.TypeSpec:
|
|
if obj := info.ObjectOf(spec.Name); obj != nil {
|
|
ts := typeSymbol(info, spec, obj, fset, q)
|
|
symbols = append(symbols, ts)
|
|
symbolsToReceiver[obj.Type()] = len(symbols) - 1
|
|
}
|
|
case *ast.ValueSpec:
|
|
for _, name := range spec.Names {
|
|
if obj := info.ObjectOf(name); obj != nil {
|
|
symbols = append(symbols, varSymbol(decl, name, obj, fset, q))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Attempt to associate methods to the corresponding type symbol.
|
|
for typ, methods := range methodsToReceiver {
|
|
if ptr, ok := typ.(*types.Pointer); ok {
|
|
typ = ptr.Elem()
|
|
}
|
|
|
|
if i, ok := symbolsToReceiver[typ]; ok {
|
|
symbols[i].Children = append(symbols[i].Children, methods...)
|
|
} else {
|
|
// The type definition for the receiver of these methods was not in the document.
|
|
symbols = append(symbols, methods...)
|
|
}
|
|
}
|
|
return symbols, nil
|
|
}
|
|
|
|
func funcSymbol(decl *ast.FuncDecl, obj types.Object, fset *token.FileSet, q types.Qualifier) Symbol {
|
|
s := Symbol{
|
|
Name: obj.Name(),
|
|
Kind: FunctionSymbol,
|
|
}
|
|
if span, err := nodeSpan(decl, fset); err == nil {
|
|
s.Span = span
|
|
}
|
|
if span, err := nodeSpan(decl.Name, fset); err == nil {
|
|
s.SelectionSpan = span
|
|
}
|
|
sig, _ := obj.Type().(*types.Signature)
|
|
if sig != nil {
|
|
if sig.Recv() != nil {
|
|
s.Kind = MethodSymbol
|
|
}
|
|
s.Detail += "("
|
|
for i := 0; i < sig.Params().Len(); i++ {
|
|
if i > 0 {
|
|
s.Detail += ", "
|
|
}
|
|
param := sig.Params().At(i)
|
|
label := types.TypeString(param.Type(), q)
|
|
if param.Name() != "" {
|
|
label = fmt.Sprintf("%s %s", param.Name(), label)
|
|
}
|
|
s.Detail += label
|
|
}
|
|
s.Detail += ")"
|
|
}
|
|
return s
|
|
}
|
|
|
|
func setKind(s *Symbol, typ types.Type, q types.Qualifier) {
|
|
switch typ := typ.Underlying().(type) {
|
|
case *types.Interface:
|
|
s.Kind = InterfaceSymbol
|
|
case *types.Struct:
|
|
s.Kind = StructSymbol
|
|
case *types.Signature:
|
|
s.Kind = FunctionSymbol
|
|
if typ.Recv() != nil {
|
|
s.Kind = MethodSymbol
|
|
}
|
|
case *types.Named:
|
|
setKind(s, typ.Underlying(), q)
|
|
case *types.Basic:
|
|
i := typ.Info()
|
|
switch {
|
|
case i&types.IsNumeric != 0:
|
|
s.Kind = NumberSymbol
|
|
case i&types.IsBoolean != 0:
|
|
s.Kind = BooleanSymbol
|
|
case i&types.IsString != 0:
|
|
s.Kind = StringSymbol
|
|
}
|
|
default:
|
|
s.Kind = VariableSymbol
|
|
}
|
|
}
|
|
|
|
func typeSymbol(info *types.Info, spec *ast.TypeSpec, obj types.Object, fset *token.FileSet, q types.Qualifier) Symbol {
|
|
s := Symbol{Name: obj.Name()}
|
|
s.Detail, _ = formatType(obj.Type(), q)
|
|
setKind(&s, obj.Type(), q)
|
|
|
|
if span, err := nodeSpan(spec, fset); err == nil {
|
|
s.Span = span
|
|
}
|
|
if span, err := nodeSpan(spec.Name, fset); err == nil {
|
|
s.SelectionSpan = span
|
|
}
|
|
|
|
t, objIsStruct := obj.Type().Underlying().(*types.Struct)
|
|
st, specIsStruct := spec.Type.(*ast.StructType)
|
|
if objIsStruct && specIsStruct {
|
|
for i := 0; i < t.NumFields(); i++ {
|
|
f := t.Field(i)
|
|
child := Symbol{Name: f.Name(), Kind: FieldSymbol}
|
|
child.Detail, _ = formatType(f.Type(), q)
|
|
|
|
spanNode, selectionNode := nodesForStructField(i, st)
|
|
if span, err := nodeSpan(spanNode, fset); err == nil {
|
|
child.Span = span
|
|
}
|
|
if span, err := nodeSpan(selectionNode, fset); err == nil {
|
|
child.SelectionSpan = span
|
|
}
|
|
|
|
s.Children = append(s.Children, child)
|
|
}
|
|
}
|
|
|
|
ti, objIsInterface := obj.Type().Underlying().(*types.Interface)
|
|
ai, specIsInterface := spec.Type.(*ast.InterfaceType)
|
|
if objIsInterface && specIsInterface {
|
|
for i := 0; i < ti.NumExplicitMethods(); i++ {
|
|
method := ti.ExplicitMethod(i)
|
|
child := Symbol{
|
|
Name: method.Name(),
|
|
Kind: MethodSymbol,
|
|
}
|
|
|
|
var spanNode, selectionNode ast.Node
|
|
Methods:
|
|
for _, f := range ai.Methods.List {
|
|
for _, id := range f.Names {
|
|
if id.Name == method.Name() {
|
|
spanNode, selectionNode = f, id
|
|
break Methods
|
|
}
|
|
}
|
|
}
|
|
if span, err := nodeSpan(spanNode, fset); err == nil {
|
|
child.Span = span
|
|
}
|
|
if span, err := nodeSpan(selectionNode, fset); err == nil {
|
|
child.SelectionSpan = span
|
|
}
|
|
s.Children = append(s.Children, child)
|
|
}
|
|
|
|
for i := 0; i < ti.NumEmbeddeds(); i++ {
|
|
embedded := ti.EmbeddedType(i)
|
|
nt, isNamed := embedded.(*types.Named)
|
|
if !isNamed {
|
|
continue
|
|
}
|
|
|
|
child := Symbol{Name: types.TypeString(embedded, q)}
|
|
setKind(&child, embedded, q)
|
|
var spanNode, selectionNode ast.Node
|
|
Embeddeds:
|
|
for _, f := range ai.Methods.List {
|
|
if len(f.Names) > 0 {
|
|
continue
|
|
}
|
|
|
|
if t := info.TypeOf(f.Type); types.Identical(nt, t) {
|
|
spanNode, selectionNode = f, f.Type
|
|
break Embeddeds
|
|
}
|
|
}
|
|
|
|
if span, err := nodeSpan(spanNode, fset); err == nil {
|
|
child.Span = span
|
|
}
|
|
if span, err := nodeSpan(selectionNode, fset); err == nil {
|
|
child.SelectionSpan = span
|
|
}
|
|
s.Children = append(s.Children, child)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func nodesForStructField(i int, st *ast.StructType) (span, selection ast.Node) {
|
|
j := 0
|
|
for _, field := range st.Fields.List {
|
|
if len(field.Names) == 0 {
|
|
if i == j {
|
|
return field, field.Type
|
|
}
|
|
j++
|
|
continue
|
|
}
|
|
for _, name := range field.Names {
|
|
if i == j {
|
|
return field, name
|
|
}
|
|
j++
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func varSymbol(decl ast.Node, name *ast.Ident, obj types.Object, fset *token.FileSet, q types.Qualifier) Symbol {
|
|
s := Symbol{
|
|
Name: obj.Name(),
|
|
Kind: VariableSymbol,
|
|
}
|
|
if _, ok := obj.(*types.Const); ok {
|
|
s.Kind = ConstantSymbol
|
|
}
|
|
if span, err := nodeSpan(decl, fset); err == nil {
|
|
s.Span = span
|
|
}
|
|
if span, err := nodeSpan(name, fset); err == nil {
|
|
s.SelectionSpan = span
|
|
}
|
|
s.Detail = types.TypeString(obj.Type(), q)
|
|
return s
|
|
}
|
|
|
|
func nodeSpan(n ast.Node, fset *token.FileSet) (span.Span, error) {
|
|
if n == nil {
|
|
return span.Span{}, errors.New("no span for nil node")
|
|
}
|
|
r := span.NewRange(fset, n.Pos(), n.End())
|
|
return r.Span()
|
|
}
|