Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / internal / lsp / link.go
1 // Copyright 2018 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 lsp
6
7 import (
8         "bytes"
9         "context"
10         "fmt"
11         "go/ast"
12         "go/token"
13         "net/url"
14         "regexp"
15         "strconv"
16         "strings"
17         "sync"
18
19         "golang.org/x/mod/modfile"
20         "golang.org/x/tools/internal/event"
21         "golang.org/x/tools/internal/lsp/debug/tag"
22         "golang.org/x/tools/internal/lsp/protocol"
23         "golang.org/x/tools/internal/lsp/source"
24         "golang.org/x/tools/internal/span"
25 )
26
27 func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) (links []protocol.DocumentLink, err error) {
28         snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
29         defer release()
30         if !ok {
31                 return nil, err
32         }
33         switch fh.Kind() {
34         case source.Mod:
35                 links, err = modLinks(ctx, snapshot, fh)
36         case source.Go:
37                 links, err = goLinks(ctx, snapshot, fh)
38         }
39         // Don't return errors for document links.
40         if err != nil {
41                 event.Error(ctx, "failed to compute document links", err, tag.URI.Of(fh.URI()))
42                 return nil, nil
43         }
44         return links, nil
45 }
46
47 func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
48         pm, err := snapshot.ParseMod(ctx, fh)
49         if err != nil {
50                 return nil, err
51         }
52         var links []protocol.DocumentLink
53         for _, req := range pm.File.Require {
54                 if req.Syntax == nil {
55                         continue
56                 }
57                 // See golang/go#36998: don't link to modules matching GOPRIVATE.
58                 if snapshot.View().IsGoPrivatePath(req.Mod.Path) {
59                         continue
60                 }
61                 dep := []byte(req.Mod.Path)
62                 s, e := req.Syntax.Start.Byte, req.Syntax.End.Byte
63                 i := bytes.Index(pm.Mapper.Content[s:e], dep)
64                 if i == -1 {
65                         continue
66                 }
67                 // Shift the start position to the location of the
68                 // dependency within the require statement.
69                 start, end := token.Pos(s+i), token.Pos(s+i+len(dep))
70                 target := fmt.Sprintf("https://%s/mod/%s", snapshot.View().Options().LinkTarget, req.Mod.String())
71                 l, err := toProtocolLink(snapshot, pm.Mapper, target, start, end, source.Mod)
72                 if err != nil {
73                         return nil, err
74                 }
75                 links = append(links, l)
76         }
77         // TODO(ridersofrohan): handle links for replace and exclude directives.
78         if syntax := pm.File.Syntax; syntax == nil {
79                 return links, nil
80         }
81         // Get all the links that are contained in the comments of the file.
82         for _, expr := range pm.File.Syntax.Stmt {
83                 comments := expr.Comment()
84                 if comments == nil {
85                         continue
86                 }
87                 for _, section := range [][]modfile.Comment{comments.Before, comments.Suffix, comments.After} {
88                         for _, comment := range section {
89                                 l, err := findLinksInString(ctx, snapshot, comment.Token, token.Pos(comment.Start.Byte), pm.Mapper, source.Mod)
90                                 if err != nil {
91                                         return nil, err
92                                 }
93                                 links = append(links, l...)
94                         }
95                 }
96         }
97         return links, nil
98 }
99
100 func goLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
101         view := snapshot.View()
102         // We don't actually need type information, so any typecheck mode is fine.
103         pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckWorkspace, source.WidestPackage)
104         if err != nil {
105                 return nil, err
106         }
107         pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
108         if err != nil {
109                 return nil, err
110         }
111         var imports []*ast.ImportSpec
112         var str []*ast.BasicLit
113         ast.Inspect(pgf.File, func(node ast.Node) bool {
114                 switch n := node.(type) {
115                 case *ast.ImportSpec:
116                         imports = append(imports, n)
117                         return false
118                 case *ast.BasicLit:
119                         // Look for links in string literals.
120                         if n.Kind == token.STRING {
121                                 str = append(str, n)
122                         }
123                         return false
124                 }
125                 return true
126         })
127         var links []protocol.DocumentLink
128         // For import specs, provide a link to a documentation website, like
129         // https://pkg.go.dev.
130         if view.Options().ImportShortcut.ShowLinks() {
131                 for _, imp := range imports {
132                         target, err := strconv.Unquote(imp.Path.Value)
133                         if err != nil {
134                                 continue
135                         }
136                         // See golang/go#36998: don't link to modules matching GOPRIVATE.
137                         if view.IsGoPrivatePath(target) {
138                                 continue
139                         }
140                         if mod, version, ok := moduleAtVersion(ctx, snapshot, target, pkg); ok && strings.ToLower(view.Options().LinkTarget) == "pkg.go.dev" {
141                                 target = strings.Replace(target, mod, mod+"@"+version, 1)
142                         }
143                         // Account for the quotation marks in the positions.
144                         start := imp.Path.Pos() + 1
145                         end := imp.Path.End() - 1
146                         target = fmt.Sprintf("https://%s/%s", view.Options().LinkTarget, target)
147                         l, err := toProtocolLink(snapshot, pgf.Mapper, target, start, end, source.Go)
148                         if err != nil {
149                                 return nil, err
150                         }
151                         links = append(links, l)
152                 }
153         }
154         for _, s := range str {
155                 l, err := findLinksInString(ctx, snapshot, s.Value, s.Pos(), pgf.Mapper, source.Go)
156                 if err != nil {
157                         return nil, err
158                 }
159                 links = append(links, l...)
160         }
161         for _, commentGroup := range pgf.File.Comments {
162                 for _, comment := range commentGroup.List {
163                         l, err := findLinksInString(ctx, snapshot, comment.Text, comment.Pos(), pgf.Mapper, source.Go)
164                         if err != nil {
165                                 return nil, err
166                         }
167                         links = append(links, l...)
168                 }
169         }
170         return links, nil
171 }
172
173 func moduleAtVersion(ctx context.Context, snapshot source.Snapshot, target string, pkg source.Package) (string, string, bool) {
174         impPkg, err := pkg.GetImport(target)
175         if err != nil {
176                 return "", "", false
177         }
178         if impPkg.Version() == nil {
179                 return "", "", false
180         }
181         version, modpath := impPkg.Version().Version, impPkg.Version().Path
182         if modpath == "" || version == "" {
183                 return "", "", false
184         }
185         return modpath, version, true
186 }
187
188 func findLinksInString(ctx context.Context, snapshot source.Snapshot, src string, pos token.Pos, m *protocol.ColumnMapper, fileKind source.FileKind) ([]protocol.DocumentLink, error) {
189         var links []protocol.DocumentLink
190         for _, index := range snapshot.View().Options().URLRegexp.FindAllIndex([]byte(src), -1) {
191                 start, end := index[0], index[1]
192                 startPos := token.Pos(int(pos) + start)
193                 endPos := token.Pos(int(pos) + end)
194                 link := src[start:end]
195                 linkURL, err := url.Parse(link)
196                 // Fallback: Linkify IP addresses as suggested in golang/go#18824.
197                 if err != nil {
198                         linkURL, err = url.Parse("//" + link)
199                         // Not all potential links will be valid, so don't return this error.
200                         if err != nil {
201                                 continue
202                         }
203                 }
204                 // If the URL has no scheme, use https.
205                 if linkURL.Scheme == "" {
206                         linkURL.Scheme = "https"
207                 }
208                 l, err := toProtocolLink(snapshot, m, linkURL.String(), startPos, endPos, fileKind)
209                 if err != nil {
210                         return nil, err
211                 }
212                 links = append(links, l)
213         }
214         // Handle golang/go#1234-style links.
215         r := getIssueRegexp()
216         for _, index := range r.FindAllIndex([]byte(src), -1) {
217                 start, end := index[0], index[1]
218                 startPos := token.Pos(int(pos) + start)
219                 endPos := token.Pos(int(pos) + end)
220                 matches := r.FindStringSubmatch(src)
221                 if len(matches) < 4 {
222                         continue
223                 }
224                 org, repo, number := matches[1], matches[2], matches[3]
225                 target := fmt.Sprintf("https://github.com/%s/%s/issues/%s", org, repo, number)
226                 l, err := toProtocolLink(snapshot, m, target, startPos, endPos, fileKind)
227                 if err != nil {
228                         return nil, err
229                 }
230                 links = append(links, l)
231         }
232         return links, nil
233 }
234
235 func getIssueRegexp() *regexp.Regexp {
236         once.Do(func() {
237                 issueRegexp = regexp.MustCompile(`(\w+)/([\w-]+)#([0-9]+)`)
238         })
239         return issueRegexp
240 }
241
242 var (
243         once        sync.Once
244         issueRegexp *regexp.Regexp
245 )
246
247 func toProtocolLink(snapshot source.Snapshot, m *protocol.ColumnMapper, target string, start, end token.Pos, fileKind source.FileKind) (protocol.DocumentLink, error) {
248         var rng protocol.Range
249         switch fileKind {
250         case source.Go:
251                 spn, err := span.NewRange(snapshot.FileSet(), start, end).Span()
252                 if err != nil {
253                         return protocol.DocumentLink{}, err
254                 }
255                 rng, err = m.Range(spn)
256                 if err != nil {
257                         return protocol.DocumentLink{}, err
258                 }
259         case source.Mod:
260                 s, e := int(start), int(end)
261                 line, col, err := m.Converter.ToPosition(s)
262                 if err != nil {
263                         return protocol.DocumentLink{}, err
264                 }
265                 start := span.NewPoint(line, col, s)
266                 line, col, err = m.Converter.ToPosition(e)
267                 if err != nil {
268                         return protocol.DocumentLink{}, err
269                 }
270                 end := span.NewPoint(line, col, e)
271                 rng, err = m.Range(span.New(m.URI, start, end))
272                 if err != nil {
273                         return protocol.DocumentLink{}, err
274                 }
275         }
276         return protocol.DocumentLink{
277                 Range:  rng,
278                 Target: target,
279         }, nil
280 }