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