internal/lsp/source: add additional generated docs
Enable documentation for Debugging options, code lenses, and LSP
commands.
See the comment in loadCommands; reading comments off a composite
literal is annoying. If the documentation were available at runtime this
would be much easier, and we should probably switch to that at the first
sign of trouble.
Fixes golang/go#33544.
Change-Id: I503b62aa2fc4a993f5978c449088db54fc79f7de
Reviewed-on: https://go-review.googlesource.com/c/tools/+/257737
Trust: Heschi Kreinick <heschi@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
new file mode 100755
index 0000000..df859c5
--- /dev/null
+++ b/internal/lsp/source/api_json.go
@@ -0,0 +1,5 @@
+// Code generated by "golang.org/x/tools/internal/lsp/source/genapijson"; DO NOT EDIT.
+
+package source
+
+const GeneratedAPIJSON = "{\"Options\":{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"EnumValues\":null,\"Default\":\"100000000\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n \\\"unusedparams\\\": true // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"codelens overrides the enabled/disabled state of code lenses. See the \\\"Code Lenses\\\"\\nsection of settings.md for the list of supported lenses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n \\\"codelens\\\": {\\n \\\"generate\\\": false, // Don't show the `go generate` lens.\\n \\\"gc_details\\\": true // Show a code lens toggling the display of gc's choices.\\n }\\n...\\n}\\n```\\n\",\"EnumValues\":null,\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n str string\\n}\\n\\nfunc main() {\\n x := wrapString{\\\"hello world\\\"}\\n fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"enum\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n * noNilcheck suppresses display of nilchecks.\\n * noEscape suppresses escape choices.\\n * noInline suppresses inlining choices.\\n * noBounds suppresses bounds checking diagnostics.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"enum\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"enum\",\"Doc\":\"symbolStyle specifies what style of symbols to return in symbol requests.\\n\",\"EnumValues\":[\"\\\"Dynamic\\\"\",\"\\\"Full\\\"\",\"\\\"Package\\\"\"],\"Default\":\"\\\"Package\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"enum\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions.\\n\",\"EnumValues\":[\"\\\"Both\\\"\",\"\\\"Definition\\\"\",\"\\\"Link\\\"\"],\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalWorkspaceModule\",\"Type\":\"bool\",\"Doc\":\"experimentalWorkspaceModule opts a user into the experimental support\\nfor multi-module workspaces.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"literalCompletions\",\"Type\":\"bool\",\"Doc\":\"literalCompletions controls whether literal candidates such as\\n\\\"\\u0026someStruct{}\\\" are offered. Tests disable this flag to simplify\\ntheir expected values.\\n\",\"EnumValues\":null,\"Default\":\"true\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"[]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"hoverKind\",\"Type\":\"enum\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nSingleLine and Structured are intended for use only by authors of editor plugins.\\n\",\"EnumValues\":[\"\\\"FullDocumentation\\\"\",\"\\\"NoDocumentation\\\"\",\"\\\"SingleLine\\\"\",\"\\\"Structured\\\"\",\"\\\"SynopsisDocumentation\\\"\"],\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"EnumValues\":null,\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"EnumValues\":null,\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"EnumValues\":null,\"Default\":\"false\"}]},\"Commands\":[{\"Command\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Command\":\"fill_struct\",\"Title\":\"fill_struct\",\"Doc\":\"fill_struct is a gopls command to fill a struct with default\\nvalues.\\n\"},{\"Command\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Command\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Command\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Command\":\"undeclared_name\",\"Title\":\"undeclared_name\",\"Doc\":\"undeclared_name adds a variable declaration for an undeclared\\nname.\\n\"},{\"Command\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Command\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Command\":\"extract_variable\",\"Title\":\"Extract to variable\",\"Doc\":\"extract_variable extracts an expression to a variable.\\n\"},{\"Command\":\"extract_function\",\"Title\":\"Extract to function\",\"Doc\":\"extract_function extracts statements to a function.\\n\"},{\"Command\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"},{\"Command\":\"generate_gopls_mod\",\"Title\":\"Generate gopls.mod\",\"Doc\":\"generate_gopls_mod (re)generates the gopls.mod file.\\n\"}],\"Lenses\":[{\"Lens\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Lens\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Lens\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Lens\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Lens\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Lens\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Lens\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"}]}"
diff --git a/internal/lsp/source/genapijson/generate.go b/internal/lsp/source/genapijson/generate.go
new file mode 100644
index 0000000..3156f4f
--- /dev/null
+++ b/internal/lsp/source/genapijson/generate.go
@@ -0,0 +1,297 @@
+// 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"
+
+ "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)
+ 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 := loadEnums(pkg)
+
+ 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.
+ // Nil values format as "null" so print them as hardcoded empty values.
+ def := reflectField.Interface()
+ defBytes, err := json.Marshal(def)
+ if err != nil {
+ return nil, err
+ }
+
+ 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][]string {
+ enums := map[types.Type][]string{}
+ for _, name := range pkg.Types.Scope().Names() {
+ obj := pkg.Types.Scope().Lookup(name)
+ cnst, ok := obj.(*types.Const)
+ if !ok {
+ continue
+ }
+ enums[obj.Type()] = append(enums[obj.Type()], cnst.Val().ExactString())
+ }
+ return enums
+}
+
+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)
+}
diff --git a/internal/lsp/source/genopts/generate_test.go b/internal/lsp/source/genapijson/generate_test.go
similarity index 77%
rename from internal/lsp/source/genopts/generate_test.go
rename to internal/lsp/source/genapijson/generate_test.go
index 51d7a63..ae0400e 100644
--- a/internal/lsp/source/genopts/generate_test.go
+++ b/internal/lsp/source/genapijson/generate_test.go
@@ -16,7 +16,7 @@
testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code.
testenv.NeedsGoPackages(t)
- got, err := ioutil.ReadFile("../options_json.go")
+ got, err := ioutil.ReadFile("../api_json.go")
if err != nil {
t.Fatal(err)
}
@@ -25,6 +25,6 @@
t.Fatal(err)
}
if !bytes.Equal(got, want) {
- t.Error("options_json is out of sync. Run `go generate ./internal/lsp/source` from the root of tools.")
+ t.Error("api_json is out of sync. Run `go generate ./internal/lsp/source` from the root of tools.")
}
}
diff --git a/internal/lsp/source/genopts/generate.go b/internal/lsp/source/genopts/generate.go
deleted file mode 100644
index 8b76a2a..0000000
--- a/internal/lsp/source/genopts/generate.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// 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 genopts generates JSON describing gopls' user options.
-package main
-
-import (
- "bytes"
- "encoding/json"
- "flag"
- "fmt"
- "go/ast"
- "go/types"
- "os"
- "reflect"
- "strings"
-
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/go/packages"
- "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.NeedSyntax | packages.NeedDeps,
- },
- "golang.org/x/tools/internal/lsp/source",
- )
- if err != nil {
- return nil, err
- }
- pkg := pkgs[0]
-
- defaults := source.DefaultOptions()
- categories := map[string][]option{}
- 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")
- categories[catName] = opts
- }
-
- marshaled, err := json.Marshal(categories)
- if err != nil {
- return nil, err
- }
-
- buf := bytes.NewBuffer(nil)
- fmt.Fprintf(buf, "// Code generated by \"golang.org/x/tools/internal/lsp/source/genopts\"; DO NOT EDIT.\n\npackage source\n\nconst OptionsJson = %q\n", string(marshaled))
- return buf.Bytes(), nil
-}
-
-type option struct {
- Name string
- Type string
- Doc string
- EnumValues []string
- Default string
-}
-
-func loadOptions(category reflect.Value, pkg *packages.Package) ([]option, 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())
- }
-
- fset := pkg.Fset
- var file *ast.File
- for _, f := range pkg.Syntax {
- if fset.Position(f.Pos()).Filename == fset.Position(optsType.Pos()).Filename {
- file = f
- }
- }
- if file == nil {
- return nil, fmt.Errorf("no file for opts type %v", optsType)
- }
-
- enums := loadEnums(pkg)
-
- var opts []option
- 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.
- // Nil values format as "null" so print them as hardcoded empty values.
- def := reflectField.Interface()
- defBytes, err := json.Marshal(def)
- if err != nil {
- return nil, err
- }
-
- 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, option{
- 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][]string {
- enums := map[types.Type][]string{}
- for _, name := range pkg.Types.Scope().Names() {
- obj := pkg.Types.Scope().Lookup(name)
- cnst, ok := obj.(*types.Const)
- if !ok {
- continue
- }
- enums[obj.Type()] = append(enums[obj.Type()], cnst.Val().ExactString())
- }
- return enums
-}
-
-func lowerFirst(x string) string {
- if x == "" {
- return x
- }
- return strings.ToLower(x[:1]) + x[1:]
-}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 6342ba0..9140f4e 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -59,7 +59,7 @@
defaultOptions *Options
)
-//go:generate go run golang.org/x/tools/internal/lsp/source/genopts -output options_json.go
+//go:generate go run golang.org/x/tools/internal/lsp/source/genapijson -output api_json.go
// DefaultOptions is the options that are used for Gopls execution independent
// of any externally provided configuration (LSP initialization, command
@@ -101,8 +101,7 @@
LinkTarget: "pkg.go.dev",
},
DebuggingOptions: DebuggingOptions{
- CompletionBudget: 100 * time.Millisecond,
- LiteralCompletions: true,
+ CompletionBudget: 100 * time.Millisecond,
},
ExperimentalOptions: ExperimentalOptions{
TempModfile: true,
@@ -123,6 +122,7 @@
Matcher: Fuzzy,
SymbolMatcher: SymbolFuzzy,
SymbolStyle: PackageQualifiedSymbols,
+ LiteralCompletions: true,
},
Hooks: Hooks{
ComputeEdits: myers.ComputeEdits,
@@ -235,20 +235,15 @@
// ```
Analyses map[string]bool
- // Overrides the enabled/disabled state of various code lenses. Currently, we
- // support several code lenses:
- //
- // * `generate`: run `go generate` as specified by a `//go:generate` directive.
- // * `upgrade_dependency`: upgrade a dependency listed in a `go.mod` file.
- // * `test`: run `go test -run` for a test func.
- // * `gc_details`: Show the gc compiler's choices for inline analysis and escaping.
+ // Codelens overrides the enabled/disabled state of code lenses. See the "Code Lenses"
+ // section of settings.md for the list of supported lenses.
//
// Example Usage:
// ```json5
// "gopls": {
// ...
// "codelens": {
- // "generate": false, // Don't run `go generate`.
+ // "generate": false, // Don't show the `go generate` lens.
// "gc_details": true // Show a code lens toggling the display of gc's choices.
// }
// ...
@@ -262,7 +257,7 @@
// CompleteUnimported enables completion for packages that you do not currently import.
CompleteUnimported bool
- // DeepCompletion If true, this turns on the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.
+ // DeepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.
//
// Consider this example:
//
@@ -326,6 +321,11 @@
// ExperimentalWorkspaceModule opts a user into the experimental support
// for multi-module workspaces.
ExperimentalWorkspaceModule bool
+
+ // LiteralCompletions controls whether literal candidates such as
+ // "&someStruct{}" are offered. Tests disable this flag to simplify
+ // their expected values.
+ LiteralCompletions bool
}
// DebuggingOptions should not affect the logical execution of Gopls, but may
@@ -340,11 +340,6 @@
// dynamically reduce the search scope to ensure we return timely
// results. Zero means unlimited.
CompletionBudget time.Duration
-
- // LiteralCompletions controls whether literal candidates such as
- // "&someStruct{}" are offered. Tests disable this flag to simplify
- // their expected values.
- LiteralCompletions bool
}
type ImportShortcut string
@@ -900,3 +895,29 @@
re.Longest()
return re
}
+
+type APIJSON struct {
+ Options map[string][]*OptionJSON
+ Commands []*CommandJSON
+ Lenses []*LensJSON
+}
+
+type OptionJSON struct {
+ Name string
+ Type string
+ Doc string
+ EnumValues []string
+ Default string
+}
+
+type CommandJSON struct {
+ Command string
+ Title string
+ Doc string
+}
+
+type LensJSON struct {
+ Lens string
+ Title string
+ Doc string
+}
diff --git a/internal/lsp/source/options_json.go b/internal/lsp/source/options_json.go
deleted file mode 100755
index f0a1259..0000000
--- a/internal/lsp/source/options_json.go
+++ /dev/null
@@ -1,5 +0,0 @@
-// Code generated by "golang.org/x/tools/internal/lsp/source/genopts"; DO NOT EDIT.
-
-package source
-
-const OptionsJson = "{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"EnumValues\":null,\"Default\":\"100000000\"},{\"Name\":\"literalCompletions\",\"Type\":\"bool\",\"Doc\":\"literalCompletions controls whether literal candidates such as\\n\\\"\\u0026someStruct{}\\\" are offered. Tests disable this flag to simplify\\ntheir expected values.\\n\",\"EnumValues\":null,\"Default\":\"true\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n \\\"unusedparams\\\": true // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"overrides the enabled/disabled state of various code lenses. Currently, we\\nsupport several code lenses:\\n\\n* `generate`: run `go generate` as specified by a `//go:generate` directive.\\n* `upgrade_dependency`: upgrade a dependency listed in a `go.mod` file.\\n* `test`: run `go test -run` for a test func.\\n* `gc_details`: Show the gc compiler's choices for inline analysis and escaping.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n \\\"codelens\\\": {\\n \\\"generate\\\": false, // Don't run `go generate`.\\n \\\"gc_details\\\": true // Show a code lens toggling the display of gc's choices.\\n }\\n...\\n}\\n```\\n\",\"EnumValues\":null,\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion If true, this turns on the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n str string\\n}\\n\\nfunc main() {\\n x := wrapString{\\\"hello world\\\"}\\n fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"enum\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n * noNilcheck suppresses display of nilchecks.\\n * noEscape suppresses escape choices.\\n * noInline suppresses inlining choices.\\n * noBounds suppresses bounds checking diagnostics.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"enum\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols.\\n\",\"EnumValues\":[\"\\\"CaseInsensitive\\\"\",\"\\\"CaseSensitive\\\"\",\"\\\"Fuzzy\\\"\"],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"enum\",\"Doc\":\"symbolStyle specifies what style of symbols to return in symbol requests.\\n\",\"EnumValues\":[\"\\\"Dynamic\\\"\",\"\\\"Full\\\"\",\"\\\"Package\\\"\"],\"Default\":\"\\\"Package\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"enum\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions.\\n\",\"EnumValues\":[\"\\\"Both\\\"\",\"\\\"Definition\\\"\",\"\\\"Link\\\"\"],\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalWorkspaceModule\",\"Type\":\"bool\",\"Doc\":\"experimentalWorkspaceModule opts a user into the experimental support\\nfor multi-module workspaces.\\n\",\"EnumValues\":null,\"Default\":\"false\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"[]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"hoverKind\",\"Type\":\"enum\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nSingleLine and Structured are intended for use only by authors of editor plugins.\\n\",\"EnumValues\":[\"\\\"FullDocumentation\\\"\",\"\\\"NoDocumentation\\\"\",\"\\\"SingleLine\\\"\",\"\\\"Structured\\\"\",\"\\\"SynopsisDocumentation\\\"\"],\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"EnumValues\":null,\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"EnumValues\":null,\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"EnumValues\":null,\"Default\":\"false\"}]}"