github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/source/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 source 6 7 import ( 8 "context" 9 "fmt" 10 "go/ast" 11 "go/printer" 12 "go/token" 13 "go/types" 14 "path/filepath" 15 "regexp" 16 "sort" 17 "strconv" 18 "strings" 19 20 "golang.org/x/mod/modfile" 21 "github.com/powerman/golang-tools/internal/lsp/protocol" 22 "github.com/powerman/golang-tools/internal/span" 23 errors "golang.org/x/xerrors" 24 ) 25 26 // MappedRange provides mapped protocol.Range for a span.Range, accounting for 27 // UTF-16 code points. 28 type MappedRange struct { 29 spanRange span.Range 30 m *protocol.ColumnMapper 31 32 // protocolRange is the result of converting the spanRange using the mapper. 33 // It is computed on-demand. 34 protocolRange *protocol.Range 35 } 36 37 // NewMappedRange returns a MappedRange for the given start and end token.Pos. 38 func NewMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end token.Pos) MappedRange { 39 return MappedRange{ 40 spanRange: span.Range{ 41 FileSet: fset, 42 Start: start, 43 End: end, 44 Converter: m.Converter, 45 }, 46 m: m, 47 } 48 } 49 50 func (s MappedRange) Range() (protocol.Range, error) { 51 if s.protocolRange == nil { 52 spn, err := s.spanRange.Span() 53 if err != nil { 54 return protocol.Range{}, err 55 } 56 prng, err := s.m.Range(spn) 57 if err != nil { 58 return protocol.Range{}, err 59 } 60 s.protocolRange = &prng 61 } 62 return *s.protocolRange, nil 63 } 64 65 func (s MappedRange) Span() (span.Span, error) { 66 return s.spanRange.Span() 67 } 68 69 func (s MappedRange) SpanRange() span.Range { 70 return s.spanRange 71 } 72 73 func (s MappedRange) URI() span.URI { 74 return s.m.URI 75 } 76 77 // GetParsedFile is a convenience function that extracts the Package and 78 // ParsedGoFile for a file in a Snapshot. pkgPolicy is one of NarrowestPackage/ 79 // WidestPackage. 80 func GetParsedFile(ctx context.Context, snapshot Snapshot, fh FileHandle, pkgPolicy PackageFilter) (Package, *ParsedGoFile, error) { 81 pkg, err := snapshot.PackageForFile(ctx, fh.URI(), TypecheckWorkspace, pkgPolicy) 82 if err != nil { 83 return nil, nil, err 84 } 85 pgh, err := pkg.File(fh.URI()) 86 return pkg, pgh, err 87 } 88 89 func IsGenerated(ctx context.Context, snapshot Snapshot, uri span.URI) bool { 90 fh, err := snapshot.GetFile(ctx, uri) 91 if err != nil { 92 return false 93 } 94 pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader) 95 if err != nil { 96 return false 97 } 98 tok := snapshot.FileSet().File(pgf.File.Pos()) 99 if tok == nil { 100 return false 101 } 102 for _, commentGroup := range pgf.File.Comments { 103 for _, comment := range commentGroup.List { 104 if matched := generatedRx.MatchString(comment.Text); matched { 105 // Check if comment is at the beginning of the line in source. 106 if pos := tok.Position(comment.Slash); pos.Column == 1 { 107 return true 108 } 109 } 110 } 111 } 112 return false 113 } 114 115 func nodeToProtocolRange(snapshot Snapshot, pkg Package, n ast.Node) (protocol.Range, error) { 116 mrng, err := posToMappedRange(snapshot, pkg, n.Pos(), n.End()) 117 if err != nil { 118 return protocol.Range{}, err 119 } 120 return mrng.Range() 121 } 122 123 func objToMappedRange(snapshot Snapshot, pkg Package, obj types.Object) (MappedRange, error) { 124 if pkgName, ok := obj.(*types.PkgName); ok { 125 // An imported Go package has a package-local, unqualified name. 126 // When the name matches the imported package name, there is no 127 // identifier in the import spec with the local package name. 128 // 129 // For example: 130 // import "go/ast" // name "ast" matches package name 131 // import a "go/ast" // name "a" does not match package name 132 // 133 // When the identifier does not appear in the source, have the range 134 // of the object be the import path, including quotes. 135 if pkgName.Imported().Name() == pkgName.Name() { 136 return posToMappedRange(snapshot, pkg, obj.Pos(), obj.Pos()+token.Pos(len(pkgName.Imported().Path())+2)) 137 } 138 } 139 return nameToMappedRange(snapshot, pkg, obj.Pos(), obj.Name()) 140 } 141 142 func nameToMappedRange(snapshot Snapshot, pkg Package, pos token.Pos, name string) (MappedRange, error) { 143 return posToMappedRange(snapshot, pkg, pos, pos+token.Pos(len(name))) 144 } 145 146 func posToMappedRange(snapshot Snapshot, pkg Package, pos, end token.Pos) (MappedRange, error) { 147 logicalFilename := snapshot.FileSet().File(pos).Position(pos).Filename 148 pgf, _, err := findFileInDeps(pkg, span.URIFromPath(logicalFilename)) 149 if err != nil { 150 return MappedRange{}, err 151 } 152 if !pos.IsValid() { 153 return MappedRange{}, errors.Errorf("invalid position for %v", pos) 154 } 155 if !end.IsValid() { 156 return MappedRange{}, errors.Errorf("invalid position for %v", end) 157 } 158 return NewMappedRange(snapshot.FileSet(), pgf.Mapper, pos, end), nil 159 } 160 161 // Matches cgo generated comment as well as the proposed standard: 162 // https://golang.org/s/generatedcode 163 var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`) 164 165 // FileKindForLang returns the file kind associated with the given language ID, 166 // or UnknownKind if the language ID is not recognized. 167 func FileKindForLang(langID string) FileKind { 168 switch langID { 169 case "go": 170 return Go 171 case "go.mod": 172 return Mod 173 case "go.sum": 174 return Sum 175 case "tmpl", "gotmpl": 176 return Tmpl 177 case "go.work": 178 return Work 179 default: 180 return UnknownKind 181 } 182 } 183 184 func (k FileKind) String() string { 185 switch k { 186 case Go: 187 return "go" 188 case Mod: 189 return "go.mod" 190 case Sum: 191 return "go.sum" 192 case Tmpl: 193 return "tmpl" 194 case Work: 195 return "go.work" 196 default: 197 return fmt.Sprintf("unk%d", k) 198 } 199 } 200 201 // nodeAtPos returns the index and the node whose position is contained inside 202 // the node list. 203 func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) { 204 if nodes == nil { 205 return nil, -1 206 } 207 for i, node := range nodes { 208 if node.Pos() <= pos && pos <= node.End() { 209 return node, i 210 } 211 } 212 return nil, -1 213 } 214 215 // IsInterface returns if a types.Type is an interface 216 func IsInterface(T types.Type) bool { 217 return T != nil && types.IsInterface(T) 218 } 219 220 // FormatNode returns the "pretty-print" output for an ast node. 221 func FormatNode(fset *token.FileSet, n ast.Node) string { 222 var buf strings.Builder 223 if err := printer.Fprint(&buf, fset, n); err != nil { 224 return "" 225 } 226 return buf.String() 227 } 228 229 // Deref returns a pointer's element type, traversing as many levels as needed. 230 // Otherwise it returns typ. 231 // 232 // It can return a pointer type for cyclic types (see golang/go#45510). 233 func Deref(typ types.Type) types.Type { 234 var seen map[types.Type]struct{} 235 for { 236 p, ok := typ.Underlying().(*types.Pointer) 237 if !ok { 238 return typ 239 } 240 if _, ok := seen[p.Elem()]; ok { 241 return typ 242 } 243 244 typ = p.Elem() 245 246 if seen == nil { 247 seen = make(map[types.Type]struct{}) 248 } 249 seen[typ] = struct{}{} 250 } 251 } 252 253 func SortDiagnostics(d []*Diagnostic) { 254 sort.Slice(d, func(i int, j int) bool { 255 return CompareDiagnostic(d[i], d[j]) < 0 256 }) 257 } 258 259 func CompareDiagnostic(a, b *Diagnostic) int { 260 if r := protocol.CompareRange(a.Range, b.Range); r != 0 { 261 return r 262 } 263 if a.Source < b.Source { 264 return -1 265 } 266 if a.Message < b.Message { 267 return -1 268 } 269 if a.Message == b.Message { 270 return 0 271 } 272 return 1 273 } 274 275 // FindPackageFromPos finds the first package containing pos in its 276 // type-checked AST. 277 func FindPackageFromPos(ctx context.Context, snapshot Snapshot, pos token.Pos) (Package, error) { 278 tok := snapshot.FileSet().File(pos) 279 if tok == nil { 280 return nil, errors.Errorf("no file for pos %v", pos) 281 } 282 uri := span.URIFromPath(tok.Name()) 283 pkgs, err := snapshot.PackagesForFile(ctx, uri, TypecheckAll, true) 284 if err != nil { 285 return nil, err 286 } 287 // Only return the package if it actually type-checked the given position. 288 for _, pkg := range pkgs { 289 parsed, err := pkg.File(uri) 290 if err != nil { 291 return nil, err 292 } 293 if parsed == nil { 294 continue 295 } 296 if parsed.Tok.Base() != tok.Base() { 297 continue 298 } 299 return pkg, nil 300 } 301 return nil, errors.Errorf("no package for given file position") 302 } 303 304 // findFileInDeps finds uri in pkg or its dependencies. 305 func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) { 306 queue := []Package{pkg} 307 seen := make(map[string]bool) 308 309 for len(queue) > 0 { 310 pkg := queue[0] 311 queue = queue[1:] 312 seen[pkg.ID()] = true 313 314 if pgf, err := pkg.File(uri); err == nil { 315 return pgf, pkg, nil 316 } 317 for _, dep := range pkg.Imports() { 318 if !seen[dep.ID()] { 319 queue = append(queue, dep) 320 } 321 } 322 } 323 return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID()) 324 } 325 326 // ImportPath returns the unquoted import path of s, 327 // or "" if the path is not properly quoted. 328 func ImportPath(s *ast.ImportSpec) string { 329 t, err := strconv.Unquote(s.Path.Value) 330 if err != nil { 331 return "" 332 } 333 return t 334 } 335 336 // NodeContains returns true if a node encloses a given position pos. 337 func NodeContains(n ast.Node, pos token.Pos) bool { 338 return n != nil && n.Pos() <= pos && pos <= n.End() 339 } 340 341 // CollectScopes returns all scopes in an ast path, ordered as innermost scope 342 // first. 343 func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope { 344 // scopes[i], where i<len(path), is the possibly nil Scope of path[i]. 345 var scopes []*types.Scope 346 for _, n := range path { 347 // Include *FuncType scope if pos is inside the function body. 348 switch node := n.(type) { 349 case *ast.FuncDecl: 350 if node.Body != nil && NodeContains(node.Body, pos) { 351 n = node.Type 352 } 353 case *ast.FuncLit: 354 if node.Body != nil && NodeContains(node.Body, pos) { 355 n = node.Type 356 } 357 } 358 scopes = append(scopes, info.Scopes[n]) 359 } 360 return scopes 361 } 362 363 // Qualifier returns a function that appropriately formats a types.PkgName 364 // appearing in a *ast.File. 365 func Qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier { 366 // Construct mapping of import paths to their defined or implicit names. 367 imports := make(map[*types.Package]string) 368 for _, imp := range f.Imports { 369 var obj types.Object 370 if imp.Name != nil { 371 obj = info.Defs[imp.Name] 372 } else { 373 obj = info.Implicits[imp] 374 } 375 if pkgname, ok := obj.(*types.PkgName); ok { 376 imports[pkgname.Imported()] = pkgname.Name() 377 } 378 } 379 // Define qualifier to replace full package paths with names of the imports. 380 return func(p *types.Package) string { 381 if p == pkg { 382 return "" 383 } 384 if name, ok := imports[p]; ok { 385 if name == "." { 386 return "" 387 } 388 return name 389 } 390 return p.Name() 391 } 392 } 393 394 // isDirective reports whether c is a comment directive. 395 // 396 // Copied and adapted from go/src/go/ast/ast.go. 397 func isDirective(c string) bool { 398 if len(c) < 3 { 399 return false 400 } 401 if c[1] != '/' { 402 return false 403 } 404 //-style comment (no newline at the end) 405 c = c[2:] 406 if len(c) == 0 { 407 // empty line 408 return false 409 } 410 // "//line " is a line directive. 411 // (The // has been removed.) 412 if strings.HasPrefix(c, "line ") { 413 return true 414 } 415 416 // "//[a-z0-9]+:[a-z0-9]" 417 // (The // has been removed.) 418 colon := strings.Index(c, ":") 419 if colon <= 0 || colon+1 >= len(c) { 420 return false 421 } 422 for i := 0; i <= colon+1; i++ { 423 if i == colon { 424 continue 425 } 426 b := c[i] 427 if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { 428 return false 429 } 430 } 431 return true 432 } 433 434 // honorSymlinks toggles whether or not we consider symlinks when comparing 435 // file or directory URIs. 436 const honorSymlinks = false 437 438 func CompareURI(left, right span.URI) int { 439 if honorSymlinks { 440 return span.CompareURI(left, right) 441 } 442 if left == right { 443 return 0 444 } 445 if left < right { 446 return -1 447 } 448 return 1 449 } 450 451 // InDir checks whether path is in the file tree rooted at dir. 452 // InDir makes some effort to succeed even in the presence of symbolic links. 453 // 454 // Copied and slightly adjusted from go/src/cmd/go/internal/search/search.go. 455 func InDir(dir, path string) bool { 456 if inDirLex(dir, path) { 457 return true 458 } 459 if !honorSymlinks { 460 return false 461 } 462 xpath, err := filepath.EvalSymlinks(path) 463 if err != nil || xpath == path { 464 xpath = "" 465 } else { 466 if inDirLex(dir, xpath) { 467 return true 468 } 469 } 470 471 xdir, err := filepath.EvalSymlinks(dir) 472 if err == nil && xdir != dir { 473 if inDirLex(xdir, path) { 474 return true 475 } 476 if xpath != "" { 477 if inDirLex(xdir, xpath) { 478 return true 479 } 480 } 481 } 482 return false 483 } 484 485 // inDirLex is like inDir but only checks the lexical form of the file names. 486 // It does not consider symbolic links. 487 // 488 // Copied from go/src/cmd/go/internal/search/search.go. 489 func inDirLex(dir, path string) bool { 490 pv := strings.ToUpper(filepath.VolumeName(path)) 491 dv := strings.ToUpper(filepath.VolumeName(dir)) 492 path = path[len(pv):] 493 dir = dir[len(dv):] 494 switch { 495 default: 496 return false 497 case pv != dv: 498 return false 499 case len(path) == len(dir): 500 if path == dir { 501 return true 502 } 503 return false 504 case dir == "": 505 return path != "" 506 case len(path) > len(dir): 507 if dir[len(dir)-1] == filepath.Separator { 508 if path[:len(dir)] == dir { 509 return path[len(dir):] != "" 510 } 511 return false 512 } 513 if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir { 514 if len(path) == len(dir)+1 { 515 return true 516 } 517 return path[len(dir)+1:] != "" 518 } 519 return false 520 } 521 } 522 523 // IsValidImport returns whether importPkgPath is importable 524 // by pkgPath 525 func IsValidImport(pkgPath, importPkgPath string) bool { 526 i := strings.LastIndex(string(importPkgPath), "/internal/") 527 if i == -1 { 528 return true 529 } 530 if IsCommandLineArguments(string(pkgPath)) { 531 return true 532 } 533 return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i])) 534 } 535 536 // IsCommandLineArguments reports whether a given value denotes 537 // "command-line-arguments" package, which is a package with an unknown ID 538 // created by the go command. It can have a test variant, which is why callers 539 // should not check that a value equals "command-line-arguments" directly. 540 func IsCommandLineArguments(s string) bool { 541 return strings.Contains(s, "command-line-arguments") 542 } 543 544 // Offset returns tok.Offset(pos), but first checks that the pos is in range 545 // for the given file. 546 func Offset(tok *token.File, pos token.Pos) (int, error) { 547 if !InRange(tok, pos) { 548 return -1, fmt.Errorf("pos %v is not in range for file [%v:%v)", pos, tok.Base(), tok.Base()+tok.Size()) 549 } 550 return tok.Offset(pos), nil 551 } 552 553 // Pos returns tok.Pos(offset), but first checks that the offset is valid for 554 // the given file. 555 func Pos(tok *token.File, offset int) (token.Pos, error) { 556 if offset < 0 || offset > tok.Size() { 557 return token.NoPos, fmt.Errorf("offset %v is not in range for file of size %v", offset, tok.Size()) 558 } 559 return tok.Pos(offset), nil 560 } 561 562 // InRange reports whether the given position is in the given token.File. 563 func InRange(tok *token.File, pos token.Pos) bool { 564 size := tok.Pos(tok.Size()) 565 return int(pos) >= tok.Base() && pos <= size 566 } 567 568 // LineToRange creates a Range spanning start and end. 569 func LineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) { 570 return ByteOffsetsToRange(m, uri, start.Byte, end.Byte) 571 } 572 573 // ByteOffsetsToRange creates a range spanning start and end. 574 func ByteOffsetsToRange(m *protocol.ColumnMapper, uri span.URI, start, end int) (protocol.Range, error) { 575 line, col, err := m.Converter.ToPosition(start) 576 if err != nil { 577 return protocol.Range{}, err 578 } 579 s := span.NewPoint(line, col, start) 580 line, col, err = m.Converter.ToPosition(end) 581 if err != nil { 582 return protocol.Range{}, err 583 } 584 e := span.NewPoint(line, col, end) 585 return m.Range(span.New(uri, s, e)) 586 }