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
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/internal/lsp/source/genapijson/generate.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201105173854-bc9fc8d8c4bc/internal/lsp/source/genapijson/generate.go
new file mode 100644 (file)
index 0000000..47a0af7
--- /dev/null
@@ -0,0 +1,340 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command genapijson generates JSON describing gopls' external-facing API,
+// including user settings and commands.
+package main
+
+import (
+       "bytes"
+       "encoding/json"
+       "flag"
+       "fmt"
+       "go/ast"
+       "go/token"
+       "go/types"
+       "os"
+       "reflect"
+       "strings"
+       "time"
+
+       "golang.org/x/tools/go/ast/astutil"
+       "golang.org/x/tools/go/packages"
+       "golang.org/x/tools/internal/lsp/mod"
+       "golang.org/x/tools/internal/lsp/source"
+)
+
+var (
+       output = flag.String("output", "", "output file")
+)
+
+func main() {
+       flag.Parse()
+       if err := doMain(); err != nil {
+               fmt.Fprintf(os.Stderr, "Generation failed: %v\n", err)
+               os.Exit(1)
+       }
+}
+
+func doMain() error {
+       out := os.Stdout
+       if *output != "" {
+               var err error
+               out, err = os.OpenFile(*output, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
+               if err != nil {
+                       return err
+               }
+               defer out.Close()
+       }
+
+       content, err := generate()
+       if err != nil {
+               return err
+       }
+       if _, err := out.Write(content); err != nil {
+               return err
+       }
+
+       return out.Close()
+}
+
+func generate() ([]byte, error) {
+       pkgs, err := packages.Load(
+               &packages.Config{
+                       Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedDeps,
+               },
+               "golang.org/x/tools/internal/lsp/source",
+       )
+       if err != nil {
+               return nil, err
+       }
+       pkg := pkgs[0]
+
+       api := &source.APIJSON{
+               Options: map[string][]*source.OptionJSON{},
+       }
+       defaults := source.DefaultOptions()
+       for _, cat := range []reflect.Value{
+               reflect.ValueOf(defaults.DebuggingOptions),
+               reflect.ValueOf(defaults.UserOptions),
+               reflect.ValueOf(defaults.ExperimentalOptions),
+       } {
+               opts, err := loadOptions(cat, pkg)
+               if err != nil {
+                       return nil, err
+               }
+               catName := strings.TrimSuffix(cat.Type().Name(), "Options")
+               api.Options[catName] = opts
+       }
+
+       api.Commands, err = loadCommands(pkg)
+       if err != nil {
+               return nil, err
+       }
+       api.Lenses = loadLenses(api.Commands)
+
+       // Transform the internal command name to the external command name.
+       for _, c := range api.Commands {
+               c.Command = source.CommandPrefix + c.Command
+       }
+
+       marshaled, err := json.Marshal(api)
+       if err != nil {
+               return nil, err
+       }
+       buf := bytes.NewBuffer(nil)
+       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))
+       return buf.Bytes(), nil
+}
+
+func loadOptions(category reflect.Value, pkg *packages.Package) ([]*source.OptionJSON, error) {
+       // Find the type information and ast.File corresponding to the category.
+       optsType := pkg.Types.Scope().Lookup(category.Type().Name())
+       if optsType == nil {
+               return nil, fmt.Errorf("could not find %v in scope %v", category.Type().Name(), pkg.Types.Scope())
+       }
+
+       file, err := fileForPos(pkg, optsType.Pos())
+       if err != nil {
+               return nil, err
+       }
+
+       enums, err := loadEnums(pkg)
+       if err != nil {
+               return nil, err
+       }
+
+       var opts []*source.OptionJSON
+       optsStruct := optsType.Type().Underlying().(*types.Struct)
+       for i := 0; i < optsStruct.NumFields(); i++ {
+               // The types field gives us the type.
+               typesField := optsStruct.Field(i)
+               path, _ := astutil.PathEnclosingInterval(file, typesField.Pos(), typesField.Pos())
+               if len(path) < 2 {
+                       return nil, fmt.Errorf("could not find AST node for field %v", typesField)
+               }
+               // The AST field gives us the doc.
+               astField, ok := path[1].(*ast.Field)
+               if !ok {
+                       return nil, fmt.Errorf("unexpected AST path %v", path)
+               }
+
+               // The reflect field gives us the default value.
+               reflectField := category.FieldByName(typesField.Name())
+               if !reflectField.IsValid() {
+                       return nil, fmt.Errorf("could not find reflect field for %v", typesField.Name())
+               }
+
+               // Format the default value. VSCode exposes settings as JSON, so showing them as JSON is reasonable.
+               def := reflectField.Interface()
+               // Durations marshal as nanoseconds, but we want the stringy versions, e.g. "100ms".
+               if t, ok := def.(time.Duration); ok {
+                       def = t.String()
+               }
+               defBytes, err := json.Marshal(def)
+               if err != nil {
+                       return nil, err
+               }
+
+               // Nil values format as "null" so print them as hardcoded empty values.
+               switch reflectField.Type().Kind() {
+               case reflect.Map:
+                       if reflectField.IsNil() {
+                               defBytes = []byte("{}")
+                       }
+               case reflect.Slice:
+                       if reflectField.IsNil() {
+                               defBytes = []byte("[]")
+                       }
+               }
+
+               typ := typesField.Type().String()
+               if _, ok := enums[typesField.Type()]; ok {
+                       typ = "enum"
+               }
+
+               opts = append(opts, &source.OptionJSON{
+                       Name:       lowerFirst(typesField.Name()),
+                       Type:       typ,
+                       Doc:        lowerFirst(astField.Doc.Text()),
+                       Default:    string(defBytes),
+                       EnumValues: enums[typesField.Type()],
+               })
+       }
+       return opts, nil
+}
+
+func loadEnums(pkg *packages.Package) (map[types.Type][]source.EnumValue, error) {
+       enums := map[types.Type][]source.EnumValue{}
+       for _, name := range pkg.Types.Scope().Names() {
+               obj := pkg.Types.Scope().Lookup(name)
+               cnst, ok := obj.(*types.Const)
+               if !ok {
+                       continue
+               }
+               f, err := fileForPos(pkg, cnst.Pos())
+               if err != nil {
+                       return nil, fmt.Errorf("finding file for %q: %v", cnst.Name(), err)
+               }
+               path, _ := astutil.PathEnclosingInterval(f, cnst.Pos(), cnst.Pos())
+               spec := path[1].(*ast.ValueSpec)
+               value := cnst.Val().ExactString()
+               doc := valueDoc(cnst.Name(), value, spec.Doc.Text())
+               v := source.EnumValue{
+                       Value: value,
+                       Doc:   doc,
+               }
+               enums[obj.Type()] = append(enums[obj.Type()], v)
+       }
+       return enums, nil
+}
+
+// valueDoc transforms a docstring documenting an constant identifier to a
+// docstring documenting its value.
+//
+// If doc is of the form "Foo is a bar", it returns '`"fooValue"` is a bar'. If
+// doc is non-standard ("this value is a bar"), it returns '`"fooValue"`: this
+// value is a bar'.
+func valueDoc(name, value, doc string) string {
+       if doc == "" {
+               return ""
+       }
+       if strings.HasPrefix(doc, name) {
+               // docstring in standard form. Replace the subject with value.
+               return fmt.Sprintf("`%s`%s", value, doc[len(name):])
+       }
+       return fmt.Sprintf("`%s`: %s", value, doc)
+}
+
+func loadCommands(pkg *packages.Package) ([]*source.CommandJSON, error) {
+       // The code that defines commands is much more complicated than the
+       // code that defines options, so reading comments for the Doc is very
+       // fragile. If this causes problems, we should switch to a dynamic
+       // approach and put the doc in the Commands struct rather than reading
+       // from the source code.
+
+       // Find the Commands slice.
+       typesSlice := pkg.Types.Scope().Lookup("Commands")
+       f, err := fileForPos(pkg, typesSlice.Pos())
+       if err != nil {
+               return nil, err
+       }
+       path, _ := astutil.PathEnclosingInterval(f, typesSlice.Pos(), typesSlice.Pos())
+       vspec := path[1].(*ast.ValueSpec)
+       var astSlice *ast.CompositeLit
+       for i, name := range vspec.Names {
+               if name.Name == "Commands" {
+                       astSlice = vspec.Values[i].(*ast.CompositeLit)
+               }
+       }
+
+       var commands []*source.CommandJSON
+
+       // Parse the objects it contains.
+       for _, elt := range astSlice.Elts {
+               // Find the composite literal of the Command.
+               typesCommand := pkg.TypesInfo.ObjectOf(elt.(*ast.Ident))
+               path, _ := astutil.PathEnclosingInterval(f, typesCommand.Pos(), typesCommand.Pos())
+               vspec := path[1].(*ast.ValueSpec)
+
+               var astCommand ast.Expr
+               for i, name := range vspec.Names {
+                       if name.Name == typesCommand.Name() {
+                               astCommand = vspec.Values[i]
+                       }
+               }
+
+               // Read the Name and Title fields of the literal.
+               var name, title string
+               ast.Inspect(astCommand, func(n ast.Node) bool {
+                       kv, ok := n.(*ast.KeyValueExpr)
+                       if ok {
+                               k := kv.Key.(*ast.Ident).Name
+                               switch k {
+                               case "Name":
+                                       name = strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`)
+                               case "Title":
+                                       title = strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`)
+                               }
+                       }
+                       return true
+               })
+
+               if title == "" {
+                       title = name
+               }
+
+               // Conventionally, the doc starts with the name of the variable.
+               // Replace it with the name of the command.
+               doc := vspec.Doc.Text()
+               doc = strings.Replace(doc, typesCommand.Name(), name, 1)
+
+               commands = append(commands, &source.CommandJSON{
+                       Command: name,
+                       Title:   title,
+                       Doc:     doc,
+               })
+       }
+       return commands, nil
+}
+
+func loadLenses(commands []*source.CommandJSON) []*source.LensJSON {
+       lensNames := map[string]struct{}{}
+       for k := range source.LensFuncs() {
+               lensNames[k] = struct{}{}
+       }
+       for k := range mod.LensFuncs() {
+               lensNames[k] = struct{}{}
+       }
+
+       var lenses []*source.LensJSON
+
+       for _, cmd := range commands {
+               if _, ok := lensNames[cmd.Command]; ok {
+                       lenses = append(lenses, &source.LensJSON{
+                               Lens:  cmd.Command,
+                               Title: cmd.Title,
+                               Doc:   cmd.Doc,
+                       })
+               }
+       }
+       return lenses
+}
+
+func lowerFirst(x string) string {
+       if x == "" {
+               return x
+       }
+       return strings.ToLower(x[:1]) + x[1:]
+}
+
+func fileForPos(pkg *packages.Package, pos token.Pos) (*ast.File, error) {
+       fset := pkg.Fset
+       for _, f := range pkg.Syntax {
+               if fset.Position(f.Pos()).Filename == fset.Position(pos).Filename {
+                       return f, nil
+               }
+       }
+       return nil, fmt.Errorf("no file for pos %v", pos)
+}