Skip to content

Commit 9ca8607

Browse files
stamblerredmitshur
authored andcommitted
internal/lsp: save all possible keys for analyses, codelenses
The possible keys for analyses and codelenses are too long to enumerate in settings, and we'd need to create enums for all possible analyzer and code lens names, which is probably not feasible. Instead, collect the list of possible values from the analyzers and command settings generation and add them to enum values. Also, handle default values by setting them in the enum keys instead of one big default value. Quite a few hacks to get this right, but maybe there are other better alternatives we can consider in the future. Fixes golang/go#42961 Change-Id: I5c096862b5e8fb89fe5d6639b4f46c06492e49c4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/280355 Trust: Rebecca Stambler <[email protected]> Trust: Hyang-Ah Hana Kim <[email protected]> Run-TryBot: Rebecca Stambler <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]> Reviewed-by: Suzy Mueller <[email protected]>
1 parent d2d86cc commit 9ca8607

File tree

4 files changed

+634
-147
lines changed

4 files changed

+634
-147
lines changed

gopls/doc/generate.go

Lines changed: 188 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"reflect"
2222
"regexp"
2323
"sort"
24+
"strconv"
2425
"strings"
2526
"time"
2627
"unicode"
@@ -77,21 +78,6 @@ func loadAPI() (*source.APIJSON, error) {
7778
Options: map[string][]*source.OptionJSON{},
7879
}
7980
defaults := source.DefaultOptions()
80-
for _, category := range []reflect.Value{
81-
reflect.ValueOf(defaults.UserOptions),
82-
} {
83-
// Find the type information and ast.File corresponding to the category.
84-
optsType := pkg.Types.Scope().Lookup(category.Type().Name())
85-
if optsType == nil {
86-
return nil, fmt.Errorf("could not find %v in scope %v", category.Type().Name(), pkg.Types.Scope())
87-
}
88-
opts, err := loadOptions(category, optsType, pkg, "")
89-
if err != nil {
90-
return nil, err
91-
}
92-
catName := strings.TrimSuffix(category.Type().Name(), "Options")
93-
api.Options[catName] = opts
94-
}
9581

9682
api.Commands, err = loadCommands(pkg)
9783
if err != nil {
@@ -111,6 +97,53 @@ func loadAPI() (*source.APIJSON, error) {
11197
} {
11298
api.Analyzers = append(api.Analyzers, loadAnalyzers(m)...)
11399
}
100+
for _, category := range []reflect.Value{
101+
reflect.ValueOf(defaults.UserOptions),
102+
} {
103+
// Find the type information and ast.File corresponding to the category.
104+
optsType := pkg.Types.Scope().Lookup(category.Type().Name())
105+
if optsType == nil {
106+
return nil, fmt.Errorf("could not find %v in scope %v", category.Type().Name(), pkg.Types.Scope())
107+
}
108+
opts, err := loadOptions(category, optsType, pkg, "")
109+
if err != nil {
110+
return nil, err
111+
}
112+
catName := strings.TrimSuffix(category.Type().Name(), "Options")
113+
api.Options[catName] = opts
114+
115+
// Hardcode the expected values for the analyses and code lenses
116+
// settings, since their keys are not enums.
117+
for _, opt := range opts {
118+
switch opt.Name {
119+
case "analyses":
120+
for _, a := range api.Analyzers {
121+
opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, source.EnumKey{
122+
Name: fmt.Sprintf("%q", a.Name),
123+
Doc: a.Doc,
124+
Default: strconv.FormatBool(a.Default),
125+
})
126+
}
127+
case "codelenses":
128+
// Hack: Lenses don't set default values, and we don't want to
129+
// pass in the list of expected lenses to loadOptions. Instead,
130+
// format the defaults using reflection here. The hackiest part
131+
// is reversing lowercasing of the field name.
132+
reflectField := category.FieldByName(upperFirst(opt.Name))
133+
for _, l := range api.Lenses {
134+
def, err := formatDefaultFromEnumBoolMap(reflectField, l.Lens)
135+
if err != nil {
136+
return nil, err
137+
}
138+
opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, source.EnumKey{
139+
Name: fmt.Sprintf("%q", l.Lens),
140+
Doc: l.Doc,
141+
Default: def,
142+
})
143+
}
144+
}
145+
}
146+
}
114147
return api, nil
115148
}
116149

@@ -161,42 +194,32 @@ func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Pa
161194
return nil, fmt.Errorf("could not find reflect field for %v", typesField.Name())
162195
}
163196

164-
// Format the default value. VSCode exposes settings as JSON, so showing them as JSON is reasonable.
165-
def := reflectField.Interface()
166-
// Durations marshal as nanoseconds, but we want the stringy versions, e.g. "100ms".
167-
if t, ok := def.(time.Duration); ok {
168-
def = t.String()
169-
}
170-
defBytes, err := json.Marshal(def)
197+
def, err := formatDefault(reflectField)
171198
if err != nil {
172199
return nil, err
173200
}
174201

175-
// Nil values format as "null" so print them as hardcoded empty values.
176-
switch reflectField.Type().Kind() {
177-
case reflect.Map:
178-
if reflectField.IsNil() {
179-
defBytes = []byte("{}")
180-
}
181-
case reflect.Slice:
182-
if reflectField.IsNil() {
183-
defBytes = []byte("[]")
184-
}
185-
}
186-
187202
typ := typesField.Type().String()
188203
if _, ok := enums[typesField.Type()]; ok {
189204
typ = "enum"
190205
}
206+
name := lowerFirst(typesField.Name())
191207

192-
// Track any maps whose keys are enums.
193-
enumValues := enums[typesField.Type()]
208+
var enumKeys source.EnumKeys
194209
if m, ok := typesField.Type().(*types.Map); ok {
195-
if e, ok := enums[m.Key()]; ok {
196-
enumValues = e
210+
e, ok := enums[m.Key()]
211+
if ok {
197212
typ = strings.Replace(typ, m.Key().String(), m.Key().Underlying().String(), 1)
198213
}
214+
keys, err := collectEnumKeys(name, m, reflectField, e)
215+
if err != nil {
216+
return nil, err
217+
}
218+
if keys != nil {
219+
enumKeys = *keys
220+
}
199221
}
222+
200223
// Get the status of the field by checking its struct tags.
201224
reflectStructField, ok := category.Type().FieldByName(typesField.Name())
202225
if !ok {
@@ -205,11 +228,12 @@ func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Pa
205228
status := reflectStructField.Tag.Get("status")
206229

207230
opts = append(opts, &source.OptionJSON{
208-
Name: lowerFirst(typesField.Name()),
231+
Name: name,
209232
Type: typ,
210233
Doc: lowerFirst(astField.Doc.Text()),
211-
Default: string(defBytes),
212-
EnumValues: enumValues,
234+
Default: def,
235+
EnumKeys: enumKeys,
236+
EnumValues: enums[typesField.Type()],
213237
Status: status,
214238
Hierarchy: hierarchy,
215239
})
@@ -242,6 +266,90 @@ func loadEnums(pkg *packages.Package) (map[types.Type][]source.EnumValue, error)
242266
return enums, nil
243267
}
244268

269+
func collectEnumKeys(name string, m *types.Map, reflectField reflect.Value, enumValues []source.EnumValue) (*source.EnumKeys, error) {
270+
// Make sure the value type gets set for analyses and codelenses
271+
// too.
272+
if len(enumValues) == 0 && !hardcodedEnumKeys(name) {
273+
return nil, nil
274+
}
275+
keys := &source.EnumKeys{
276+
ValueType: m.Elem().String(),
277+
}
278+
// We can get default values for enum -> bool maps.
279+
var isEnumBoolMap bool
280+
if basic, ok := m.Elem().(*types.Basic); ok && basic.Kind() == types.Bool {
281+
isEnumBoolMap = true
282+
}
283+
for _, v := range enumValues {
284+
var def string
285+
if isEnumBoolMap {
286+
var err error
287+
def, err = formatDefaultFromEnumBoolMap(reflectField, v.Value)
288+
if err != nil {
289+
return nil, err
290+
}
291+
}
292+
keys.Keys = append(keys.Keys, source.EnumKey{
293+
Name: v.Value,
294+
Doc: v.Doc,
295+
Default: def,
296+
})
297+
}
298+
return keys, nil
299+
}
300+
301+
func formatDefaultFromEnumBoolMap(reflectMap reflect.Value, enumKey string) (string, error) {
302+
if reflectMap.Kind() != reflect.Map {
303+
return "", nil
304+
}
305+
name := enumKey
306+
if unquoted, err := strconv.Unquote(name); err == nil {
307+
name = unquoted
308+
}
309+
for _, e := range reflectMap.MapKeys() {
310+
if e.String() == name {
311+
value := reflectMap.MapIndex(e)
312+
if value.Type().Kind() == reflect.Bool {
313+
return formatDefault(value)
314+
}
315+
}
316+
}
317+
// Assume that if the value isn't mentioned in the map, it defaults to
318+
// the default value, false.
319+
return formatDefault(reflect.ValueOf(false))
320+
}
321+
322+
// formatDefault formats the default value into a JSON-like string.
323+
// VS Code exposes settings as JSON, so showing them as JSON is reasonable.
324+
// TODO(rstambler): Reconsider this approach, as the VS Code Go generator now
325+
// marshals to JSON.
326+
func formatDefault(reflectField reflect.Value) (string, error) {
327+
def := reflectField.Interface()
328+
329+
// Durations marshal as nanoseconds, but we want the stringy versions,
330+
// e.g. "100ms".
331+
if t, ok := def.(time.Duration); ok {
332+
def = t.String()
333+
}
334+
defBytes, err := json.Marshal(def)
335+
if err != nil {
336+
return "", err
337+
}
338+
339+
// Nil values format as "null" so print them as hardcoded empty values.
340+
switch reflectField.Type().Kind() {
341+
case reflect.Map:
342+
if reflectField.IsNil() {
343+
defBytes = []byte("{}")
344+
}
345+
case reflect.Slice:
346+
if reflectField.IsNil() {
347+
defBytes = []byte("[]")
348+
}
349+
}
350+
return string(defBytes), err
351+
}
352+
245353
// valueDoc transforms a docstring documenting an constant identifier to a
246354
// docstring documenting its value.
247355
//
@@ -379,6 +487,13 @@ func lowerFirst(x string) string {
379487
return strings.ToLower(x[:1]) + x[1:]
380488
}
381489

490+
func upperFirst(x string) string {
491+
if x == "" {
492+
return x
493+
}
494+
return strings.ToUpper(x[:1]) + x[1:]
495+
}
496+
382497
func fileForPos(pkg *packages.Package, pos token.Pos) (*ast.File, error) {
383498
fset := pkg.Fset
384499
for _, f := range pkg.Syntax {
@@ -411,7 +526,7 @@ func rewriteFile(file string, api *source.APIJSON, write bool, rewrite func([]by
411526
return true, nil
412527
}
413528

414-
func rewriteAPI(input []byte, api *source.APIJSON) ([]byte, error) {
529+
func rewriteAPI(_ []byte, api *source.APIJSON) ([]byte, error) {
415530
buf := bytes.NewBuffer(nil)
416531
apiStr := litter.Options{
417532
HomePackage: "source",
@@ -423,6 +538,7 @@ func rewriteAPI(input []byte, api *source.APIJSON) ([]byte, error) {
423538
apiStr = strings.ReplaceAll(apiStr, "&LensJSON", "")
424539
apiStr = strings.ReplaceAll(apiStr, "&AnalyzerJSON", "")
425540
apiStr = strings.ReplaceAll(apiStr, " EnumValue{", "{")
541+
apiStr = strings.ReplaceAll(apiStr, " EnumKey{", "{")
426542
apiBytes, err := format.Source([]byte(apiStr))
427543
if err != nil {
428544
return nil, err
@@ -463,7 +579,7 @@ func rewriteSettings(doc []byte, api *source.APIJSON) ([]byte, error) {
463579
header := strMultiply("#", level+1)
464580
fmt.Fprintf(section, "%s **%v** *%v*\n\n", header, opt.Name, opt.Type)
465581
writeStatus(section, opt.Status)
466-
enumValues := collectEnumValues(opt)
582+
enumValues := collectEnums(opt)
467583
fmt.Fprintf(section, "%v%v\nDefault: `%v`.\n\n", opt.Doc, enumValues, opt.Default)
468584
}
469585
}
@@ -535,29 +651,40 @@ func collectGroups(opts []*source.OptionJSON) []optionsGroup {
535651
return groups
536652
}
537653

538-
func collectEnumValues(opt *source.OptionJSON) string {
539-
var enumValues strings.Builder
540-
if len(opt.EnumValues) > 0 {
541-
var msg string
542-
if opt.Type == "enum" {
543-
msg = "\nMust be one of:\n\n"
654+
func collectEnums(opt *source.OptionJSON) string {
655+
var b strings.Builder
656+
write := func(name, doc string, index, len int) {
657+
if doc != "" {
658+
unbroken := parBreakRE.ReplaceAllString(doc, "\\\n")
659+
fmt.Fprintf(&b, "* %s", unbroken)
544660
} else {
545-
msg = "\nCan contain any of:\n\n"
661+
fmt.Fprintf(&b, "* `%s`", name)
662+
}
663+
if index < len-1 {
664+
fmt.Fprint(&b, "\n")
546665
}
547-
enumValues.WriteString(msg)
666+
}
667+
if len(opt.EnumValues) > 0 && opt.Type == "enum" {
668+
b.WriteString("\nMust be one of:\n\n")
548669
for i, val := range opt.EnumValues {
549-
if val.Doc != "" {
550-
unbroken := parBreakRE.ReplaceAllString(val.Doc, "\\\n")
551-
fmt.Fprintf(&enumValues, "* %s", unbroken)
552-
} else {
553-
fmt.Fprintf(&enumValues, "* `%s`", val.Value)
554-
}
555-
if i < len(opt.EnumValues)-1 {
556-
fmt.Fprint(&enumValues, "\n")
557-
}
670+
write(val.Value, val.Doc, i, len(opt.EnumValues))
671+
}
672+
} else if len(opt.EnumKeys.Keys) > 0 && shouldShowEnumKeysInSettings(opt.Name) {
673+
b.WriteString("\nCan contain any of:\n\n")
674+
for i, val := range opt.EnumKeys.Keys {
675+
write(val.Name, val.Doc, i, len(opt.EnumKeys.Keys))
558676
}
559677
}
560-
return enumValues.String()
678+
return b.String()
679+
}
680+
681+
func shouldShowEnumKeysInSettings(name string) bool {
682+
// Both of these fields have too many possible options to print.
683+
return !hardcodedEnumKeys(name)
684+
}
685+
686+
func hardcodedEnumKeys(name string) bool {
687+
return name == "analyses" || name == "codelenses"
561688
}
562689

563690
func writeBullet(w io.Writer, title string, level int) {

gopls/doc/settings.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ Default: `false`.
145145

146146
#### **codelenses** *map[string]bool*
147147

148-
codelenses overrides the enabled/disabled state of code lenses. See the "Code Lenses"
149-
section of settings.md for the list of supported lenses.
148+
codelenses overrides the enabled/disabled state of code lenses. See the
149+
"Code Lenses" section of the
150+
[Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md)
151+
for the list of supported lenses.
150152

151153
Example Usage:
152154

@@ -213,7 +215,8 @@ Default: `"Fuzzy"`.
213215

214216
analyses specify analyses that the user would like to enable or disable.
215217
A map of the names of analysis passes that should be enabled/disabled.
216-
A full list of analyzers that gopls uses can be found [here](analyzers.md)
218+
A full list of analyzers that gopls uses can be found
219+
[here](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).
217220

218221
Example Usage:
219222

@@ -376,7 +379,9 @@ Default: `false`.
376379

377380
## Code Lenses
378381

379-
These are the code lenses that `gopls` currently supports. They can be enabled and disabled using the `codeLenses` setting, documented above. The names and features are subject to change.
382+
These are the code lenses that `gopls` currently supports. They can be enabled
383+
and disabled using the `codelenses` setting, documented above. Their names and
384+
features are subject to change.
380385

381386
<!-- BEGIN Lenses: DO NOT MANUALLY EDIT THIS SECTION -->
382387
### **Run go generate**

0 commit comments

Comments
 (0)