.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 / cache / mod.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 cache
6
7 import (
8         "context"
9         "fmt"
10         "path/filepath"
11         "regexp"
12         "strings"
13
14         "golang.org/x/mod/modfile"
15         "golang.org/x/mod/module"
16         "golang.org/x/tools/internal/event"
17         "golang.org/x/tools/internal/gocommand"
18         "golang.org/x/tools/internal/lsp/command"
19         "golang.org/x/tools/internal/lsp/debug/tag"
20         "golang.org/x/tools/internal/lsp/protocol"
21         "golang.org/x/tools/internal/lsp/source"
22         "golang.org/x/tools/internal/memoize"
23         "golang.org/x/tools/internal/span"
24 )
25
26 type parseModHandle struct {
27         handle *memoize.Handle
28 }
29
30 type parseModData struct {
31         parsed *source.ParsedModule
32
33         // err is any error encountered while parsing the file.
34         err error
35 }
36
37 func (mh *parseModHandle) parse(ctx context.Context, snapshot *snapshot) (*source.ParsedModule, error) {
38         v, err := mh.handle.Get(ctx, snapshot.generation, snapshot)
39         if err != nil {
40                 return nil, err
41         }
42         data := v.(*parseModData)
43         return data.parsed, data.err
44 }
45
46 func (s *snapshot) ParseMod(ctx context.Context, modFH source.FileHandle) (*source.ParsedModule, error) {
47         if handle := s.getParseModHandle(modFH.URI()); handle != nil {
48                 return handle.parse(ctx, s)
49         }
50         h := s.generation.Bind(modFH.FileIdentity(), func(ctx context.Context, _ memoize.Arg) interface{} {
51                 _, done := event.Start(ctx, "cache.ParseModHandle", tag.URI.Of(modFH.URI()))
52                 defer done()
53
54                 contents, err := modFH.Read()
55                 if err != nil {
56                         return &parseModData{err: err}
57                 }
58                 m := &protocol.ColumnMapper{
59                         URI:       modFH.URI(),
60                         Converter: span.NewContentConverter(modFH.URI().Filename(), contents),
61                         Content:   contents,
62                 }
63                 file, parseErr := modfile.Parse(modFH.URI().Filename(), contents, nil)
64                 // Attempt to convert the error to a standardized parse error.
65                 var parseErrors []*source.Diagnostic
66                 if parseErr != nil {
67                         mfErrList, ok := parseErr.(modfile.ErrorList)
68                         if !ok {
69                                 return &parseModData{err: fmt.Errorf("unexpected parse error type %v", parseErr)}
70                         }
71                         for _, mfErr := range mfErrList {
72                                 rng, err := rangeFromPositions(m, mfErr.Pos, mfErr.Pos)
73                                 if err != nil {
74                                         return &parseModData{err: err}
75                                 }
76                                 parseErrors = []*source.Diagnostic{{
77                                         URI:      modFH.URI(),
78                                         Range:    rng,
79                                         Severity: protocol.SeverityError,
80                                         Source:   source.ParseError,
81                                         Message:  mfErr.Err.Error(),
82                                 }}
83                         }
84                 }
85                 return &parseModData{
86                         parsed: &source.ParsedModule{
87                                 URI:         modFH.URI(),
88                                 Mapper:      m,
89                                 File:        file,
90                                 ParseErrors: parseErrors,
91                         },
92                         err: parseErr,
93                 }
94         }, nil)
95
96         pmh := &parseModHandle{handle: h}
97         s.mu.Lock()
98         s.parseModHandles[modFH.URI()] = pmh
99         s.mu.Unlock()
100
101         return pmh.parse(ctx, s)
102 }
103
104 // goSum reads the go.sum file for the go.mod file at modURI, if it exists. If
105 // it doesn't exist, it returns nil.
106 func (s *snapshot) goSum(ctx context.Context, modURI span.URI) []byte {
107         // Get the go.sum file, either from the snapshot or directly from the
108         // cache. Avoid (*snapshot).GetFile here, as we don't want to add
109         // nonexistent file handles to the snapshot if the file does not exist.
110         sumURI := span.URIFromPath(sumFilename(modURI))
111         var sumFH source.FileHandle = s.FindFile(sumURI)
112         if sumFH == nil {
113                 var err error
114                 sumFH, err = s.view.session.cache.getFile(ctx, sumURI)
115                 if err != nil {
116                         return nil
117                 }
118         }
119         content, err := sumFH.Read()
120         if err != nil {
121                 return nil
122         }
123         return content
124 }
125
126 func sumFilename(modURI span.URI) string {
127         return strings.TrimSuffix(modURI.Filename(), ".mod") + ".sum"
128 }
129
130 // modKey is uniquely identifies cached data for `go mod why` or dependencies
131 // to upgrade.
132 type modKey struct {
133         sessionID, env, view string
134         mod                  source.FileIdentity
135         verb                 modAction
136 }
137
138 type modAction int
139
140 const (
141         why modAction = iota
142         upgrade
143 )
144
145 type modWhyHandle struct {
146         handle *memoize.Handle
147 }
148
149 type modWhyData struct {
150         // why keeps track of the `go mod why` results for each require statement
151         // in the go.mod file.
152         why map[string]string
153
154         err error
155 }
156
157 func (mwh *modWhyHandle) why(ctx context.Context, snapshot *snapshot) (map[string]string, error) {
158         v, err := mwh.handle.Get(ctx, snapshot.generation, snapshot)
159         if err != nil {
160                 return nil, err
161         }
162         data := v.(*modWhyData)
163         return data.why, data.err
164 }
165
166 func (s *snapshot) ModWhy(ctx context.Context, fh source.FileHandle) (map[string]string, error) {
167         if fh.Kind() != source.Mod {
168                 return nil, fmt.Errorf("%s is not a go.mod file", fh.URI())
169         }
170         if handle := s.getModWhyHandle(fh.URI()); handle != nil {
171                 return handle.why(ctx, s)
172         }
173         key := modKey{
174                 sessionID: s.view.session.id,
175                 env:       hashEnv(s),
176                 mod:       fh.FileIdentity(),
177                 view:      s.view.rootURI.Filename(),
178                 verb:      why,
179         }
180         h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
181                 ctx, done := event.Start(ctx, "cache.ModWhyHandle", tag.URI.Of(fh.URI()))
182                 defer done()
183
184                 snapshot := arg.(*snapshot)
185
186                 pm, err := snapshot.ParseMod(ctx, fh)
187                 if err != nil {
188                         return &modWhyData{err: err}
189                 }
190                 // No requires to explain.
191                 if len(pm.File.Require) == 0 {
192                         return &modWhyData{}
193                 }
194                 // Run `go mod why` on all the dependencies.
195                 inv := &gocommand.Invocation{
196                         Verb:       "mod",
197                         Args:       []string{"why", "-m"},
198                         WorkingDir: filepath.Dir(fh.URI().Filename()),
199                 }
200                 for _, req := range pm.File.Require {
201                         inv.Args = append(inv.Args, req.Mod.Path)
202                 }
203                 stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal, inv)
204                 if err != nil {
205                         return &modWhyData{err: err}
206                 }
207                 whyList := strings.Split(stdout.String(), "\n\n")
208                 if len(whyList) != len(pm.File.Require) {
209                         return &modWhyData{
210                                 err: fmt.Errorf("mismatched number of results: got %v, want %v", len(whyList), len(pm.File.Require)),
211                         }
212                 }
213                 why := make(map[string]string, len(pm.File.Require))
214                 for i, req := range pm.File.Require {
215                         why[req.Mod.Path] = whyList[i]
216                 }
217                 return &modWhyData{why: why}
218         }, nil)
219
220         mwh := &modWhyHandle{handle: h}
221         s.mu.Lock()
222         s.modWhyHandles[fh.URI()] = mwh
223         s.mu.Unlock()
224
225         return mwh.why(ctx, s)
226 }
227
228 // extractGoCommandError tries to parse errors that come from the go command
229 // and shape them into go.mod diagnostics.
230 func (s *snapshot) extractGoCommandErrors(ctx context.Context, goCmdError string) ([]*source.Diagnostic, error) {
231         diagLocations := map[*source.ParsedModule]span.Span{}
232         backupDiagLocations := map[*source.ParsedModule]span.Span{}
233
234         // The go command emits parse errors for completely invalid go.mod files.
235         // Those are reported by our own diagnostics and can be ignored here.
236         // As of writing, we are not aware of any other errors that include
237         // file/position information, so don't even try to find it.
238         if strings.Contains(goCmdError, "errors parsing go.mod") {
239                 return nil, nil
240         }
241
242         // Match the error against all the mod files in the workspace.
243         for _, uri := range s.ModFiles() {
244                 fh, err := s.GetFile(ctx, uri)
245                 if err != nil {
246                         return nil, err
247                 }
248                 pm, err := s.ParseMod(ctx, fh)
249                 if err != nil {
250                         return nil, err
251                 }
252                 spn, found, err := s.matchErrorToModule(ctx, pm, goCmdError)
253                 if err != nil {
254                         return nil, err
255                 }
256                 if found {
257                         diagLocations[pm] = spn
258                 } else {
259                         backupDiagLocations[pm] = spn
260                 }
261         }
262
263         // If we didn't find any good matches, assign diagnostics to all go.mod files.
264         if len(diagLocations) == 0 {
265                 diagLocations = backupDiagLocations
266         }
267
268         var srcErrs []*source.Diagnostic
269         for pm, spn := range diagLocations {
270                 diag, err := s.goCommandDiagnostic(pm, spn, goCmdError)
271                 if err != nil {
272                         return nil, err
273                 }
274                 srcErrs = append(srcErrs, diag)
275         }
276         return srcErrs, nil
277 }
278
279 var moduleVersionInErrorRe = regexp.MustCompile(`[:\s]([+-._~0-9A-Za-z]+)@([+-._~0-9A-Za-z]+)[:\s]`)
280
281 // matchErrorToModule matches a go command error message to a go.mod file.
282 // Some examples:
283 //
284 //    example.com@v1.2.2: reading example.com/@v/v1.2.2.mod: no such file or directory
285 //    go: github.com/cockroachdb/apd/v2@v2.0.72: reading github.com/cockroachdb/apd/go.mod at revision v2.0.72: unknown revision v2.0.72
286 //    go: example.com@v1.2.3 requires\n\trandom.org@v1.2.3: parsing go.mod:\n\tmodule declares its path as: bob.org\n\tbut was required as: random.org
287 //
288 // It returns the location of a reference to the one of the modules and true
289 // if one exists. If none is found it returns a fallback location and false.
290 func (s *snapshot) matchErrorToModule(ctx context.Context, pm *source.ParsedModule, goCmdError string) (span.Span, bool, error) {
291         var reference *modfile.Line
292         matches := moduleVersionInErrorRe.FindAllStringSubmatch(goCmdError, -1)
293
294         for i := len(matches) - 1; i >= 0; i-- {
295                 ver := module.Version{Path: matches[i][1], Version: matches[i][2]}
296                 // Any module versions that come from the workspace module should not
297                 // be shown to the user.
298                 if source.IsWorkspaceModuleVersion(ver.Version) {
299                         continue
300                 }
301                 if err := module.Check(ver.Path, ver.Version); err != nil {
302                         continue
303                 }
304                 reference = findModuleReference(pm.File, ver)
305                 if reference != nil {
306                         break
307                 }
308         }
309
310         if reference == nil {
311                 // No match for the module path was found in the go.mod file.
312                 // Show the error on the module declaration, if one exists, or
313                 // just the first line of the file.
314                 if pm.File.Module == nil {
315                         return span.New(pm.URI, span.NewPoint(1, 1, 0), span.Point{}), false, nil
316                 }
317                 spn, err := spanFromPositions(pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End)
318                 return spn, false, err
319         }
320
321         spn, err := spanFromPositions(pm.Mapper, reference.Start, reference.End)
322         return spn, true, err
323 }
324
325 // goCommandDiagnostic creates a diagnostic for a given go command error.
326 func (s *snapshot) goCommandDiagnostic(pm *source.ParsedModule, spn span.Span, goCmdError string) (*source.Diagnostic, error) {
327         rng, err := pm.Mapper.Range(spn)
328         if err != nil {
329                 return nil, err
330         }
331
332         matches := moduleVersionInErrorRe.FindAllStringSubmatch(goCmdError, -1)
333         var innermost *module.Version
334         for i := len(matches) - 1; i >= 0; i-- {
335                 ver := module.Version{Path: matches[i][1], Version: matches[i][2]}
336                 // Any module versions that come from the workspace module should not
337                 // be shown to the user.
338                 if source.IsWorkspaceModuleVersion(ver.Version) {
339                         continue
340                 }
341                 if err := module.Check(ver.Path, ver.Version); err != nil {
342                         continue
343                 }
344                 innermost = &ver
345                 break
346         }
347
348         switch {
349         case strings.Contains(goCmdError, "inconsistent vendoring"):
350                 cmd, err := command.NewVendorCommand("Run go mod vendor", command.URIArg{URI: protocol.URIFromSpanURI(pm.URI)})
351                 if err != nil {
352                         return nil, err
353                 }
354                 return &source.Diagnostic{
355                         URI:      pm.URI,
356                         Range:    rng,
357                         Severity: protocol.SeverityError,
358                         Source:   source.ListError,
359                         Message: `Inconsistent vendoring detected. Please re-run "go mod vendor".
360 See https://github.com/golang/go/issues/39164 for more detail on this issue.`,
361                         SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
362                 }, nil
363
364         case strings.Contains(goCmdError, "updates to go.sum needed"), strings.Contains(goCmdError, "missing go.sum entry"):
365                 var args []protocol.DocumentURI
366                 for _, uri := range s.ModFiles() {
367                         args = append(args, protocol.URIFromSpanURI(uri))
368                 }
369                 tidyCmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: args})
370                 if err != nil {
371                         return nil, err
372                 }
373                 updateCmd, err := command.NewUpdateGoSumCommand("Update go.sum", command.URIArgs{URIs: args})
374                 if err != nil {
375                         return nil, err
376                 }
377                 msg := "go.sum is out of sync with go.mod. Please update it by applying the quick fix."
378                 if innermost != nil {
379                         msg = fmt.Sprintf("go.sum is out of sync with go.mod: entry for %v is missing. Please updating it by applying the quick fix.", innermost)
380                 }
381                 return &source.Diagnostic{
382                         URI:      pm.URI,
383                         Range:    rng,
384                         Severity: protocol.SeverityError,
385                         Source:   source.ListError,
386                         Message:  msg,
387                         SuggestedFixes: []source.SuggestedFix{
388                                 source.SuggestedFixFromCommand(tidyCmd),
389                                 source.SuggestedFixFromCommand(updateCmd),
390                         },
391                 }, nil
392         case strings.Contains(goCmdError, "disabled by GOPROXY=off") && innermost != nil:
393                 title := fmt.Sprintf("Download %v@%v", innermost.Path, innermost.Version)
394                 cmd, err := command.NewAddDependencyCommand(title, command.DependencyArgs{
395                         URI:        protocol.URIFromSpanURI(pm.URI),
396                         AddRequire: false,
397                         GoCmdArgs:  []string{fmt.Sprintf("%v@%v", innermost.Path, innermost.Version)},
398                 })
399                 if err != nil {
400                         return nil, err
401                 }
402                 return &source.Diagnostic{
403                         URI:            pm.URI,
404                         Range:          rng,
405                         Severity:       protocol.SeverityError,
406                         Message:        fmt.Sprintf("%v@%v has not been downloaded", innermost.Path, innermost.Version),
407                         Source:         source.ListError,
408                         SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
409                 }, nil
410         default:
411                 return &source.Diagnostic{
412                         URI:      pm.URI,
413                         Range:    rng,
414                         Severity: protocol.SeverityError,
415                         Source:   source.ListError,
416                         Message:  goCmdError,
417                 }, nil
418         }
419 }
420
421 func findModuleReference(mf *modfile.File, ver module.Version) *modfile.Line {
422         for _, req := range mf.Require {
423                 if req.Mod == ver {
424                         return req.Syntax
425                 }
426         }
427         for _, ex := range mf.Exclude {
428                 if ex.Mod == ver {
429                         return ex.Syntax
430                 }
431         }
432         for _, rep := range mf.Replace {
433                 if rep.New == ver || rep.Old == ver {
434                         return rep.Syntax
435                 }
436         }
437         return nil
438 }