@@ -21,6 +21,7 @@ import (
21
21
"reflect"
22
22
"regexp"
23
23
"sort"
24
+ "strconv"
24
25
"strings"
25
26
"time"
26
27
"unicode"
@@ -77,21 +78,6 @@ func loadAPI() (*source.APIJSON, error) {
77
78
Options : map [string ][]* source.OptionJSON {},
78
79
}
79
80
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
- }
95
81
96
82
api .Commands , err = loadCommands (pkg )
97
83
if err != nil {
@@ -111,6 +97,53 @@ func loadAPI() (*source.APIJSON, error) {
111
97
} {
112
98
api .Analyzers = append (api .Analyzers , loadAnalyzers (m )... )
113
99
}
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
+ }
114
147
return api , nil
115
148
}
116
149
@@ -161,42 +194,32 @@ func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Pa
161
194
return nil , fmt .Errorf ("could not find reflect field for %v" , typesField .Name ())
162
195
}
163
196
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 )
171
198
if err != nil {
172
199
return nil , err
173
200
}
174
201
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
-
187
202
typ := typesField .Type ().String ()
188
203
if _ , ok := enums [typesField .Type ()]; ok {
189
204
typ = "enum"
190
205
}
206
+ name := lowerFirst (typesField .Name ())
191
207
192
- // Track any maps whose keys are enums.
193
- enumValues := enums [typesField .Type ()]
208
+ var enumKeys source.EnumKeys
194
209
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 {
197
212
typ = strings .Replace (typ , m .Key ().String (), m .Key ().Underlying ().String (), 1 )
198
213
}
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
+ }
199
221
}
222
+
200
223
// Get the status of the field by checking its struct tags.
201
224
reflectStructField , ok := category .Type ().FieldByName (typesField .Name ())
202
225
if ! ok {
@@ -205,11 +228,12 @@ func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Pa
205
228
status := reflectStructField .Tag .Get ("status" )
206
229
207
230
opts = append (opts , & source.OptionJSON {
208
- Name : lowerFirst ( typesField . Name ()) ,
231
+ Name : name ,
209
232
Type : typ ,
210
233
Doc : lowerFirst (astField .Doc .Text ()),
211
- Default : string (defBytes ),
212
- EnumValues : enumValues ,
234
+ Default : def ,
235
+ EnumKeys : enumKeys ,
236
+ EnumValues : enums [typesField .Type ()],
213
237
Status : status ,
214
238
Hierarchy : hierarchy ,
215
239
})
@@ -242,6 +266,90 @@ func loadEnums(pkg *packages.Package) (map[types.Type][]source.EnumValue, error)
242
266
return enums , nil
243
267
}
244
268
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
+
245
353
// valueDoc transforms a docstring documenting an constant identifier to a
246
354
// docstring documenting its value.
247
355
//
@@ -379,6 +487,13 @@ func lowerFirst(x string) string {
379
487
return strings .ToLower (x [:1 ]) + x [1 :]
380
488
}
381
489
490
+ func upperFirst (x string ) string {
491
+ if x == "" {
492
+ return x
493
+ }
494
+ return strings .ToUpper (x [:1 ]) + x [1 :]
495
+ }
496
+
382
497
func fileForPos (pkg * packages.Package , pos token.Pos ) (* ast.File , error ) {
383
498
fset := pkg .Fset
384
499
for _ , f := range pkg .Syntax {
@@ -411,7 +526,7 @@ func rewriteFile(file string, api *source.APIJSON, write bool, rewrite func([]by
411
526
return true , nil
412
527
}
413
528
414
- func rewriteAPI (input []byte , api * source.APIJSON ) ([]byte , error ) {
529
+ func rewriteAPI (_ []byte , api * source.APIJSON ) ([]byte , error ) {
415
530
buf := bytes .NewBuffer (nil )
416
531
apiStr := litter.Options {
417
532
HomePackage : "source" ,
@@ -423,6 +538,7 @@ func rewriteAPI(input []byte, api *source.APIJSON) ([]byte, error) {
423
538
apiStr = strings .ReplaceAll (apiStr , "&LensJSON" , "" )
424
539
apiStr = strings .ReplaceAll (apiStr , "&AnalyzerJSON" , "" )
425
540
apiStr = strings .ReplaceAll (apiStr , " EnumValue{" , "{" )
541
+ apiStr = strings .ReplaceAll (apiStr , " EnumKey{" , "{" )
426
542
apiBytes , err := format .Source ([]byte (apiStr ))
427
543
if err != nil {
428
544
return nil , err
@@ -463,7 +579,7 @@ func rewriteSettings(doc []byte, api *source.APIJSON) ([]byte, error) {
463
579
header := strMultiply ("#" , level + 1 )
464
580
fmt .Fprintf (section , "%s **%v** *%v*\n \n " , header , opt .Name , opt .Type )
465
581
writeStatus (section , opt .Status )
466
- enumValues := collectEnumValues (opt )
582
+ enumValues := collectEnums (opt )
467
583
fmt .Fprintf (section , "%v%v\n Default: `%v`.\n \n " , opt .Doc , enumValues , opt .Default )
468
584
}
469
585
}
@@ -535,29 +651,40 @@ func collectGroups(opts []*source.OptionJSON) []optionsGroup {
535
651
return groups
536
652
}
537
653
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 = " \n Must 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 )
544
660
} else {
545
- msg = "\n Can contain any of:\n \n "
661
+ fmt .Fprintf (& b , "* `%s`" , name )
662
+ }
663
+ if index < len - 1 {
664
+ fmt .Fprint (& b , "\n " )
546
665
}
547
- enumValues .WriteString (msg )
666
+ }
667
+ if len (opt .EnumValues ) > 0 && opt .Type == "enum" {
668
+ b .WriteString ("\n Must be one of:\n \n " )
548
669
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 ("\n Can contain any of:\n \n " )
674
+ for i , val := range opt .EnumKeys .Keys {
675
+ write (val .Name , val .Doc , i , len (opt .EnumKeys .Keys ))
558
676
}
559
677
}
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"
561
688
}
562
689
563
690
func writeBullet (w io.Writer , title string , level int ) {
0 commit comments