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 }