| // 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 ( |
| "bytes" |
| "context" |
| "go/ast" |
| "go/format" |
| "go/token" |
| |
| "golang.org/x/tools/internal/span" |
| ) |
| |
| type SymbolKind int |
| |
| const ( |
| PackageSymbol SymbolKind = iota |
| StructSymbol |
| VariableSymbol |
| ConstantSymbol |
| FunctionSymbol |
| MethodSymbol |
| ) |
| |
| type Symbol struct { |
| Name string |
| Detail string |
| Span span.Span |
| Kind SymbolKind |
| Children []Symbol |
| } |
| |
| func DocumentSymbols(ctx context.Context, f File) []Symbol { |
| var symbols []Symbol |
| fset := f.GetFileSet(ctx) |
| astFile := f.GetAST(ctx) |
| for _, decl := range astFile.Decls { |
| switch decl := decl.(type) { |
| case *ast.FuncDecl: |
| symbols = append(symbols, funcSymbol(decl, fset)) |
| case *ast.GenDecl: |
| for _, spec := range decl.Specs { |
| switch spec := spec.(type) { |
| case *ast.ImportSpec: |
| symbols = append(symbols, importSymbol(spec, fset)) |
| case *ast.TypeSpec: |
| symbols = append(symbols, typeSymbol(spec, fset)) |
| case *ast.ValueSpec: |
| for _, name := range spec.Names { |
| symbols = append(symbols, varSymbol(decl, name, fset)) |
| } |
| } |
| } |
| } |
| } |
| return symbols |
| } |
| |
| func funcSymbol(decl *ast.FuncDecl, fset *token.FileSet) Symbol { |
| s := Symbol{ |
| Name: decl.Name.String(), |
| Kind: FunctionSymbol, |
| } |
| |
| if decl.Recv != nil { |
| s.Kind = MethodSymbol |
| } |
| |
| span, err := nodeSpan(decl, fset) |
| if err == nil { |
| s.Span = span |
| } |
| buf := &bytes.Buffer{} |
| if err := format.Node(buf, fset, decl); err == nil { |
| s.Detail = buf.String() |
| } |
| return s |
| } |
| |
| func importSymbol(spec *ast.ImportSpec, fset *token.FileSet) Symbol { |
| s := Symbol{ |
| Name: spec.Path.Value, |
| Kind: PackageSymbol, |
| Detail: "import " + spec.Path.Value, |
| } |
| span, err := nodeSpan(spec, fset) |
| if err == nil { |
| s.Span = span |
| } |
| return s |
| } |
| |
| func typeSymbol(spec *ast.TypeSpec, fset *token.FileSet) Symbol { |
| s := Symbol{ |
| Name: spec.Name.String(), |
| Kind: StructSymbol, |
| } |
| span, err := nodeSpan(spec, fset) |
| if err == nil { |
| s.Span = span |
| } |
| return s |
| } |
| |
| func varSymbol(decl *ast.GenDecl, name *ast.Ident, fset *token.FileSet) Symbol { |
| s := Symbol{ |
| Name: name.Name, |
| Kind: VariableSymbol, |
| } |
| |
| if decl.Tok == token.CONST { |
| s.Kind = ConstantSymbol |
| } |
| |
| span, err := nodeSpan(name, fset) |
| if err == nil { |
| s.Span = span |
| } |
| |
| return s |
| } |
| |
| func nodeSpan(n ast.Node, fset *token.FileSet) (span.Span, error) { |
| r := span.NewRange(fset, n.Pos(), n.End()) |
| return r.Span() |
| } |