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