| // 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/gopls/internal/lsp/protocol" |
| "golang.org/x/tools/internal/event" |
| ) |
| |
| func DocumentSymbols(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.DocumentSymbol, error) { |
| ctx, done := event.Start(ctx, "source.DocumentSymbols") |
| defer done() |
| |
| pgf, err := snapshot.ParseGo(ctx, fh, ParseFull) |
| if err != nil { |
| return nil, fmt.Errorf("getting file for DocumentSymbols: %w", err) |
| } |
| |
| // Build symbols for file declarations. When encountering a declaration with |
| // errors (typically because positions are invalid), we skip the declaration |
| // entirely. VS Code fails to show any symbols if one of the top-level |
| // symbols is missing position information. |
| var symbols []protocol.DocumentSymbol |
| for _, decl := range pgf.File.Decls { |
| switch decl := decl.(type) { |
| case *ast.FuncDecl: |
| if decl.Name.Name == "_" { |
| continue |
| } |
| fs, err := funcSymbol(pgf.Mapper, pgf.Tok, decl) |
| if err == nil { |
| // If function is a method, prepend the type of the method. |
| if decl.Recv != nil && len(decl.Recv.List) > 0 { |
| fs.Name = fmt.Sprintf("(%s).%s", types.ExprString(decl.Recv.List[0].Type), fs.Name) |
| } |
| symbols = append(symbols, fs) |
| } |
| case *ast.GenDecl: |
| for _, spec := range decl.Specs { |
| switch spec := spec.(type) { |
| case *ast.TypeSpec: |
| if spec.Name.Name == "_" { |
| continue |
| } |
| ts, err := typeSymbol(pgf.Mapper, pgf.Tok, spec) |
| if err == nil { |
| symbols = append(symbols, ts) |
| } |
| case *ast.ValueSpec: |
| for _, name := range spec.Names { |
| if name.Name == "_" { |
| continue |
| } |
| vs, err := varSymbol(pgf.Mapper, pgf.Tok, spec, name, decl.Tok == token.CONST) |
| if err == nil { |
| symbols = append(symbols, vs) |
| } |
| } |
| } |
| } |
| } |
| } |
| return symbols, nil |
| } |
| |
| func funcSymbol(m *protocol.Mapper, tf *token.File, decl *ast.FuncDecl) (protocol.DocumentSymbol, error) { |
| s := protocol.DocumentSymbol{ |
| Name: decl.Name.Name, |
| Kind: protocol.Function, |
| } |
| if decl.Recv != nil { |
| s.Kind = protocol.Method |
| } |
| var err error |
| s.Range, err = m.NodeRange(tf, decl) |
| if err != nil { |
| return protocol.DocumentSymbol{}, err |
| } |
| s.SelectionRange, err = m.NodeRange(tf, decl.Name) |
| if err != nil { |
| return protocol.DocumentSymbol{}, err |
| } |
| s.Detail = types.ExprString(decl.Type) |
| return s, nil |
| } |
| |
| func typeSymbol(m *protocol.Mapper, tf *token.File, spec *ast.TypeSpec) (protocol.DocumentSymbol, error) { |
| s := protocol.DocumentSymbol{ |
| Name: spec.Name.Name, |
| } |
| var err error |
| s.Range, err = m.NodeRange(tf, spec) |
| if err != nil { |
| return protocol.DocumentSymbol{}, err |
| } |
| s.SelectionRange, err = m.NodeRange(tf, spec.Name) |
| if err != nil { |
| return protocol.DocumentSymbol{}, err |
| } |
| s.Kind, s.Detail, s.Children = typeDetails(m, tf, spec.Type) |
| return s, nil |
| } |
| |
| func typeDetails(m *protocol.Mapper, tf *token.File, typExpr ast.Expr) (kind protocol.SymbolKind, detail string, children []protocol.DocumentSymbol) { |
| switch typExpr := typExpr.(type) { |
| case *ast.StructType: |
| kind = protocol.Struct |
| children = fieldListSymbols(m, tf, typExpr.Fields, protocol.Field) |
| if len(children) > 0 { |
| detail = "struct{...}" |
| } else { |
| detail = "struct{}" |
| } |
| |
| // Find interface methods and embedded types. |
| case *ast.InterfaceType: |
| kind = protocol.Interface |
| children = fieldListSymbols(m, tf, typExpr.Methods, protocol.Method) |
| if len(children) > 0 { |
| detail = "interface{...}" |
| } else { |
| detail = "interface{}" |
| } |
| |
| case *ast.FuncType: |
| kind = protocol.Function |
| detail = types.ExprString(typExpr) |
| |
| default: |
| kind = protocol.Class // catch-all, for cases where we don't know the kind syntactically |
| detail = types.ExprString(typExpr) |
| } |
| return |
| } |
| |
| func fieldListSymbols(m *protocol.Mapper, tf *token.File, fields *ast.FieldList, fieldKind protocol.SymbolKind) []protocol.DocumentSymbol { |
| if fields == nil { |
| return nil |
| } |
| |
| var symbols []protocol.DocumentSymbol |
| for _, field := range fields.List { |
| detail, children := "", []protocol.DocumentSymbol(nil) |
| if field.Type != nil { |
| _, detail, children = typeDetails(m, tf, field.Type) |
| } |
| if len(field.Names) == 0 { // embedded interface or struct field |
| // By default, use the formatted type details as the name of this field. |
| // This handles potentially invalid syntax, as well as type embeddings in |
| // interfaces. |
| child := protocol.DocumentSymbol{ |
| Name: detail, |
| Kind: protocol.Field, // consider all embeddings to be fields |
| Children: children, |
| } |
| |
| // If the field is a valid embedding, promote the type name to field |
| // name. |
| selection := field.Type |
| if id := embeddedIdent(field.Type); id != nil { |
| child.Name = id.Name |
| child.Detail = detail |
| selection = id |
| } |
| |
| if rng, err := m.NodeRange(tf, field.Type); err == nil { |
| child.Range = rng |
| } |
| if rng, err := m.NodeRange(tf, selection); err == nil { |
| child.SelectionRange = rng |
| } |
| |
| symbols = append(symbols, child) |
| } else { |
| for _, name := range field.Names { |
| child := protocol.DocumentSymbol{ |
| Name: name.Name, |
| Kind: fieldKind, |
| Detail: detail, |
| Children: children, |
| } |
| |
| if rng, err := m.NodeRange(tf, field); err == nil { |
| child.Range = rng |
| } |
| if rng, err := m.NodeRange(tf, name); err == nil { |
| child.SelectionRange = rng |
| } |
| |
| symbols = append(symbols, child) |
| } |
| } |
| |
| } |
| return symbols |
| } |
| |
| func varSymbol(m *protocol.Mapper, tf *token.File, spec *ast.ValueSpec, name *ast.Ident, isConst bool) (protocol.DocumentSymbol, error) { |
| s := protocol.DocumentSymbol{ |
| Name: name.Name, |
| Kind: protocol.Variable, |
| } |
| if isConst { |
| s.Kind = protocol.Constant |
| } |
| var err error |
| s.Range, err = m.NodeRange(tf, spec) |
| if err != nil { |
| return protocol.DocumentSymbol{}, err |
| } |
| s.SelectionRange, err = m.NodeRange(tf, name) |
| if err != nil { |
| return protocol.DocumentSymbol{}, err |
| } |
| if spec.Type != nil { // type may be missing from the syntax |
| _, s.Detail, s.Children = typeDetails(m, tf, spec.Type) |
| } |
| return s, nil |
| } |