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