Skip to content

Commit cb3016b

Browse files
adonovangopherbot
authored andcommitted
[gopls-release-branch.0.16] gopls/internal/settings: move CodeLensSource from protocol
This enum is properly a setting, not a protocol feature. The .md and api.json generator script assumes that all enums are in the settings package, and consequently emitted incorrect names for this type. Generator changes in detail: - remove the package.Load of both "settings" and "protocol", reverting part of an early change. - remove loadAPI special case for "codelenses" not being an enum-keyed map, as it is one. (As a consequence, the enum values appear in api.json in alphabetical, not declaration, order. Also, the title portion of the codelend doc string is no longer discarded.) - add lots of missing commentary. This may seem like a large change at the 11th hour, but note: - the only change to the production code is a renaming; - the effects of the generator changes are entirely confined to settings.md and api.json. Fixes golang/go#68057 Change-Id: I097f0a9b2e34b8f9a3438112b55efb2081b4acb2 Reviewed-on: https://go-review.googlesource.com/c/tools/+/593615 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> (cherry picked from commit 1cd1a4f08f940bc68597469ffb33630f33ef104f) Reviewed-on: https://go-review.googlesource.com/c/tools/+/593676 Auto-Submit: Alan Donovan <[email protected]>
1 parent 6c3e23f commit cb3016b

File tree

11 files changed

+210
-220
lines changed

11 files changed

+210
-220
lines changed

gopls/doc/generate/generate.go

+53-67
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import (
3939
"golang.org/x/tools/gopls/internal/doc"
4040
"golang.org/x/tools/gopls/internal/golang"
4141
"golang.org/x/tools/gopls/internal/mod"
42-
"golang.org/x/tools/gopls/internal/protocol"
4342
"golang.org/x/tools/gopls/internal/protocol/command/commandmeta"
4443
"golang.org/x/tools/gopls/internal/settings"
4544
"golang.org/x/tools/gopls/internal/util/maps"
@@ -136,23 +135,12 @@ func loadAPI() (*doc.API, error) {
136135
&packages.Config{
137136
Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedDeps,
138137
},
139-
"golang.org/x/tools/gopls/internal/settings", // for settings
140-
"golang.org/x/tools/gopls/internal/protocol", // for lenses
138+
"golang.org/x/tools/gopls/internal/settings",
141139
)
142140
if err != nil {
143141
return nil, err
144142
}
145-
// TODO(adonovan): document at packages.Load that the result
146-
// order does not match the pattern order.
147-
var protocolPkg, settingsPkg *packages.Package
148-
for _, pkg := range pkgs {
149-
switch pkg.Types.Name() {
150-
case "settings":
151-
settingsPkg = pkg
152-
case "protocol":
153-
protocolPkg = pkg
154-
}
155-
}
143+
settingsPkg := pkgs[0]
156144

157145
defaults := settings.DefaultOptions()
158146
api := &doc.API{
@@ -164,7 +152,7 @@ func loadAPI() (*doc.API, error) {
164152
if err != nil {
165153
return nil, err
166154
}
167-
api.Lenses, err = loadLenses(protocolPkg, defaults.Codelenses)
155+
api.Lenses, err = loadLenses(settingsPkg, defaults.Codelenses)
168156
if err != nil {
169157
return nil, err
170158
}
@@ -185,8 +173,8 @@ func loadAPI() (*doc.API, error) {
185173
catName := strings.TrimSuffix(category.Type().Name(), "Options")
186174
api.Options[catName] = opts
187175

188-
// Hardcode the expected values for the analyses and code lenses
189-
// settings, since their keys are not enums.
176+
// Hardcode the expected values for the "analyses" and "hints" settings,
177+
// since their map keys are strings, not enums.
190178
for _, opt := range opts {
191179
switch opt.Name {
192180
case "analyses":
@@ -197,24 +185,9 @@ func loadAPI() (*doc.API, error) {
197185
Default: strconv.FormatBool(a.Default),
198186
})
199187
}
200-
case "codelenses":
201-
// Hack: Lenses don't set default values, and we don't want to
202-
// pass in the list of expected lenses to loadOptions. Instead,
203-
// format the defaults using reflection here. The hackiest part
204-
// is reversing lowercasing of the field name.
205-
reflectField := category.FieldByName(upperFirst(opt.Name))
206-
for _, l := range api.Lenses {
207-
def, err := formatDefaultFromEnumBoolMap(reflectField, l.Lens)
208-
if err != nil {
209-
return nil, err
210-
}
211-
opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, doc.EnumKey{
212-
Name: fmt.Sprintf("%q", l.Lens),
213-
Doc: l.Doc,
214-
Default: def,
215-
})
216-
}
217188
case "hints":
189+
// TODO(adonovan): simplify InlayHints to use an enum,
190+
// following CodeLensSource.
218191
for _, a := range api.Hints {
219192
opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, doc.EnumKey{
220193
Name: fmt.Sprintf("%q", a.Name),
@@ -282,24 +255,48 @@ func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Pa
282255
return nil, err
283256
}
284257

258+
// Derive the doc-and-api.json type from the Go field type.
259+
//
260+
// In principle, we should use JSON nomenclature here
261+
// (number, array, object, etc; see #68057), but in
262+
// practice we use the Go type string ([]T, map[K]V,
263+
// etc) with only one tweak: enumeration types are
264+
// replaced by "enum", including when they appear as
265+
// map keys.
266+
//
267+
// Notable edge cases:
268+
// - any (e.g. in linksInHover) is really a sum of false | true | "internal".
269+
// - time.Duration is really a string with a particular syntax.
285270
typ := typesField.Type().String()
286271
if _, ok := enums[typesField.Type()]; ok {
287272
typ = "enum"
288273
}
289274
name := lowerFirst(typesField.Name())
290275

276+
// enum-keyed maps
291277
var enumKeys doc.EnumKeys
292278
if m, ok := typesField.Type().Underlying().(*types.Map); ok {
293-
e, ok := enums[m.Key()]
279+
values, ok := enums[m.Key()]
294280
if ok {
295-
typ = strings.Replace(typ, m.Key().String(), m.Key().Underlying().String(), 1)
296-
}
297-
keys, err := collectEnumKeys(name, m, reflectField, e)
298-
if err != nil {
299-
return nil, err
281+
// Update type name: "map[CodeLensSource]T" -> "map[enum]T"
282+
// hack: assumes key substring is unique!
283+
typ = strings.Replace(typ, m.Key().String(), "enum", 1)
300284
}
301-
if keys != nil {
302-
enumKeys = *keys
285+
286+
// Edge case: "analyses" is a string (not enum) keyed map,
287+
// but its EnumKeys.ValueType was historically populated.
288+
// (But not "env"; not sure why.)
289+
if ok || name == "analyses" {
290+
enumKeys.ValueType = m.Elem().String()
291+
292+
// For map[enum]T fields, gather the set of valid
293+
// EnumKeys (from type information). If T=bool, also
294+
// record the default value (from reflection).
295+
keys, err := collectEnumKeys(m, reflectField, values)
296+
if err != nil {
297+
return nil, err
298+
}
299+
enumKeys.Keys = keys
303300
}
304301
}
305302

@@ -350,20 +347,13 @@ func loadEnums(pkg *packages.Package) (map[types.Type][]doc.EnumValue, error) {
350347
return enums, nil
351348
}
352349

353-
func collectEnumKeys(name string, m *types.Map, reflectField reflect.Value, enumValues []doc.EnumValue) (*doc.EnumKeys, error) {
354-
// Make sure the value type gets set for analyses and codelenses
355-
// too.
356-
if len(enumValues) == 0 && !hardcodedEnumKeys(name) {
357-
return nil, nil
358-
}
359-
keys := &doc.EnumKeys{
360-
ValueType: m.Elem().String(),
361-
}
350+
func collectEnumKeys(m *types.Map, reflectField reflect.Value, enumValues []doc.EnumValue) ([]doc.EnumKey, error) {
362351
// We can get default values for enum -> bool maps.
363352
var isEnumBoolMap bool
364353
if basic, ok := m.Elem().Underlying().(*types.Basic); ok && basic.Kind() == types.Bool {
365354
isEnumBoolMap = true
366355
}
356+
var keys []doc.EnumKey
367357
for _, v := range enumValues {
368358
var def string
369359
if isEnumBoolMap {
@@ -373,7 +363,7 @@ func collectEnumKeys(name string, m *types.Map, reflectField reflect.Value, enum
373363
return nil, err
374364
}
375365
}
376-
keys.Keys = append(keys.Keys, doc.EnumKey{
366+
keys = append(keys, doc.EnumKey{
377367
Name: v.Value,
378368
Doc: v.Doc,
379369
Default: def,
@@ -529,19 +519,19 @@ func structDoc(fields []*commandmeta.Field, level int) string {
529519
return b.String()
530520
}
531521

532-
// loadLenses combines the syntactic comments from the protocol
522+
// loadLenses combines the syntactic comments from the settings
533523
// package with the default values from settings.DefaultOptions(), and
534524
// returns a list of Code Lens descriptors.
535-
func loadLenses(protocolPkg *packages.Package, defaults map[protocol.CodeLensSource]bool) ([]*doc.Lens, error) {
525+
func loadLenses(settingsPkg *packages.Package, defaults map[settings.CodeLensSource]bool) ([]*doc.Lens, error) {
536526
// Find the CodeLensSource enums among the files of the protocol package.
537527
// Map each enum value to its doc comment.
538528
enumDoc := make(map[string]string)
539-
for _, f := range protocolPkg.Syntax {
529+
for _, f := range settingsPkg.Syntax {
540530
for _, decl := range f.Decls {
541531
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.CONST {
542532
for _, spec := range decl.Specs {
543533
spec := spec.(*ast.ValueSpec)
544-
posn := safetoken.StartPosition(protocolPkg.Fset, spec.Pos())
534+
posn := safetoken.StartPosition(settingsPkg.Fset, spec.Pos())
545535
if id, ok := spec.Type.(*ast.Ident); ok && id.Name == "CodeLensSource" {
546536
if len(spec.Names) != 1 || len(spec.Values) != 1 {
547537
return nil, fmt.Errorf("%s: declare one CodeLensSource per line", posn)
@@ -566,7 +556,7 @@ func loadLenses(protocolPkg *packages.Package, defaults map[protocol.CodeLensSou
566556

567557
// Build list of Lens descriptors.
568558
var lenses []*doc.Lens
569-
addAll := func(sources map[protocol.CodeLensSource]cache.CodeLensSourceFunc, fileType string) error {
559+
addAll := func(sources map[settings.CodeLensSource]cache.CodeLensSourceFunc, fileType string) error {
570560
slice := maps.Keys(sources)
571561
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
572562
for _, source := range slice {
@@ -732,10 +722,6 @@ func rewriteSettings(prevContent []byte, api *doc.API) ([]byte, error) {
732722
buf.WriteString(opt.Doc)
733723

734724
// enums
735-
//
736-
// TODO(adonovan): `CodeLensSource` should be treated as an enum,
737-
// but loadEnums considers only the `settings` package,
738-
// not `protocol`.
739725
write := func(name, doc string) {
740726
if doc != "" {
741727
unbroken := parBreakRE.ReplaceAllString(doc, "\\\n")
@@ -745,12 +731,14 @@ func rewriteSettings(prevContent []byte, api *doc.API) ([]byte, error) {
745731
}
746732
}
747733
if len(opt.EnumValues) > 0 && opt.Type == "enum" {
734+
// enum as top-level type constructor
748735
buf.WriteString("\nMust be one of:\n\n")
749736
for _, val := range opt.EnumValues {
750737
write(val.Value, val.Doc)
751738
}
752739
} else if len(opt.EnumKeys.Keys) > 0 && shouldShowEnumKeysInSettings(opt.Name) {
753-
buf.WriteString("\nCan contain any of:\n\n")
740+
// enum as map key (currently just "annotations")
741+
buf.WriteString("\nEach enum must be one of:\n\n")
754742
for _, val := range opt.EnumKeys.Keys {
755743
write(val.Name, val.Doc)
756744
}
@@ -772,7 +760,9 @@ func rewriteSettings(prevContent []byte, api *doc.API) ([]byte, error) {
772760
var parBreakRE = regexp.MustCompile("\n{2,}")
773761

774762
func shouldShowEnumKeysInSettings(name string) bool {
775-
// These fields have too many possible options to print.
763+
// These fields have too many possible options,
764+
// or too voluminous documentation, to render as enums.
765+
// Instead they each get their own page in the manual.
776766
return !(name == "analyses" || name == "codelenses" || name == "hints")
777767
}
778768

@@ -830,10 +820,6 @@ func collectGroups(opts []*doc.Option) []optionsGroup {
830820
return groups
831821
}
832822

833-
func hardcodedEnumKeys(name string) bool {
834-
return name == "analyses" || name == "codelenses"
835-
}
836-
837823
func capitalize(s string) string {
838824
return string(unicode.ToUpper(rune(s[0]))) + s[1:]
839825
}

gopls/doc/settings.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ Default: `false`.
180180
## UI
181181

182182
<a id='codelenses'></a>
183-
### `codelenses` *map[golang.org/x/tools/gopls/internal/protocol.CodeLensSource]bool*
183+
### `codelenses` *map[enum]bool*
184184

185185
codelenses overrides the enabled/disabled state of each of gopls'
186186
sources of [Code Lenses](codelenses.md).
@@ -325,14 +325,14 @@ These analyses are documented on
325325
Default: `false`.
326326

327327
<a id='annotations'></a>
328-
### `annotations` *map[string]bool*
328+
### `annotations` *map[enum]bool*
329329

330330
**This setting is experimental and may be deleted.**
331331

332332
annotations specifies the various kinds of optimization diagnostics
333333
that should be reported by the gc_details command.
334334

335-
Can contain any of:
335+
Each enum must be one of:
336336

337337
* `"bounds"` controls bounds checking diagnostics.
338338
* `"escape"` controls diagnostics about escape choices.

gopls/internal/cmd/codelens.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ func (r *codelens) Run(ctx context.Context, args ...string) error {
7878
origOptions(opts)
7979
}
8080
if opts.Codelenses == nil {
81-
opts.Codelenses = make(map[protocol.CodeLensSource]bool)
81+
opts.Codelenses = make(map[settings.CodeLensSource]bool)
8282
}
83-
opts.Codelenses[protocol.CodeLensTest] = true
83+
opts.Codelenses[settings.CodeLensTest] = true
8484
}
8585

8686
conn, err := r.app.connect(ctx)

gopls/internal/doc/api.json

+12-12
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@
646646
},
647647
{
648648
"Name": "annotations",
649-
"Type": "map[string]bool",
649+
"Type": "map[enum]bool",
650650
"Doc": "annotations specifies the various kinds of optimization diagnostics\nthat should be reported by the gc_details command.\n",
651651
"EnumKeys": {
652652
"ValueType": "bool",
@@ -799,49 +799,49 @@
799799
},
800800
{
801801
"Name": "codelenses",
802-
"Type": "map[golang.org/x/tools/gopls/internal/protocol.CodeLensSource]bool",
802+
"Type": "map[enum]bool",
803803
"Doc": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"codelenses\": {\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",
804804
"EnumKeys": {
805805
"ValueType": "bool",
806806
"Keys": [
807807
{
808808
"Name": "\"gc_details\"",
809-
"Doc": "\nThis codelens source causes the `package` declaration of\neach file to be annotated with a command to toggle the\nstate of the per-session variable that controls whether\noptimization decisions from the Go compiler (formerly known\nas \"gc\") should be displayed as diagnostics.\n\nOptimization decisions include:\n- whether a variable escapes, and how escape is inferred;\n- whether a nil-pointer check is implied or eliminated;\n- whether a function can be inlined.\n\nTODO(adonovan): this source is off by default because the\nannotation is annoying and because VS Code has a separate\n\"Toggle gc details\" command. Replace it with a Code Action\n(\"Source action...\").\n",
809+
"Doc": "`\"gc_details\"`: Toggle display of Go compiler optimization decisions\n\nThis codelens source causes the `package` declaration of\neach file to be annotated with a command to toggle the\nstate of the per-session variable that controls whether\noptimization decisions from the Go compiler (formerly known\nas \"gc\") should be displayed as diagnostics.\n\nOptimization decisions include:\n- whether a variable escapes, and how escape is inferred;\n- whether a nil-pointer check is implied or eliminated;\n- whether a function can be inlined.\n\nTODO(adonovan): this source is off by default because the\nannotation is annoying and because VS Code has a separate\n\"Toggle gc details\" command. Replace it with a Code Action\n(\"Source action...\").\n",
810810
"Default": "false"
811811
},
812812
{
813813
"Name": "\"generate\"",
814-
"Doc": "\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n",
814+
"Doc": "`\"generate\"`: Run `go generate`\n\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n",
815815
"Default": "true"
816816
},
817817
{
818818
"Name": "\"regenerate_cgo\"",
819-
"Doc": "\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
819+
"Doc": "`\"regenerate_cgo\"`: Re-generate cgo declarations\n\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
820820
"Default": "true"
821821
},
822822
{
823-
"Name": "\"test\"",
824-
"Doc": "\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n",
823+
"Name": "\"run_govulncheck\"",
824+
"Doc": "`\"run_govulncheck\"`: Run govulncheck\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run Govulncheck.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static\nanalysis tool that computes the set of functions reachable\nwithin your application, including dependencies;\nqueries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
825825
"Default": "false"
826826
},
827827
{
828-
"Name": "\"run_govulncheck\"",
829-
"Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run Govulncheck.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static\nanalysis tool that computes the set of functions reachable\nwithin your application, including dependencies;\nqueries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
828+
"Name": "\"test\"",
829+
"Doc": "`\"test\"`: Run tests and benchmarks\n\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n",
830830
"Default": "false"
831831
},
832832
{
833833
"Name": "\"tidy\"",
834-
"Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n",
834+
"Doc": "`\"tidy\"`: Tidy go.mod file\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n",
835835
"Default": "true"
836836
},
837837
{
838838
"Name": "\"upgrade_dependency\"",
839-
"Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n",
839+
"Doc": "`\"upgrade_dependency\"`: Update dependencies\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n",
840840
"Default": "true"
841841
},
842842
{
843843
"Name": "\"vendor\"",
844-
"Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n",
844+
"Doc": "`\"vendor\"`: Update vendor directory\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n",
845845
"Default": "true"
846846
}
847847
]

gopls/internal/golang/code_lens.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ import (
1717
"golang.org/x/tools/gopls/internal/file"
1818
"golang.org/x/tools/gopls/internal/protocol"
1919
"golang.org/x/tools/gopls/internal/protocol/command"
20+
"golang.org/x/tools/gopls/internal/settings"
2021
)
2122

2223
// CodeLensSources returns the supported sources of code lenses for Go files.
23-
func CodeLensSources() map[protocol.CodeLensSource]cache.CodeLensSourceFunc {
24-
return map[protocol.CodeLensSource]cache.CodeLensSourceFunc{
25-
protocol.CodeLensGenerate: goGenerateCodeLens, // commands: Generate
26-
protocol.CodeLensTest: runTestCodeLens, // commands: Test
27-
protocol.CodeLensRegenerateCgo: regenerateCgoLens, // commands: RegenerateCgo
28-
protocol.CodeLensGCDetails: toggleDetailsCodeLens, // commands: GCDetails
24+
func CodeLensSources() map[settings.CodeLensSource]cache.CodeLensSourceFunc {
25+
return map[settings.CodeLensSource]cache.CodeLensSourceFunc{
26+
settings.CodeLensGenerate: goGenerateCodeLens, // commands: Generate
27+
settings.CodeLensTest: runTestCodeLens, // commands: Test
28+
settings.CodeLensRegenerateCgo: regenerateCgoLens, // commands: RegenerateCgo
29+
settings.CodeLensGCDetails: toggleDetailsCodeLens, // commands: GCDetails
2930
}
3031
}
3132

0 commit comments

Comments
 (0)