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 / cache / mod_tidy.go
1 // Copyright 2020 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         "go/ast"
11         "io/ioutil"
12         "os"
13         "path/filepath"
14         "sort"
15         "strconv"
16         "strings"
17
18         "golang.org/x/mod/modfile"
19         "golang.org/x/tools/internal/event"
20         "golang.org/x/tools/internal/gocommand"
21         "golang.org/x/tools/internal/lsp/debug/tag"
22         "golang.org/x/tools/internal/lsp/diff"
23         "golang.org/x/tools/internal/lsp/protocol"
24         "golang.org/x/tools/internal/lsp/source"
25         "golang.org/x/tools/internal/memoize"
26         "golang.org/x/tools/internal/span"
27 )
28
29 type modTidyKey struct {
30         sessionID       string
31         env             string
32         gomod           source.FileIdentity
33         imports         string
34         unsavedOverlays string
35         view            string
36 }
37
38 type modTidyHandle struct {
39         handle *memoize.Handle
40 }
41
42 type modTidyData struct {
43         tidied *source.TidiedModule
44         err    error
45 }
46
47 func (mth *modTidyHandle) tidy(ctx context.Context, snapshot *snapshot) (*source.TidiedModule, error) {
48         v, err := mth.handle.Get(ctx, snapshot.generation, snapshot)
49         if err != nil {
50                 return nil, err
51         }
52         data := v.(*modTidyData)
53         return data.tidied, data.err
54 }
55
56 func (s *snapshot) ModTidy(ctx context.Context, fh source.FileHandle) (*source.TidiedModule, error) {
57         if fh.Kind() != source.Mod {
58                 return nil, fmt.Errorf("%s is not a go.mod file", fh.URI())
59         }
60         if handle := s.getModTidyHandle(fh.URI()); handle != nil {
61                 return handle.tidy(ctx, s)
62         }
63         // If the file handle is an overlay, it may not be written to disk.
64         // The go.mod file has to be on disk for `go mod tidy` to work.
65         if _, ok := fh.(*overlay); ok {
66                 if info, _ := os.Stat(fh.URI().Filename()); info == nil {
67                         return nil, source.ErrNoModOnDisk
68                 }
69         }
70         workspacePkgs, err := s.WorkspacePackages(ctx)
71         if err != nil {
72                 if tm, ok := s.parseModErrors(ctx, fh, err); ok {
73                         return tm, nil
74                 }
75                 return nil, err
76         }
77         importHash, err := hashImports(ctx, workspacePkgs)
78         if err != nil {
79                 return nil, err
80         }
81
82         s.mu.Lock()
83         overlayHash := hashUnsavedOverlays(s.files)
84         s.mu.Unlock()
85
86         key := modTidyKey{
87                 sessionID:       s.view.session.id,
88                 view:            s.view.folder.Filename(),
89                 imports:         importHash,
90                 unsavedOverlays: overlayHash,
91                 gomod:           fh.FileIdentity(),
92                 env:             hashEnv(s),
93         }
94         h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
95                 ctx, done := event.Start(ctx, "cache.ModTidyHandle", tag.URI.Of(fh.URI()))
96                 defer done()
97
98                 snapshot := arg.(*snapshot)
99                 pm, err := snapshot.ParseMod(ctx, fh)
100                 if err != nil || len(pm.ParseErrors) > 0 {
101                         if err == nil {
102                                 err = fmt.Errorf("could not parse module to tidy: %v", pm.ParseErrors)
103                         }
104                         var errors []source.Error
105                         if pm != nil {
106                                 errors = pm.ParseErrors
107                         }
108                         return &modTidyData{
109                                 tidied: &source.TidiedModule{
110                                         Parsed: pm,
111                                         Errors: errors,
112                                 },
113                                 err: err,
114                         }
115                 }
116                 inv := &gocommand.Invocation{
117                         Verb:       "mod",
118                         Args:       []string{"tidy"},
119                         WorkingDir: filepath.Dir(fh.URI().Filename()),
120                 }
121                 tmpURI, inv, cleanup, err := snapshot.goCommandInvocation(ctx, source.WriteTemporaryModFile, inv)
122                 if err != nil {
123                         return &modTidyData{err: err}
124                 }
125                 // Keep the temporary go.mod file around long enough to parse it.
126                 defer cleanup()
127
128                 if _, err := s.view.session.gocmdRunner.Run(ctx, *inv); err != nil {
129                         return &modTidyData{err: err}
130                 }
131                 // Go directly to disk to get the temporary mod file, since it is
132                 // always on disk.
133                 tempContents, err := ioutil.ReadFile(tmpURI.Filename())
134                 if err != nil {
135                         return &modTidyData{err: err}
136                 }
137                 ideal, err := modfile.Parse(tmpURI.Filename(), tempContents, nil)
138                 if err != nil {
139                         // We do not need to worry about the temporary file's parse errors
140                         // since it has been "tidied".
141                         return &modTidyData{err: err}
142                 }
143                 // Compare the original and tidied go.mod files to compute errors and
144                 // suggested fixes.
145                 errors, err := modTidyErrors(ctx, snapshot, pm, ideal, workspacePkgs)
146                 if err != nil {
147                         return &modTidyData{err: err}
148                 }
149                 return &modTidyData{
150                         tidied: &source.TidiedModule{
151                                 Errors:        errors,
152                                 Parsed:        pm,
153                                 TidiedContent: tempContents,
154                         },
155                 }
156         }, nil)
157
158         mth := &modTidyHandle{handle: h}
159         s.mu.Lock()
160         s.modTidyHandles[fh.URI()] = mth
161         s.mu.Unlock()
162
163         return mth.tidy(ctx, s)
164 }
165
166 func (s *snapshot) parseModErrors(ctx context.Context, fh source.FileHandle, err error) (*source.TidiedModule, bool) {
167         if err == nil {
168                 return nil, false
169         }
170         switch {
171         // Match on common error messages. This is really hacky, but I'm not sure
172         // of any better way. This can be removed when golang/go#39164 is resolved.
173         case strings.Contains(err.Error(), "inconsistent vendoring"):
174                 pmf, err := s.ParseMod(ctx, fh)
175                 if err != nil {
176                         return nil, false
177                 }
178                 if pmf.File.Module == nil || pmf.File.Module.Syntax == nil {
179                         return nil, false
180                 }
181                 rng, err := rangeFromPositions(pmf.Mapper, pmf.File.Module.Syntax.Start, pmf.File.Module.Syntax.End)
182                 if err != nil {
183                         return nil, false
184                 }
185                 args, err := source.MarshalArgs(protocol.URIFromSpanURI(fh.URI()))
186                 if err != nil {
187                         return nil, false
188                 }
189                 return &source.TidiedModule{
190                         Parsed: pmf,
191                         Errors: []source.Error{{
192                                 URI:   fh.URI(),
193                                 Range: rng,
194                                 Kind:  source.ListError,
195                                 Message: `Inconsistent vendoring detected. Please re-run "go mod vendor".
196 See https://github.com/golang/go/issues/39164 for more detail on this issue.`,
197                                 SuggestedFixes: []source.SuggestedFix{{
198                                         Command: &protocol.Command{
199                                                 Command:   source.CommandVendor.ID(),
200                                                 Title:     source.CommandVendor.Title,
201                                                 Arguments: args,
202                                         },
203                                 }},
204                         }},
205                 }, true
206         }
207         return nil, false
208 }
209
210 func hashImports(ctx context.Context, wsPackages []source.Package) (string, error) {
211         results := make(map[string]bool)
212         var imports []string
213         for _, pkg := range wsPackages {
214                 for _, path := range pkg.Imports() {
215                         imp := path.PkgPath()
216                         if _, ok := results[imp]; !ok {
217                                 results[imp] = true
218                                 imports = append(imports, imp)
219                         }
220                 }
221         }
222         sort.Strings(imports)
223         hashed := strings.Join(imports, ",")
224         return hashContents([]byte(hashed)), nil
225 }
226
227 // modTidyErrors computes the differences between the original and tidied
228 // go.mod files to produce diagnostic and suggested fixes. Some diagnostics
229 // may appear on the Go files that import packages from missing modules.
230 func modTidyErrors(ctx context.Context, snapshot source.Snapshot, pm *source.ParsedModule, ideal *modfile.File, workspacePkgs []source.Package) (errors []source.Error, err error) {
231         // First, determine which modules are unused and which are missing from the
232         // original go.mod file.
233         var (
234                 unused          = make(map[string]*modfile.Require, len(pm.File.Require))
235                 missing         = make(map[string]*modfile.Require, len(ideal.Require))
236                 wrongDirectness = make(map[string]*modfile.Require, len(pm.File.Require))
237         )
238         for _, req := range pm.File.Require {
239                 unused[req.Mod.Path] = req
240         }
241         for _, req := range ideal.Require {
242                 origReq := unused[req.Mod.Path]
243                 if origReq == nil {
244                         missing[req.Mod.Path] = req
245                         continue
246                 } else if origReq.Indirect != req.Indirect {
247                         wrongDirectness[req.Mod.Path] = origReq
248                 }
249                 delete(unused, req.Mod.Path)
250         }
251         for _, req := range unused {
252                 srcErr, err := unusedError(pm.Mapper, req, snapshot.View().Options().ComputeEdits)
253                 if err != nil {
254                         return nil, err
255                 }
256                 errors = append(errors, srcErr)
257         }
258         for _, req := range wrongDirectness {
259                 // Handle dependencies that are incorrectly labeled indirect and
260                 // vice versa.
261                 srcErr, err := directnessError(pm.Mapper, req, snapshot.View().Options().ComputeEdits)
262                 if err != nil {
263                         return nil, err
264                 }
265                 errors = append(errors, srcErr)
266         }
267         // Next, compute any diagnostics for modules that are missing from the
268         // go.mod file. The fixes will be for the go.mod file, but the
269         // diagnostics should also appear in both the go.mod file and the import
270         // statements in the Go files in which the dependencies are used.
271         missingModuleFixes := map[*modfile.Require][]source.SuggestedFix{}
272         for _, req := range missing {
273                 srcErr, err := missingModuleError(snapshot, pm, req)
274                 if err != nil {
275                         return nil, err
276                 }
277                 missingModuleFixes[req] = srcErr.SuggestedFixes
278                 errors = append(errors, srcErr)
279         }
280         // Add diagnostics for missing modules anywhere they are imported in the
281         // workspace.
282         for _, pkg := range workspacePkgs {
283                 missingImports := map[string]*modfile.Require{}
284                 for _, imp := range pkg.Imports() {
285                         if req, ok := missing[imp.PkgPath()]; ok {
286                                 missingImports[imp.PkgPath()] = req
287                                 break
288                         }
289                         // If the import is a package of the dependency, then add the
290                         // package to the map, this will eliminate the need to do this
291                         // prefix package search on each import for each file.
292                         // Example:
293                         //
294                         // import (
295                         //   "golang.org/x/tools/go/expect"
296                         //   "golang.org/x/tools/go/packages"
297                         // )
298                         // They both are related to the same module: "golang.org/x/tools".
299                         var match string
300                         for _, req := range ideal.Require {
301                                 if strings.HasPrefix(imp.PkgPath(), req.Mod.Path) && len(req.Mod.Path) > len(match) {
302                                         match = req.Mod.Path
303                                 }
304                         }
305                         if req, ok := missing[match]; ok {
306                                 missingImports[imp.PkgPath()] = req
307                         }
308                 }
309                 // None of this package's imports are from missing modules.
310                 if len(missingImports) == 0 {
311                         continue
312                 }
313                 for _, pgf := range pkg.CompiledGoFiles() {
314                         file, m := pgf.File, pgf.Mapper
315                         if file == nil || m == nil {
316                                 continue
317                         }
318                         imports := make(map[string]*ast.ImportSpec)
319                         for _, imp := range file.Imports {
320                                 if imp.Path == nil {
321                                         continue
322                                 }
323                                 if target, err := strconv.Unquote(imp.Path.Value); err == nil {
324                                         imports[target] = imp
325                                 }
326                         }
327                         if len(imports) == 0 {
328                                 continue
329                         }
330                         for importPath, req := range missingImports {
331                                 imp, ok := imports[importPath]
332                                 if !ok {
333                                         continue
334                                 }
335                                 fixes, ok := missingModuleFixes[req]
336                                 if !ok {
337                                         return nil, fmt.Errorf("no missing module fix for %q (%q)", importPath, req.Mod.Path)
338                                 }
339                                 srcErr, err := missingModuleForImport(snapshot, m, imp, req, fixes)
340                                 if err != nil {
341                                         return nil, err
342                                 }
343                                 errors = append(errors, srcErr)
344                         }
345                 }
346         }
347         return errors, nil
348 }
349
350 // unusedError returns a source.Error for an unused require.
351 func unusedError(m *protocol.ColumnMapper, req *modfile.Require, computeEdits diff.ComputeEdits) (source.Error, error) {
352         rng, err := rangeFromPositions(m, req.Syntax.Start, req.Syntax.End)
353         if err != nil {
354                 return source.Error{}, err
355         }
356         args, err := source.MarshalArgs(m.URI, false, []string{req.Mod.Path + "@none"})
357         if err != nil {
358                 return source.Error{}, err
359         }
360         return source.Error{
361                 Category: source.GoModTidy,
362                 Message:  fmt.Sprintf("%s is not used in this module", req.Mod.Path),
363                 Range:    rng,
364                 URI:      m.URI,
365                 SuggestedFixes: []source.SuggestedFix{{
366                         Title: fmt.Sprintf("Remove dependency: %s", req.Mod.Path),
367                         Command: &protocol.Command{
368                                 Title:     source.CommandRemoveDependency.Title,
369                                 Command:   source.CommandRemoveDependency.ID(),
370                                 Arguments: args,
371                         },
372                 }},
373         }, nil
374 }
375
376 // directnessError extracts errors when a dependency is labeled indirect when
377 // it should be direct and vice versa.
378 func directnessError(m *protocol.ColumnMapper, req *modfile.Require, computeEdits diff.ComputeEdits) (source.Error, error) {
379         rng, err := rangeFromPositions(m, req.Syntax.Start, req.Syntax.End)
380         if err != nil {
381                 return source.Error{}, err
382         }
383         direction := "indirect"
384         if req.Indirect {
385                 direction = "direct"
386
387                 // If the dependency should be direct, just highlight the // indirect.
388                 if comments := req.Syntax.Comment(); comments != nil && len(comments.Suffix) > 0 {
389                         end := comments.Suffix[0].Start
390                         end.LineRune += len(comments.Suffix[0].Token)
391                         end.Byte += len([]byte(comments.Suffix[0].Token))
392                         rng, err = rangeFromPositions(m, comments.Suffix[0].Start, end)
393                         if err != nil {
394                                 return source.Error{}, err
395                         }
396                 }
397         }
398         // If the dependency should be indirect, add the // indirect.
399         edits, err := switchDirectness(req, m, computeEdits)
400         if err != nil {
401                 return source.Error{}, err
402         }
403         return source.Error{
404                 Message:  fmt.Sprintf("%s should be %s", req.Mod.Path, direction),
405                 Range:    rng,
406                 URI:      m.URI,
407                 Category: source.GoModTidy,
408                 SuggestedFixes: []source.SuggestedFix{{
409                         Title: fmt.Sprintf("Change %s to %s", req.Mod.Path, direction),
410                         Edits: map[span.URI][]protocol.TextEdit{
411                                 m.URI: edits,
412                         },
413                 }},
414         }, nil
415 }
416
417 func missingModuleError(snapshot source.Snapshot, pm *source.ParsedModule, req *modfile.Require) (source.Error, error) {
418         var rng protocol.Range
419         // Default to the start of the file if there is no module declaration.
420         if pm.File != nil && pm.File.Module != nil && pm.File.Module.Syntax != nil {
421                 start, end := pm.File.Module.Syntax.Span()
422                 var err error
423                 rng, err = rangeFromPositions(pm.Mapper, start, end)
424                 if err != nil {
425                         return source.Error{}, err
426                 }
427         }
428         args, err := source.MarshalArgs(pm.Mapper.URI, !req.Indirect, []string{req.Mod.Path + "@" + req.Mod.Version})
429         if err != nil {
430                 return source.Error{}, err
431         }
432         return source.Error{
433                 URI:      pm.Mapper.URI,
434                 Range:    rng,
435                 Message:  fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
436                 Category: source.GoModTidy,
437                 Kind:     source.ModTidyError,
438                 SuggestedFixes: []source.SuggestedFix{{
439                         Title: fmt.Sprintf("Add %s to your go.mod file", req.Mod.Path),
440                         Command: &protocol.Command{
441                                 Title:     source.CommandAddDependency.Title,
442                                 Command:   source.CommandAddDependency.ID(),
443                                 Arguments: args,
444                         },
445                 }},
446         }, nil
447 }
448
449 // switchDirectness gets the edits needed to change an indirect dependency to
450 // direct and vice versa.
451 func switchDirectness(req *modfile.Require, m *protocol.ColumnMapper, computeEdits diff.ComputeEdits) ([]protocol.TextEdit, error) {
452         // We need a private copy of the parsed go.mod file, since we're going to
453         // modify it.
454         copied, err := modfile.Parse("", m.Content, nil)
455         if err != nil {
456                 return nil, err
457         }
458         // Change the directness in the matching require statement. To avoid
459         // reordering the require statements, rewrite all of them.
460         var requires []*modfile.Require
461         for _, r := range copied.Require {
462                 if r.Mod.Path == req.Mod.Path {
463                         requires = append(requires, &modfile.Require{
464                                 Mod:      r.Mod,
465                                 Syntax:   r.Syntax,
466                                 Indirect: !r.Indirect,
467                         })
468                         continue
469                 }
470                 requires = append(requires, r)
471         }
472         copied.SetRequire(requires)
473         newContent, err := copied.Format()
474         if err != nil {
475                 return nil, err
476         }
477         // Calculate the edits to be made due to the change.
478         diff := computeEdits(m.URI, string(m.Content), string(newContent))
479         return source.ToProtocolEdits(m, diff)
480 }
481
482 // missingModuleForImport creates an error for a given import path that comes
483 // from a missing module.
484 func missingModuleForImport(snapshot source.Snapshot, m *protocol.ColumnMapper, imp *ast.ImportSpec, req *modfile.Require, fixes []source.SuggestedFix) (source.Error, error) {
485         if req.Syntax == nil {
486                 return source.Error{}, fmt.Errorf("no syntax for %v", req)
487         }
488         spn, err := span.NewRange(snapshot.FileSet(), imp.Path.Pos(), imp.Path.End()).Span()
489         if err != nil {
490                 return source.Error{}, err
491         }
492         rng, err := m.Range(spn)
493         if err != nil {
494                 return source.Error{}, err
495         }
496         return source.Error{
497                 Category:       source.GoModTidy,
498                 URI:            m.URI,
499                 Range:          rng,
500                 Message:        fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
501                 Kind:           source.ModTidyError,
502                 SuggestedFixes: fixes,
503         }, nil
504 }
505
506 func rangeFromPositions(m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {
507         toPoint := func(offset int) (span.Point, error) {
508                 l, c, err := m.Converter.ToPosition(offset)
509                 if err != nil {
510                         return span.Point{}, err
511                 }
512                 return span.NewPoint(l, c, offset), nil
513         }
514         start, err := toPoint(s.Byte)
515         if err != nil {
516                 return protocol.Range{}, err
517         }
518         end, err := toPoint(e.Byte)
519         if err != nil {
520                 return protocol.Range{}, err
521         }
522         return m.Range(span.New(m.URI, start, end))
523 }