Skip to content

Commit bea7919

Browse files
committed
extension/tools/goplssetting: handle enum types
gopls v0.17 will have LinksInHover that is the true|false|"gopls" enum type. Previously, we assumed enum type is always string. That is no longer true. Handle this sum-type enum. For golang/go#68057 Change-Id: I9eb2e8376191d997895f8998bfffb2662fbfb92e Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/616679 Commit-Queue: Hyang-Ah Hana Kim <[email protected]> kokoro-CI: kokoro <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent e22a80d commit bea7919

File tree

3 files changed

+156
-88
lines changed

3 files changed

+156
-88
lines changed

extension/tools/generate.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,18 @@ func enumDescriptionsSnippet(p *Property) string {
495495
if hasDesc && len(desc) == len(p.Enum) {
496496
b.WriteString("\n\n")
497497
for i, e := range p.Enum {
498-
fmt.Fprintf(b, "* `%v`", e)
499-
if d := desc[i]; d != "" {
500-
fmt.Fprintf(b, ": %v", strings.TrimRight(strings.ReplaceAll(d, "\n\n", "<br/>"), "\n"))
498+
enumName := fmt.Sprintf("`%v`", e)
499+
if d := desc[i]; d == "" {
500+
fmt.Fprintf(b, "* %v", enumName)
501+
} else {
502+
enumDesc := strings.TrimRight(strings.ReplaceAll(d, "\n\n", "<br/>"), "\n")
503+
if strings.HasPrefix(d, enumName+":") {
504+
// gopls's enum descriptions are sometimes already formatted
505+
// like `name: description` format. Remove the duplicate prefix.
506+
fmt.Fprintf(b, "* %v", enumDesc)
507+
} else {
508+
fmt.Fprintf(b, "* %v: %v", enumName, enumDesc)
509+
}
501510
}
502511
b.WriteString("\n")
503512
}
@@ -644,7 +653,7 @@ func describeDebugProperty(p *Property) string {
644653
if p.MarkdownDescription != "" {
645654
desc = p.MarkdownDescription
646655
}
647-
if p == nil || strings.Contains(desc, "Not applicable when using `dlv-dap` mode.") {
656+
if strings.Contains(desc, "Not applicable when using `dlv-dap` mode.") {
648657
return ""
649658
}
650659

extension/tools/goplssetting/goplssetting.go

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import (
1010
"fmt"
1111
"io/ioutil"
1212
"log"
13+
"maps"
1314
"os"
1415
"os/exec"
16+
"slices"
1517
"sort"
1618
"strconv"
1719
"strings"
@@ -47,7 +49,7 @@ func Generate(inputFile string, skipCleanup bool) ([]byte, error) {
4749
if err != nil {
4850
return nil, err
4951
}
50-
b, err := asVSCodeSettings(options)
52+
b, err := asVSCodeSettingsJSON(options)
5153
if err != nil {
5254
return nil, err
5355
}
@@ -169,9 +171,17 @@ func rewritePackageJSON(newSettings, inFile string) ([]byte, error) {
169171
return bytes.TrimSpace(stdout.Bytes()), nil
170172
}
171173

172-
// asVSCodeSettings converts the given options to match the VS Code settings
174+
// asVSCodeSettingsJSON converts the given options to match the VS Code settings
173175
// format.
174-
func asVSCodeSettings(options []*Option) ([]byte, error) {
176+
func asVSCodeSettingsJSON(options []*Option) ([]byte, error) {
177+
obj, err := asVSCodeSettings(options)
178+
if err != nil {
179+
return nil, err
180+
}
181+
return json.Marshal(obj)
182+
}
183+
184+
func asVSCodeSettings(options []*Option) (map[string]*Object, error) {
175185
seen := map[string][]*Option{}
176186
for _, opt := range options {
177187
seen[opt.Hierarchy] = append(seen[opt.Hierarchy], opt)
@@ -192,7 +202,7 @@ func asVSCodeSettings(options []*Option) ([]byte, error) {
192202
AdditionalProperties: false,
193203
Properties: goplsProperties,
194204
}
195-
return json.Marshal(goProperties)
205+
return goProperties, nil
196206
}
197207

198208
func collectProperties(m map[string][]*Option) (goplsProperties, goProperties map[string]*Object, err error) {
@@ -261,18 +271,38 @@ func toObject(opt *Option) (*Object, error) {
261271
// outputs acceptable properties.
262272
// TODO: deprecation attribute
263273
}
264-
// Handle any enum types.
265-
if opt.Type == "enum" {
274+
if opt.Type != "enum" {
275+
obj.Type = propertyType(opt.Type)
276+
} else { // Map enum type to a sum type.
277+
// Assume value type is bool | string.
278+
seenTypes := map[string]bool{}
266279
for _, v := range opt.EnumValues {
267-
unquotedName, err := strconv.Unquote(v.Value)
268-
if err != nil {
269-
return nil, err
280+
// EnumValue.Value: string in JSON syntax (quoted)
281+
var x any
282+
if err := json.Unmarshal([]byte(v.Value), &x); err != nil {
283+
return nil, fmt.Errorf("failed to unmarshal %q: %v", v.Value, err)
284+
}
285+
286+
switch t := x.(type) {
287+
case string:
288+
obj.Enum = append(obj.Enum, t)
289+
seenTypes["string"] = true
290+
case bool:
291+
obj.Enum = append(obj.Enum, t)
292+
seenTypes["bool"] = true
293+
default:
294+
panic(fmt.Sprintf("type %T %+v as enum value type is not supported", t, t))
270295
}
271-
obj.Enum = append(obj.Enum, unquotedName)
272296
obj.MarkdownEnumDescriptions = append(obj.MarkdownEnumDescriptions, v.Doc)
273297
}
274-
}
275-
// Handle any objects whose keys are enums.
298+
obj.Type = propertyType(slices.Sorted(maps.Keys(seenTypes))...)
299+
}
300+
// Handle objects whose keys are listed in EnumKeys.
301+
// Gopls uses either enum or string as key types, for example,
302+
// map[string]bool: analyses
303+
// map[enum]bool: codelenses, annotations
304+
// Both cases where 'enum' is used as a key type actually use
305+
// only string type enum. For simplicity, map all to string-keyed objects.
276306
if len(opt.EnumKeys.Keys) > 0 {
277307
if obj.Properties == nil {
278308
obj.Properties = map[string]*Object{}
@@ -289,7 +319,6 @@ func toObject(opt *Option) (*Object, error) {
289319
}
290320
}
291321
}
292-
obj.Type = propertyType(opt.Type)
293322
obj.Default = formatOptionDefault(opt)
294323

295324
return obj, nil
@@ -337,42 +366,46 @@ var associatedToExtensionProperties = map[string][]string{
337366
"buildFlags": {"go.buildFlags", "go.buildTags"},
338367
}
339368

340-
func propertyType(t string) string {
369+
func propertyType(typs ...string) any /* string or []string */ {
370+
if len(typs) == 0 {
371+
panic("unexpected: len(typs) == 0")
372+
}
373+
if len(typs) == 1 {
374+
return mapType(typs[0])
375+
}
376+
377+
var ret []string
378+
for _, t := range typs {
379+
ret = append(ret, mapType(t))
380+
}
381+
return ret
382+
}
383+
384+
func mapType(t string) string {
341385
switch t {
342386
case "string":
343387
return "string"
344388
case "bool":
345389
return "boolean"
346-
case "enum":
347-
return "string"
348390
case "time.Duration":
349391
return "string"
350392
case "[]string":
351393
return "array"
352394
case "map[string]string", "map[string]bool", "map[enum]string", "map[enum]bool":
353395
return "object"
354396
case "any":
355-
return "boolean" // TODO(hyangah): change to "" after https://go.dev/cl/593656 is released.
397+
return "boolean"
356398
}
357399
log.Fatalf("unknown type %q", t)
358400
return ""
359401
}
360402

361-
func check(err error) {
362-
if err == nil {
363-
return
364-
}
365-
366-
log.Output(1, err.Error())
367-
os.Exit(1)
368-
}
369-
370403
// Object represents a VS Code settings object.
371404
type Object struct {
372-
Type string `json:"type,omitempty"`
405+
Type any `json:"type,omitempty"` // string | []string
373406
MarkdownDescription string `json:"markdownDescription,omitempty"`
374407
AdditionalProperties bool `json:"additionalProperties,omitempty"`
375-
Enum []string `json:"enum,omitempty"`
408+
Enum []any `json:"enum,omitempty"`
376409
MarkdownEnumDescriptions []string `json:"markdownEnumDescriptions,omitempty"`
377410
Default interface{} `json:"default,omitempty"`
378411
Scope string `json:"scope,omitempty"`
@@ -395,6 +428,7 @@ type API struct {
395428
Options map[string][]*Option
396429
Lenses []*Lens
397430
Analyzers []*Analyzer
431+
Hints []*Hint
398432
}
399433

400434
type Option struct {

0 commit comments

Comments
 (0)