golang.org/x/tools/gopls@v0.15.3/internal/golang/util.go (about) 1 // Copyright 2019 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 golang 6 7 import ( 8 "context" 9 "go/ast" 10 "go/printer" 11 "go/token" 12 "go/types" 13 "regexp" 14 "strings" 15 16 "golang.org/x/tools/gopls/internal/cache" 17 "golang.org/x/tools/gopls/internal/cache/metadata" 18 "golang.org/x/tools/gopls/internal/protocol" 19 "golang.org/x/tools/gopls/internal/util/astutil" 20 "golang.org/x/tools/gopls/internal/util/bug" 21 "golang.org/x/tools/gopls/internal/util/safetoken" 22 "golang.org/x/tools/internal/tokeninternal" 23 ) 24 25 // IsGenerated gets and reads the file denoted by uri and reports 26 // whether it contains a "generated file" comment as described at 27 // https://golang.org/s/generatedcode. 28 // 29 // TODO(adonovan): opt: this function does too much. 30 // Move snapshot.ReadFile into the caller (most of which have already done it). 31 func IsGenerated(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) bool { 32 fh, err := snapshot.ReadFile(ctx, uri) 33 if err != nil { 34 return false 35 } 36 pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader) 37 if err != nil { 38 return false 39 } 40 for _, commentGroup := range pgf.File.Comments { 41 for _, comment := range commentGroup.List { 42 if matched := generatedRx.MatchString(comment.Text); matched { 43 // Check if comment is at the beginning of the line in source. 44 if safetoken.Position(pgf.Tok, comment.Slash).Column == 1 { 45 return true 46 } 47 } 48 } 49 } 50 return false 51 } 52 53 // adjustedObjEnd returns the end position of obj, possibly modified for 54 // package names. 55 // 56 // TODO(rfindley): eliminate this function, by inlining it at callsites where 57 // it makes sense. 58 func adjustedObjEnd(obj types.Object) token.Pos { 59 nameLen := len(obj.Name()) 60 if pkgName, ok := obj.(*types.PkgName); ok { 61 // An imported Go package has a package-local, unqualified name. 62 // When the name matches the imported package name, there is no 63 // identifier in the import spec with the local package name. 64 // 65 // For example: 66 // import "go/ast" // name "ast" matches package name 67 // import a "go/ast" // name "a" does not match package name 68 // 69 // When the identifier does not appear in the source, have the range 70 // of the object be the import path, including quotes. 71 if pkgName.Imported().Name() == pkgName.Name() { 72 nameLen = len(pkgName.Imported().Path()) + len(`""`) 73 } 74 } 75 return obj.Pos() + token.Pos(nameLen) 76 } 77 78 // Matches cgo generated comment as well as the proposed standard: 79 // 80 // https://golang.org/s/generatedcode 81 var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`) 82 83 // nodeAtPos returns the index and the node whose position is contained inside 84 // the node list. 85 func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) { 86 if nodes == nil { 87 return nil, -1 88 } 89 for i, node := range nodes { 90 if node.Pos() <= pos && pos <= node.End() { 91 return node, i 92 } 93 } 94 return nil, -1 95 } 96 97 // FormatNode returns the "pretty-print" output for an ast node. 98 func FormatNode(fset *token.FileSet, n ast.Node) string { 99 var buf strings.Builder 100 if err := printer.Fprint(&buf, fset, n); err != nil { 101 // TODO(rfindley): we should use bug.Reportf here. 102 // We encounter this during completion.resolveInvalid. 103 return "" 104 } 105 return buf.String() 106 } 107 108 // FormatNodeFile is like FormatNode, but requires only the token.File for the 109 // syntax containing the given ast node. 110 func FormatNodeFile(file *token.File, n ast.Node) string { 111 fset := tokeninternal.FileSetFor(file) 112 return FormatNode(fset, n) 113 } 114 115 // Deref returns a pointer's element type, traversing as many levels as needed. 116 // Otherwise it returns typ. 117 // 118 // It can return a pointer type for cyclic types (see golang/go#45510). 119 func Deref(typ types.Type) types.Type { 120 var seen map[types.Type]struct{} 121 for { 122 p, ok := typ.Underlying().(*types.Pointer) 123 if !ok { 124 return typ 125 } 126 if _, ok := seen[p.Elem()]; ok { 127 return typ 128 } 129 130 typ = p.Elem() 131 132 if seen == nil { 133 seen = make(map[types.Type]struct{}) 134 } 135 seen[typ] = struct{}{} 136 } 137 } 138 139 // findFileInDeps finds package metadata containing URI in the transitive 140 // dependencies of m. When using the Go command, the answer is unique. 141 func findFileInDeps(s metadata.Source, mp *metadata.Package, uri protocol.DocumentURI) *metadata.Package { 142 seen := make(map[PackageID]bool) 143 var search func(*metadata.Package) *metadata.Package 144 search = func(mp *metadata.Package) *metadata.Package { 145 if seen[mp.ID] { 146 return nil 147 } 148 seen[mp.ID] = true 149 for _, cgf := range mp.CompiledGoFiles { 150 if cgf == uri { 151 return mp 152 } 153 } 154 for _, dep := range mp.DepsByPkgPath { 155 mp := s.Metadata(dep) 156 if mp == nil { 157 bug.Reportf("nil metadata for %q", dep) 158 continue 159 } 160 if found := search(mp); found != nil { 161 return found 162 } 163 } 164 return nil 165 } 166 return search(mp) 167 } 168 169 // CollectScopes returns all scopes in an ast path, ordered as innermost scope 170 // first. 171 func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope { 172 // scopes[i], where i<len(path), is the possibly nil Scope of path[i]. 173 var scopes []*types.Scope 174 for _, n := range path { 175 // Include *FuncType scope if pos is inside the function body. 176 switch node := n.(type) { 177 case *ast.FuncDecl: 178 if node.Body != nil && astutil.NodeContains(node.Body, pos) { 179 n = node.Type 180 } 181 case *ast.FuncLit: 182 if node.Body != nil && astutil.NodeContains(node.Body, pos) { 183 n = node.Type 184 } 185 } 186 scopes = append(scopes, info.Scopes[n]) 187 } 188 return scopes 189 } 190 191 // requalifier returns a function that re-qualifies identifiers and qualified 192 // identifiers contained in targetFile using the given metadata qualifier. 193 func requalifier(s metadata.Source, targetFile *ast.File, targetMeta *metadata.Package, mq MetadataQualifier) func(string) string { 194 qm := map[string]string{ 195 "": mq(targetMeta.Name, "", targetMeta.PkgPath), 196 } 197 198 // Construct mapping of import paths to their defined or implicit names. 199 for _, imp := range targetFile.Imports { 200 name, pkgName, impPath, pkgPath := importInfo(s, imp, targetMeta) 201 202 // Re-map the target name for the source file. 203 qm[name] = mq(pkgName, impPath, pkgPath) 204 } 205 206 return func(name string) string { 207 if newName, ok := qm[name]; ok { 208 return newName 209 } 210 return name 211 } 212 } 213 214 // A MetadataQualifier is a function that qualifies an identifier declared in a 215 // package with the given package name, import path, and package path. 216 // 217 // In scenarios where metadata is missing the provided PackageName and 218 // PackagePath may be empty, but ImportPath must always be non-empty. 219 type MetadataQualifier func(PackageName, ImportPath, PackagePath) string 220 221 // MetadataQualifierForFile returns a metadata qualifier that chooses the best 222 // qualification of an imported package relative to the file f in package with 223 // metadata m. 224 func MetadataQualifierForFile(s metadata.Source, f *ast.File, mp *metadata.Package) MetadataQualifier { 225 // Record local names for import paths. 226 localNames := make(map[ImportPath]string) // local names for imports in f 227 for _, imp := range f.Imports { 228 name, _, impPath, _ := importInfo(s, imp, mp) 229 localNames[impPath] = name 230 } 231 232 // Record a package path -> import path mapping. 233 inverseDeps := make(map[PackageID]PackagePath) 234 for path, id := range mp.DepsByPkgPath { 235 inverseDeps[id] = path 236 } 237 importsByPkgPath := make(map[PackagePath]ImportPath) // best import paths by pkgPath 238 for impPath, id := range mp.DepsByImpPath { 239 if id == "" { 240 continue 241 } 242 pkgPath := inverseDeps[id] 243 _, hasPath := importsByPkgPath[pkgPath] 244 _, hasImp := localNames[impPath] 245 // In rare cases, there may be multiple import paths with the same package 246 // path. In such scenarios, prefer an import path that already exists in 247 // the file. 248 if !hasPath || hasImp { 249 importsByPkgPath[pkgPath] = impPath 250 } 251 } 252 253 return func(pkgName PackageName, impPath ImportPath, pkgPath PackagePath) string { 254 // If supplied, translate the package path to an import path in the source 255 // package. 256 if pkgPath != "" { 257 if srcImp := importsByPkgPath[pkgPath]; srcImp != "" { 258 impPath = srcImp 259 } 260 if pkgPath == mp.PkgPath { 261 return "" 262 } 263 } 264 if localName, ok := localNames[impPath]; ok && impPath != "" { 265 return localName 266 } 267 if pkgName != "" { 268 return string(pkgName) 269 } 270 idx := strings.LastIndexByte(string(impPath), '/') 271 return string(impPath[idx+1:]) 272 } 273 } 274 275 // importInfo collects information about the import specified by imp, 276 // extracting its file-local name, package name, import path, and package path. 277 // 278 // If metadata is missing for the import, the resulting package name and 279 // package path may be empty, and the file local name may be guessed based on 280 // the import path. 281 // 282 // Note: previous versions of this helper used a PackageID->PackagePath map 283 // extracted from m, for extracting package path even in the case where 284 // metadata for a dep was missing. This should not be necessary, as we should 285 // always have metadata for IDs contained in DepsByPkgPath. 286 func importInfo(s metadata.Source, imp *ast.ImportSpec, mp *metadata.Package) (string, PackageName, ImportPath, PackagePath) { 287 var ( 288 name string // local name 289 pkgName PackageName 290 impPath = metadata.UnquoteImportPath(imp) 291 pkgPath PackagePath 292 ) 293 294 // If the import has a local name, use it. 295 if imp.Name != nil { 296 name = imp.Name.Name 297 } 298 299 // Try to find metadata for the import. If successful and there is no local 300 // name, the package name is the local name. 301 if depID := mp.DepsByImpPath[impPath]; depID != "" { 302 if depMP := s.Metadata(depID); depMP != nil { 303 if name == "" { 304 name = string(depMP.Name) 305 } 306 pkgName = depMP.Name 307 pkgPath = depMP.PkgPath 308 } 309 } 310 311 // If the local name is still unknown, guess it based on the import path. 312 if name == "" { 313 idx := strings.LastIndexByte(string(impPath), '/') 314 name = string(impPath[idx+1:]) 315 } 316 return name, pkgName, impPath, pkgPath 317 } 318 319 // isDirective reports whether c is a comment directive. 320 // 321 // Copied and adapted from go/src/go/ast/ast.go. 322 func isDirective(c string) bool { 323 if len(c) < 3 { 324 return false 325 } 326 if c[1] != '/' { 327 return false 328 } 329 //-style comment (no newline at the end) 330 c = c[2:] 331 if len(c) == 0 { 332 // empty line 333 return false 334 } 335 // "//line " is a line directive. 336 // (The // has been removed.) 337 if strings.HasPrefix(c, "line ") { 338 return true 339 } 340 341 // "//[a-z0-9]+:[a-z0-9]" 342 // (The // has been removed.) 343 colon := strings.Index(c, ":") 344 if colon <= 0 || colon+1 >= len(c) { 345 return false 346 } 347 for i := 0; i <= colon+1; i++ { 348 if i == colon { 349 continue 350 } 351 b := c[i] 352 if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { 353 return false 354 } 355 } 356 return true 357 } 358 359 // embeddedIdent returns the type name identifier for an embedding x, if x in a 360 // valid embedding. Otherwise, it returns nil. 361 // 362 // Spec: An embedded field must be specified as a type name T or as a pointer 363 // to a non-interface type name *T 364 func embeddedIdent(x ast.Expr) *ast.Ident { 365 if star, ok := x.(*ast.StarExpr); ok { 366 x = star.X 367 } 368 switch ix := x.(type) { // check for instantiated receivers 369 case *ast.IndexExpr: 370 x = ix.X 371 case *ast.IndexListExpr: 372 x = ix.X 373 } 374 switch x := x.(type) { 375 case *ast.Ident: 376 return x 377 case *ast.SelectorExpr: 378 if _, ok := x.X.(*ast.Ident); ok { 379 return x.Sel 380 } 381 } 382 return nil 383 } 384 385 // An importFunc is an implementation of the single-method 386 // types.Importer interface based on a function value. 387 type ImporterFunc func(path string) (*types.Package, error) 388 389 func (f ImporterFunc) Import(path string) (*types.Package, error) { return f(path) }