.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / internal / lsp / source / util.go
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         "golang.org/x/tools/internal/lsp/protocol"
20         "golang.org/x/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 func Deref(typ types.Type) types.Type {
225         for {
226                 p, ok := typ.Underlying().(*types.Pointer)
227                 if !ok {
228                         return typ
229                 }
230                 typ = p.Elem()
231         }
232 }
233
234 func SortDiagnostics(d []*Diagnostic) {
235         sort.Slice(d, func(i int, j int) bool {
236                 return CompareDiagnostic(d[i], d[j]) < 0
237         })
238 }
239
240 func CompareDiagnostic(a, b *Diagnostic) int {
241         if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
242                 return r
243         }
244         if a.Source < b.Source {
245                 return -1
246         }
247         if a.Message < b.Message {
248                 return -1
249         }
250         if a.Message == b.Message {
251                 return 0
252         }
253         return 1
254 }
255
256 // FindPosInPackage finds the parsed file for a position in a given search
257 // package.
258 func FindPosInPackage(snapshot Snapshot, searchpkg Package, pos token.Pos) (*ParsedGoFile, Package, error) {
259         tok := snapshot.FileSet().File(pos)
260         if tok == nil {
261                 return nil, nil, errors.Errorf("no file for pos in package %s", searchpkg.ID())
262         }
263         uri := span.URIFromPath(tok.Name())
264
265         pgf, pkg, err := findFileInDeps(searchpkg, uri)
266         if err != nil {
267                 return nil, nil, err
268         }
269         return pgf, pkg, nil
270 }
271
272 // findFileInDeps finds uri in pkg or its dependencies.
273 func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) {
274         queue := []Package{pkg}
275         seen := make(map[string]bool)
276
277         for len(queue) > 0 {
278                 pkg := queue[0]
279                 queue = queue[1:]
280                 seen[pkg.ID()] = true
281
282                 if pgf, err := pkg.File(uri); err == nil {
283                         return pgf, pkg, nil
284                 }
285                 for _, dep := range pkg.Imports() {
286                         if !seen[dep.ID()] {
287                                 queue = append(queue, dep)
288                         }
289                 }
290         }
291         return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
292 }
293
294 // ImportPath returns the unquoted import path of s,
295 // or "" if the path is not properly quoted.
296 func ImportPath(s *ast.ImportSpec) string {
297         t, err := strconv.Unquote(s.Path.Value)
298         if err != nil {
299                 return ""
300         }
301         return t
302 }
303
304 // NodeContains returns true if a node encloses a given position pos.
305 func NodeContains(n ast.Node, pos token.Pos) bool {
306         return n != nil && n.Pos() <= pos && pos <= n.End()
307 }
308
309 // CollectScopes returns all scopes in an ast path, ordered as innermost scope
310 // first.
311 func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope {
312         // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
313         var scopes []*types.Scope
314         for _, n := range path {
315                 // Include *FuncType scope if pos is inside the function body.
316                 switch node := n.(type) {
317                 case *ast.FuncDecl:
318                         if node.Body != nil && NodeContains(node.Body, pos) {
319                                 n = node.Type
320                         }
321                 case *ast.FuncLit:
322                         if node.Body != nil && NodeContains(node.Body, pos) {
323                                 n = node.Type
324                         }
325                 }
326                 scopes = append(scopes, info.Scopes[n])
327         }
328         return scopes
329 }
330
331 // Qualifier returns a function that appropriately formats a types.PkgName
332 // appearing in a *ast.File.
333 func Qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
334         // Construct mapping of import paths to their defined or implicit names.
335         imports := make(map[*types.Package]string)
336         for _, imp := range f.Imports {
337                 var obj types.Object
338                 if imp.Name != nil {
339                         obj = info.Defs[imp.Name]
340                 } else {
341                         obj = info.Implicits[imp]
342                 }
343                 if pkgname, ok := obj.(*types.PkgName); ok {
344                         imports[pkgname.Imported()] = pkgname.Name()
345                 }
346         }
347         // Define qualifier to replace full package paths with names of the imports.
348         return func(p *types.Package) string {
349                 if p == pkg {
350                         return ""
351                 }
352                 if name, ok := imports[p]; ok {
353                         return name
354                 }
355                 return p.Name()
356         }
357 }
358
359 // isDirective reports whether c is a comment directive.
360 //
361 // Copied and adapted from go/src/go/ast/ast.go.
362 func isDirective(c string) bool {
363         if len(c) < 3 {
364                 return false
365         }
366         if c[1] != '/' {
367                 return false
368         }
369         //-style comment (no newline at the end)
370         c = c[2:]
371         if len(c) == 0 {
372                 // empty line
373                 return false
374         }
375         // "//line " is a line directive.
376         // (The // has been removed.)
377         if strings.HasPrefix(c, "line ") {
378                 return true
379         }
380
381         // "//[a-z0-9]+:[a-z0-9]"
382         // (The // has been removed.)
383         colon := strings.Index(c, ":")
384         if colon <= 0 || colon+1 >= len(c) {
385                 return false
386         }
387         for i := 0; i <= colon+1; i++ {
388                 if i == colon {
389                         continue
390                 }
391                 b := c[i]
392                 if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
393                         return false
394                 }
395         }
396         return true
397 }
398
399 // honorSymlinks toggles whether or not we consider symlinks when comparing
400 // file or directory URIs.
401 const honorSymlinks = false
402
403 func CompareURI(left, right span.URI) int {
404         if honorSymlinks {
405                 return span.CompareURI(left, right)
406         }
407         if left == right {
408                 return 0
409         }
410         if left < right {
411                 return -1
412         }
413         return 1
414 }
415
416 // InDir checks whether path is in the file tree rooted at dir.
417 // InDir makes some effort to succeed even in the presence of symbolic links.
418 //
419 // Copied and slightly adjusted from go/src/cmd/go/internal/search/search.go.
420 func InDir(dir, path string) bool {
421         if inDirLex(dir, path) {
422                 return true
423         }
424         if !honorSymlinks {
425                 return false
426         }
427         xpath, err := filepath.EvalSymlinks(path)
428         if err != nil || xpath == path {
429                 xpath = ""
430         } else {
431                 if inDirLex(dir, xpath) {
432                         return true
433                 }
434         }
435
436         xdir, err := filepath.EvalSymlinks(dir)
437         if err == nil && xdir != dir {
438                 if inDirLex(xdir, path) {
439                         return true
440                 }
441                 if xpath != "" {
442                         if inDirLex(xdir, xpath) {
443                                 return true
444                         }
445                 }
446         }
447         return false
448 }
449
450 // inDirLex is like inDir but only checks the lexical form of the file names.
451 // It does not consider symbolic links.
452 //
453 // Copied from go/src/cmd/go/internal/search/search.go.
454 func inDirLex(dir, path string) bool {
455         pv := strings.ToUpper(filepath.VolumeName(path))
456         dv := strings.ToUpper(filepath.VolumeName(dir))
457         path = path[len(pv):]
458         dir = dir[len(dv):]
459         switch {
460         default:
461                 return false
462         case pv != dv:
463                 return false
464         case len(path) == len(dir):
465                 if path == dir {
466                         return true
467                 }
468                 return false
469         case dir == "":
470                 return path != ""
471         case len(path) > len(dir):
472                 if dir[len(dir)-1] == filepath.Separator {
473                         if path[:len(dir)] == dir {
474                                 return path[len(dir):] != ""
475                         }
476                         return false
477                 }
478                 if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
479                         if len(path) == len(dir)+1 {
480                                 return true
481                         }
482                         return path[len(dir)+1:] != ""
483                 }
484                 return false
485         }
486 }