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 / source / gc_annotations.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 source
6
7 import (
8         "bytes"
9         "context"
10         "encoding/json"
11         "fmt"
12         "io/ioutil"
13         "os"
14         "path/filepath"
15         "strings"
16
17         "golang.org/x/tools/internal/gocommand"
18         "golang.org/x/tools/internal/lsp/protocol"
19         "golang.org/x/tools/internal/span"
20 )
21
22 func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[VersionedFileIdentity][]*Diagnostic, error) {
23         outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid()))
24
25         if err := os.MkdirAll(outDir, 0700); err != nil {
26                 return nil, err
27         }
28         tmpFile, err := ioutil.TempFile(os.TempDir(), "gopls-x")
29         if err != nil {
30                 return nil, err
31         }
32         defer os.Remove(tmpFile.Name())
33
34         outDirURI := span.URIFromPath(outDir)
35         // GC details doesn't handle Windows URIs in the form of "file:///C:/...",
36         // so rewrite them to "file://C:/...". See golang/go#41614.
37         if !strings.HasPrefix(outDir, "/") {
38                 outDirURI = span.URI(strings.Replace(string(outDirURI), "file:///", "file://", 1))
39         }
40         inv := &gocommand.Invocation{
41                 Verb: "build",
42                 Args: []string{
43                         fmt.Sprintf("-gcflags=-json=0,%s", outDirURI),
44                         fmt.Sprintf("-o=%s", tmpFile.Name()),
45                         ".",
46                 },
47                 WorkingDir: pkgDir.Filename(),
48         }
49         _, err = snapshot.RunGoCommandDirect(ctx, Normal, inv)
50         if err != nil {
51                 return nil, err
52         }
53         files, err := findJSONFiles(outDir)
54         if err != nil {
55                 return nil, err
56         }
57         reports := make(map[VersionedFileIdentity][]*Diagnostic)
58         opts := snapshot.View().Options()
59         var parseError error
60         for _, fn := range files {
61                 uri, diagnostics, err := parseDetailsFile(fn, opts)
62                 if err != nil {
63                         // expect errors for all the files, save 1
64                         parseError = err
65                 }
66                 fh := snapshot.FindFile(uri)
67                 if fh == nil {
68                         continue
69                 }
70                 if pkgDir.Filename() != filepath.Dir(fh.URI().Filename()) {
71                         // https://github.com/golang/go/issues/42198
72                         // sometimes the detail diagnostics generated for files
73                         // outside the package can never be taken back.
74                         continue
75                 }
76                 reports[fh.VersionedFileIdentity()] = diagnostics
77         }
78         return reports, parseError
79 }
80
81 func parseDetailsFile(filename string, options *Options) (span.URI, []*Diagnostic, error) {
82         buf, err := ioutil.ReadFile(filename)
83         if err != nil {
84                 return "", nil, err
85         }
86         var (
87                 uri         span.URI
88                 i           int
89                 diagnostics []*Diagnostic
90         )
91         type metadata struct {
92                 File string `json:"file,omitempty"`
93         }
94         for dec := json.NewDecoder(bytes.NewReader(buf)); dec.More(); {
95                 // The first element always contains metadata.
96                 if i == 0 {
97                         i++
98                         m := new(metadata)
99                         if err := dec.Decode(m); err != nil {
100                                 return "", nil, err
101                         }
102                         if !strings.HasSuffix(m.File, ".go") {
103                                 continue // <autogenerated>
104                         }
105                         uri = span.URIFromPath(m.File)
106                         continue
107                 }
108                 d := new(protocol.Diagnostic)
109                 if err := dec.Decode(d); err != nil {
110                         return "", nil, err
111                 }
112                 msg := d.Code.(string)
113                 if msg != "" {
114                         msg = fmt.Sprintf("%s(%s)", msg, d.Message)
115                 }
116                 if skipDiagnostic(msg, d.Source, options) {
117                         continue
118                 }
119                 var related []RelatedInformation
120                 for _, ri := range d.RelatedInformation {
121                         related = append(related, RelatedInformation{
122                                 URI:     ri.Location.URI.SpanURI(),
123                                 Range:   zeroIndexedRange(ri.Location.Range),
124                                 Message: ri.Message,
125                         })
126                 }
127                 diagnostic := &Diagnostic{
128                         Range:    zeroIndexedRange(d.Range),
129                         Message:  msg,
130                         Severity: d.Severity,
131                         Source:   d.Source,
132                         Tags:     d.Tags,
133                         Related:  related,
134                 }
135                 diagnostics = append(diagnostics, diagnostic)
136                 i++
137         }
138         return uri, diagnostics, nil
139 }
140
141 // skipDiagnostic reports whether a given diagnostic should be shown to the end
142 // user, given the current options.
143 func skipDiagnostic(msg, source string, o *Options) bool {
144         if source != "go compiler" {
145                 return false
146         }
147         switch {
148         case o.Annotations["noInline"]:
149                 return strings.HasPrefix(msg, "canInline") ||
150                         strings.HasPrefix(msg, "cannotInline") ||
151                         strings.HasPrefix(msg, "inlineCall")
152         case o.Annotations["noEscape"]:
153                 return strings.HasPrefix(msg, "escape") || msg == "leak"
154         case o.Annotations["noNilcheck"]:
155                 return strings.HasPrefix(msg, "nilcheck")
156         case o.Annotations["noBounds"]:
157                 return strings.HasPrefix(msg, "isInBounds") ||
158                         strings.HasPrefix(msg, "isSliceInBounds")
159         }
160         return false
161 }
162
163 // The range produced by the compiler is 1-indexed, so subtract range by 1.
164 func zeroIndexedRange(rng protocol.Range) protocol.Range {
165         return protocol.Range{
166                 Start: protocol.Position{
167                         Line:      rng.Start.Line - 1,
168                         Character: rng.Start.Character - 1,
169                 },
170                 End: protocol.Position{
171                         Line:      rng.End.Line - 1,
172                         Character: rng.End.Character - 1,
173                 },
174         }
175 }
176
177 func findJSONFiles(dir string) ([]string, error) {
178         ans := []string{}
179         f := func(path string, fi os.FileInfo, _ error) error {
180                 if fi.IsDir() {
181                         return nil
182                 }
183                 if strings.HasSuffix(path, ".json") {
184                         ans = append(ans, path)
185                 }
186                 return nil
187         }
188         err := filepath.Walk(dir, f)
189         return ans, err
190 }