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