github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/cache/symbols.go (about)

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cache
     6  
     7  import (
     8  	"context"
     9  	"go/ast"
    10  	"go/token"
    11  	"go/types"
    12  	"strings"
    13  
    14  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    15  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    16  	"github.com/jhump/golang-x-tools/internal/memoize"
    17  	"github.com/jhump/golang-x-tools/internal/span"
    18  )
    19  
    20  type symbolHandle struct {
    21  	handle *memoize.Handle
    22  
    23  	fh source.FileHandle
    24  
    25  	// key is the hashed key for the package.
    26  	key symbolHandleKey
    27  }
    28  
    29  // symbolData contains the data produced by extracting symbols from a file.
    30  type symbolData struct {
    31  	symbols []source.Symbol
    32  	err     error
    33  }
    34  
    35  type symbolHandleKey string
    36  
    37  func (s *snapshot) buildSymbolHandle(ctx context.Context, fh source.FileHandle) *symbolHandle {
    38  	if h := s.getSymbolHandle(fh.URI()); h != nil {
    39  		return h
    40  	}
    41  	key := symbolHandleKey(fh.FileIdentity().Hash)
    42  	h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
    43  		snapshot := arg.(*snapshot)
    44  		data := &symbolData{}
    45  		data.symbols, data.err = symbolize(ctx, snapshot, fh)
    46  		return data
    47  	}, nil)
    48  
    49  	sh := &symbolHandle{
    50  		handle: h,
    51  		fh:     fh,
    52  		key:    key,
    53  	}
    54  	return s.addSymbolHandle(sh)
    55  }
    56  
    57  // symbolize extracts symbols from a file. It does not parse the file through the cache.
    58  func symbolize(ctx context.Context, snapshot *snapshot, fh source.FileHandle) ([]source.Symbol, error) {
    59  	var w symbolWalker
    60  	fset := token.NewFileSet() // don't use snapshot.FileSet, as that would needlessly leak memory.
    61  	data := parseGo(ctx, fset, fh, source.ParseFull)
    62  	if data.parsed != nil && data.parsed.File != nil {
    63  		w.curFile = data.parsed
    64  		w.curURI = protocol.URIFromSpanURI(data.parsed.URI)
    65  		w.fileDecls(data.parsed.File.Decls)
    66  	}
    67  	return w.symbols, w.firstError
    68  }
    69  
    70  type symbolWalker struct {
    71  	curFile    *source.ParsedGoFile
    72  	pkgName    string
    73  	curURI     protocol.DocumentURI
    74  	symbols    []source.Symbol
    75  	firstError error
    76  }
    77  
    78  func (w *symbolWalker) atNode(node ast.Node, name string, kind protocol.SymbolKind, path ...*ast.Ident) {
    79  	var b strings.Builder
    80  	for _, ident := range path {
    81  		if ident != nil {
    82  			b.WriteString(ident.Name)
    83  			b.WriteString(".")
    84  		}
    85  	}
    86  	b.WriteString(name)
    87  
    88  	rng, err := fileRange(w.curFile, node.Pos(), node.End())
    89  	if err != nil {
    90  		w.error(err)
    91  		return
    92  	}
    93  	sym := source.Symbol{
    94  		Name:  b.String(),
    95  		Kind:  kind,
    96  		Range: rng,
    97  	}
    98  	w.symbols = append(w.symbols, sym)
    99  }
   100  
   101  func (w *symbolWalker) error(err error) {
   102  	if err != nil && w.firstError == nil {
   103  		w.firstError = err
   104  	}
   105  }
   106  
   107  func fileRange(pgf *source.ParsedGoFile, start, end token.Pos) (protocol.Range, error) {
   108  	s, err := span.FileSpan(pgf.Tok, pgf.Mapper.Converter, start, end)
   109  	if err != nil {
   110  		return protocol.Range{}, nil
   111  	}
   112  	return pgf.Mapper.Range(s)
   113  }
   114  
   115  func (w *symbolWalker) fileDecls(decls []ast.Decl) {
   116  	for _, decl := range decls {
   117  		switch decl := decl.(type) {
   118  		case *ast.FuncDecl:
   119  			kind := protocol.Function
   120  			var recv *ast.Ident
   121  			if decl.Recv.NumFields() > 0 {
   122  				kind = protocol.Method
   123  				recv = unpackRecv(decl.Recv.List[0].Type)
   124  			}
   125  			w.atNode(decl.Name, decl.Name.Name, kind, recv)
   126  		case *ast.GenDecl:
   127  			for _, spec := range decl.Specs {
   128  				switch spec := spec.(type) {
   129  				case *ast.TypeSpec:
   130  					kind := guessKind(spec)
   131  					w.atNode(spec.Name, spec.Name.Name, kind)
   132  					w.walkType(spec.Type, spec.Name)
   133  				case *ast.ValueSpec:
   134  					for _, name := range spec.Names {
   135  						kind := protocol.Variable
   136  						if decl.Tok == token.CONST {
   137  							kind = protocol.Constant
   138  						}
   139  						w.atNode(name, name.Name, kind)
   140  					}
   141  				}
   142  			}
   143  		}
   144  	}
   145  }
   146  
   147  func guessKind(spec *ast.TypeSpec) protocol.SymbolKind {
   148  	switch spec.Type.(type) {
   149  	case *ast.InterfaceType:
   150  		return protocol.Interface
   151  	case *ast.StructType:
   152  		return protocol.Struct
   153  	case *ast.FuncType:
   154  		return protocol.Function
   155  	}
   156  	return protocol.Class
   157  }
   158  
   159  func unpackRecv(rtyp ast.Expr) *ast.Ident {
   160  	// Extract the receiver identifier. Lifted from go/types/resolver.go
   161  L:
   162  	for {
   163  		switch t := rtyp.(type) {
   164  		case *ast.ParenExpr:
   165  			rtyp = t.X
   166  		case *ast.StarExpr:
   167  			rtyp = t.X
   168  		default:
   169  			break L
   170  		}
   171  	}
   172  	if name, _ := rtyp.(*ast.Ident); name != nil {
   173  		return name
   174  	}
   175  	return nil
   176  }
   177  
   178  // walkType processes symbols related to a type expression. path is path of
   179  // nested type identifiers to the type expression.
   180  func (w *symbolWalker) walkType(typ ast.Expr, path ...*ast.Ident) {
   181  	switch st := typ.(type) {
   182  	case *ast.StructType:
   183  		for _, field := range st.Fields.List {
   184  			w.walkField(field, protocol.Field, protocol.Field, path...)
   185  		}
   186  	case *ast.InterfaceType:
   187  		for _, field := range st.Methods.List {
   188  			w.walkField(field, protocol.Interface, protocol.Method, path...)
   189  		}
   190  	}
   191  }
   192  
   193  // walkField processes symbols related to the struct field or interface method.
   194  //
   195  // unnamedKind and namedKind are the symbol kinds if the field is resp. unnamed
   196  // or named. path is the path of nested identifiers containing the field.
   197  func (w *symbolWalker) walkField(field *ast.Field, unnamedKind, namedKind protocol.SymbolKind, path ...*ast.Ident) {
   198  	if len(field.Names) == 0 {
   199  		switch typ := field.Type.(type) {
   200  		case *ast.SelectorExpr:
   201  			// embedded qualified type
   202  			w.atNode(field, typ.Sel.Name, unnamedKind, path...)
   203  		default:
   204  			w.atNode(field, types.ExprString(field.Type), unnamedKind, path...)
   205  		}
   206  	}
   207  	for _, name := range field.Names {
   208  		w.atNode(name, name.Name, namedKind, path...)
   209  		w.walkType(field.Type, append(path, name)...)
   210  	}
   211  }