@@ -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+
382497func 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\n Default: `%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 = " \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 )
544660 } 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 " )
546665 }
547- enumValues .WriteString (msg )
666+ }
667+ if len (opt .EnumValues ) > 0 && opt .Type == "enum" {
668+ b .WriteString ("\n Must 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 ("\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 ))
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
563690func writeBullet (w io.Writer , title string , level int ) {
0 commit comments