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 / genapijson / generate.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 // Command genapijson generates JSON describing gopls' external-facing API,
6 // including user settings and commands.
7 package main
8
9 import (
10         "bytes"
11         "encoding/json"
12         "flag"
13         "fmt"
14         "go/ast"
15         "go/token"
16         "go/types"
17         "os"
18         "reflect"
19         "strings"
20         "time"
21
22         "golang.org/x/tools/go/ast/astutil"
23         "golang.org/x/tools/go/packages"
24         "golang.org/x/tools/internal/lsp/mod"
25         "golang.org/x/tools/internal/lsp/source"
26 )
27
28 var (
29         output = flag.String("output", "", "output file")
30 )
31
32 func main() {
33         flag.Parse()
34         if err := doMain(); err != nil {
35                 fmt.Fprintf(os.Stderr, "Generation failed: %v\n", err)
36                 os.Exit(1)
37         }
38 }
39
40 func doMain() error {
41         out := os.Stdout
42         if *output != "" {
43                 var err error
44                 out, err = os.OpenFile(*output, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
45                 if err != nil {
46                         return err
47                 }
48                 defer out.Close()
49         }
50
51         content, err := generate()
52         if err != nil {
53                 return err
54         }
55         if _, err := out.Write(content); err != nil {
56                 return err
57         }
58
59         return out.Close()
60 }
61
62 func generate() ([]byte, error) {
63         pkgs, err := packages.Load(
64                 &packages.Config{
65                         Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedDeps,
66                 },
67                 "golang.org/x/tools/internal/lsp/source",
68         )
69         if err != nil {
70                 return nil, err
71         }
72         pkg := pkgs[0]
73
74         api := &source.APIJSON{
75                 Options: map[string][]*source.OptionJSON{},
76         }
77         defaults := source.DefaultOptions()
78         for _, cat := range []reflect.Value{
79                 reflect.ValueOf(defaults.DebuggingOptions),
80                 reflect.ValueOf(defaults.UserOptions),
81                 reflect.ValueOf(defaults.ExperimentalOptions),
82         } {
83                 opts, err := loadOptions(cat, pkg)
84                 if err != nil {
85                         return nil, err
86                 }
87                 catName := strings.TrimSuffix(cat.Type().Name(), "Options")
88                 api.Options[catName] = opts
89         }
90
91         api.Commands, err = loadCommands(pkg)
92         if err != nil {
93                 return nil, err
94         }
95         api.Lenses = loadLenses(api.Commands)
96
97         // Transform the internal command name to the external command name.
98         for _, c := range api.Commands {
99                 c.Command = source.CommandPrefix + c.Command
100         }
101
102         marshaled, err := json.Marshal(api)
103         if err != nil {
104                 return nil, err
105         }
106         buf := bytes.NewBuffer(nil)
107         fmt.Fprintf(buf, "// Code generated by \"golang.org/x/tools/internal/lsp/source/genapijson\"; DO NOT EDIT.\n\npackage source\n\nconst GeneratedAPIJSON = %q\n", string(marshaled))
108         return buf.Bytes(), nil
109 }
110
111 func loadOptions(category reflect.Value, pkg *packages.Package) ([]*source.OptionJSON, error) {
112         // Find the type information and ast.File corresponding to the category.
113         optsType := pkg.Types.Scope().Lookup(category.Type().Name())
114         if optsType == nil {
115                 return nil, fmt.Errorf("could not find %v in scope %v", category.Type().Name(), pkg.Types.Scope())
116         }
117
118         file, err := fileForPos(pkg, optsType.Pos())
119         if err != nil {
120                 return nil, err
121         }
122
123         enums, err := loadEnums(pkg)
124         if err != nil {
125                 return nil, err
126         }
127
128         var opts []*source.OptionJSON
129         optsStruct := optsType.Type().Underlying().(*types.Struct)
130         for i := 0; i < optsStruct.NumFields(); i++ {
131                 // The types field gives us the type.
132                 typesField := optsStruct.Field(i)
133                 path, _ := astutil.PathEnclosingInterval(file, typesField.Pos(), typesField.Pos())
134                 if len(path) < 2 {
135                         return nil, fmt.Errorf("could not find AST node for field %v", typesField)
136                 }
137                 // The AST field gives us the doc.
138                 astField, ok := path[1].(*ast.Field)
139                 if !ok {
140                         return nil, fmt.Errorf("unexpected AST path %v", path)
141                 }
142
143                 // The reflect field gives us the default value.
144                 reflectField := category.FieldByName(typesField.Name())
145                 if !reflectField.IsValid() {
146                         return nil, fmt.Errorf("could not find reflect field for %v", typesField.Name())
147                 }
148
149                 // Format the default value. VSCode exposes settings as JSON, so showing them as JSON is reasonable.
150                 def := reflectField.Interface()
151                 // Durations marshal as nanoseconds, but we want the stringy versions, e.g. "100ms".
152                 if t, ok := def.(time.Duration); ok {
153                         def = t.String()
154                 }
155                 defBytes, err := json.Marshal(def)
156                 if err != nil {
157                         return nil, err
158                 }
159
160                 // Nil values format as "null" so print them as hardcoded empty values.
161                 switch reflectField.Type().Kind() {
162                 case reflect.Map:
163                         if reflectField.IsNil() {
164                                 defBytes = []byte("{}")
165                         }
166                 case reflect.Slice:
167                         if reflectField.IsNil() {
168                                 defBytes = []byte("[]")
169                         }
170                 }
171
172                 typ := typesField.Type().String()
173                 if _, ok := enums[typesField.Type()]; ok {
174                         typ = "enum"
175                 }
176
177                 opts = append(opts, &source.OptionJSON{
178                         Name:       lowerFirst(typesField.Name()),
179                         Type:       typ,
180                         Doc:        lowerFirst(astField.Doc.Text()),
181                         Default:    string(defBytes),
182                         EnumValues: enums[typesField.Type()],
183                 })
184         }
185         return opts, nil
186 }
187
188 func loadEnums(pkg *packages.Package) (map[types.Type][]source.EnumValue, error) {
189         enums := map[types.Type][]source.EnumValue{}
190         for _, name := range pkg.Types.Scope().Names() {
191                 obj := pkg.Types.Scope().Lookup(name)
192                 cnst, ok := obj.(*types.Const)
193                 if !ok {
194                         continue
195                 }
196                 f, err := fileForPos(pkg, cnst.Pos())
197                 if err != nil {
198                         return nil, fmt.Errorf("finding file for %q: %v", cnst.Name(), err)
199                 }
200                 path, _ := astutil.PathEnclosingInterval(f, cnst.Pos(), cnst.Pos())
201                 spec := path[1].(*ast.ValueSpec)
202                 value := cnst.Val().ExactString()
203                 doc := valueDoc(cnst.Name(), value, spec.Doc.Text())
204                 v := source.EnumValue{
205                         Value: value,
206                         Doc:   doc,
207                 }
208                 enums[obj.Type()] = append(enums[obj.Type()], v)
209         }
210         return enums, nil
211 }
212
213 // valueDoc transforms a docstring documenting an constant identifier to a
214 // docstring documenting its value.
215 //
216 // If doc is of the form "Foo is a bar", it returns '`"fooValue"` is a bar'. If
217 // doc is non-standard ("this value is a bar"), it returns '`"fooValue"`: this
218 // value is a bar'.
219 func valueDoc(name, value, doc string) string {
220         if doc == "" {
221                 return ""
222         }
223         if strings.HasPrefix(doc, name) {
224                 // docstring in standard form. Replace the subject with value.
225                 return fmt.Sprintf("`%s`%s", value, doc[len(name):])
226         }
227         return fmt.Sprintf("`%s`: %s", value, doc)
228 }
229
230 func loadCommands(pkg *packages.Package) ([]*source.CommandJSON, error) {
231         // The code that defines commands is much more complicated than the
232         // code that defines options, so reading comments for the Doc is very
233         // fragile. If this causes problems, we should switch to a dynamic
234         // approach and put the doc in the Commands struct rather than reading
235         // from the source code.
236
237         // Find the Commands slice.
238         typesSlice := pkg.Types.Scope().Lookup("Commands")
239         f, err := fileForPos(pkg, typesSlice.Pos())
240         if err != nil {
241                 return nil, err
242         }
243         path, _ := astutil.PathEnclosingInterval(f, typesSlice.Pos(), typesSlice.Pos())
244         vspec := path[1].(*ast.ValueSpec)
245         var astSlice *ast.CompositeLit
246         for i, name := range vspec.Names {
247                 if name.Name == "Commands" {
248                         astSlice = vspec.Values[i].(*ast.CompositeLit)
249                 }
250         }
251
252         var commands []*source.CommandJSON
253
254         // Parse the objects it contains.
255         for _, elt := range astSlice.Elts {
256                 // Find the composite literal of the Command.
257                 typesCommand := pkg.TypesInfo.ObjectOf(elt.(*ast.Ident))
258                 path, _ := astutil.PathEnclosingInterval(f, typesCommand.Pos(), typesCommand.Pos())
259                 vspec := path[1].(*ast.ValueSpec)
260
261                 var astCommand ast.Expr
262                 for i, name := range vspec.Names {
263                         if name.Name == typesCommand.Name() {
264                                 astCommand = vspec.Values[i]
265                         }
266                 }
267
268                 // Read the Name and Title fields of the literal.
269                 var name, title string
270                 ast.Inspect(astCommand, func(n ast.Node) bool {
271                         kv, ok := n.(*ast.KeyValueExpr)
272                         if ok {
273                                 k := kv.Key.(*ast.Ident).Name
274                                 switch k {
275                                 case "Name":
276                                         name = strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`)
277                                 case "Title":
278                                         title = strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`)
279                                 }
280                         }
281                         return true
282                 })
283
284                 if title == "" {
285                         title = name
286                 }
287
288                 // Conventionally, the doc starts with the name of the variable.
289                 // Replace it with the name of the command.
290                 doc := vspec.Doc.Text()
291                 doc = strings.Replace(doc, typesCommand.Name(), name, 1)
292
293                 commands = append(commands, &source.CommandJSON{
294                         Command: name,
295                         Title:   title,
296                         Doc:     doc,
297                 })
298         }
299         return commands, nil
300 }
301
302 func loadLenses(commands []*source.CommandJSON) []*source.LensJSON {
303         lensNames := map[string]struct{}{}
304         for k := range source.LensFuncs() {
305                 lensNames[k] = struct{}{}
306         }
307         for k := range mod.LensFuncs() {
308                 lensNames[k] = struct{}{}
309         }
310
311         var lenses []*source.LensJSON
312
313         for _, cmd := range commands {
314                 if _, ok := lensNames[cmd.Command]; ok {
315                         lenses = append(lenses, &source.LensJSON{
316                                 Lens:  cmd.Command,
317                                 Title: cmd.Title,
318                                 Doc:   cmd.Doc,
319                         })
320                 }
321         }
322         return lenses
323 }
324
325 func lowerFirst(x string) string {
326         if x == "" {
327                 return x
328         }
329         return strings.ToLower(x[:1]) + x[1:]
330 }
331
332 func fileForPos(pkg *packages.Package, pos token.Pos) (*ast.File, error) {
333         fset := pkg.Fset
334         for _, f := range pkg.Syntax {
335                 if fset.Position(f.Pos()).Filename == fset.Position(pos).Filename {
336                         return f, nil
337                 }
338         }
339         return nil, fmt.Errorf("no file for pos %v", pos)
340 }