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