Skip to content

Commit

Permalink
extension/tools/goplssetting: handle enum types
Browse files Browse the repository at this point in the history
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
  • Loading branch information
hyangah committed Oct 10, 2024
1 parent 9017f1e commit f19b641
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 88 deletions.
17 changes: 13 additions & 4 deletions extension/tools/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,18 @@ func enumDescriptionsSnippet(p *Property) string {
if hasDesc && len(desc) == len(p.Enum) {
b.WriteString("\n\n")
for i, e := range p.Enum {
fmt.Fprintf(b, "* `%v`", e)
if d := desc[i]; d != "" {
fmt.Fprintf(b, ": %v", strings.TrimRight(strings.ReplaceAll(d, "\n\n", "<br/>"), "\n"))
enumName := fmt.Sprintf("`%v`", e)
if d := desc[i]; d == "" {
fmt.Fprintf(b, "* %v", enumName)
} else {
enumDesc := strings.TrimRight(strings.ReplaceAll(d, "\n\n", "<br/>"), "\n")
if strings.HasPrefix(d, enumName+":") {
// gopls's enum descriptions are sometimes already formatted
// like `name: description` format. Remove the duplicate prefix.
fmt.Fprintf(b, "* %v", enumDesc)
} else {
fmt.Fprintf(b, "* %v: %v", enumName, enumDesc)
}
}
b.WriteString("\n")
}
Expand Down Expand Up @@ -644,7 +653,7 @@ func describeDebugProperty(p *Property) string {
if p.MarkdownDescription != "" {
desc = p.MarkdownDescription
}
if p == nil || strings.Contains(desc, "Not applicable when using `dlv-dap` mode.") {
if strings.Contains(desc, "Not applicable when using `dlv-dap` mode.") {
return ""
}

Expand Down
90 changes: 62 additions & 28 deletions extension/tools/goplssetting/goplssetting.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"fmt"
"io/ioutil"
"log"
"maps"
"os"
"os/exec"
"slices"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -47,7 +49,7 @@ func Generate(inputFile string, skipCleanup bool) ([]byte, error) {
if err != nil {
return nil, err
}
b, err := asVSCodeSettings(options)
b, err := asVSCodeSettingsJSON(options)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -169,9 +171,17 @@ func rewritePackageJSON(newSettings, inFile string) ([]byte, error) {
return bytes.TrimSpace(stdout.Bytes()), nil
}

// asVSCodeSettings converts the given options to match the VS Code settings
// asVSCodeSettingsJSON converts the given options to match the VS Code settings
// format.
func asVSCodeSettings(options []*Option) ([]byte, error) {
func asVSCodeSettingsJSON(options []*Option) ([]byte, error) {
obj, err := asVSCodeSettings(options)
if err != nil {
return nil, err
}
return json.Marshal(obj)
}

func asVSCodeSettings(options []*Option) (map[string]*Object, error) {
seen := map[string][]*Option{}
for _, opt := range options {
seen[opt.Hierarchy] = append(seen[opt.Hierarchy], opt)
Expand All @@ -192,7 +202,7 @@ func asVSCodeSettings(options []*Option) ([]byte, error) {
AdditionalProperties: false,
Properties: goplsProperties,
}
return json.Marshal(goProperties)
return goProperties, nil
}

func collectProperties(m map[string][]*Option) (goplsProperties, goProperties map[string]*Object, err error) {
Expand Down Expand Up @@ -261,18 +271,38 @@ func toObject(opt *Option) (*Object, error) {
// outputs acceptable properties.
// TODO: deprecation attribute
}
// Handle any enum types.
if opt.Type == "enum" {
if opt.Type != "enum" {
obj.Type = propertyType(opt.Type)
} else { // Map enum type to a sum type.
// Assume value type is bool | string.
seenTypes := map[string]bool{}
for _, v := range opt.EnumValues {
unquotedName, err := strconv.Unquote(v.Value)
if err != nil {
return nil, err
// EnumValue.Value: string in JSON syntax (quoted)
var x any
if err := json.Unmarshal([]byte(v.Value), &x); err != nil {
return nil, fmt.Errorf("failed to unmarshal %q: %v", v.Value, err)
}

switch t := x.(type) {
case string:
obj.Enum = append(obj.Enum, t)
seenTypes["string"] = true
case bool:
obj.Enum = append(obj.Enum, t)
seenTypes["bool"] = true
default:
panic(fmt.Sprintf("type %T %+v as enum value type is not supported", t, t))
}
obj.Enum = append(obj.Enum, unquotedName)
obj.MarkdownEnumDescriptions = append(obj.MarkdownEnumDescriptions, v.Doc)
}
}
// Handle any objects whose keys are enums.
obj.Type = propertyType(slices.Sorted(maps.Keys(seenTypes))...)
}
// Handle objects whose keys are listed in EnumKeys.
// Gopls uses either enum or string as key types, for example,
// map[string]bool: analyses
// map[enum]bool: codelenses, annotations
// Both cases where 'enum' is used as a key type actually use
// only string type enum. For simplicity, map all to string-keyed objects.
if len(opt.EnumKeys.Keys) > 0 {
if obj.Properties == nil {
obj.Properties = map[string]*Object{}
Expand All @@ -289,7 +319,6 @@ func toObject(opt *Option) (*Object, error) {
}
}
}
obj.Type = propertyType(opt.Type)
obj.Default = formatOptionDefault(opt)

return obj, nil
Expand Down Expand Up @@ -337,42 +366,46 @@ var associatedToExtensionProperties = map[string][]string{
"buildFlags": {"go.buildFlags", "go.buildTags"},
}

func propertyType(t string) string {
func propertyType(typs ...string) any /* string or []string */ {
if len(typs) == 0 {
panic("unexpected: len(typs) == 0")
}
if len(typs) == 1 {
return mapType(typs[0])
}

var ret []string
for _, t := range typs {
ret = append(ret, mapType(t))
}
return ret
}

func mapType(t string) string {
switch t {
case "string":
return "string"
case "bool":
return "boolean"
case "enum":
return "string"
case "time.Duration":
return "string"
case "[]string":
return "array"
case "map[string]string", "map[string]bool", "map[enum]string", "map[enum]bool":
return "object"
case "any":
return "boolean" // TODO(hyangah): change to "" after https://go.dev/cl/593656 is released.
return "boolean"
}
log.Fatalf("unknown type %q", t)
return ""
}

func check(err error) {
if err == nil {
return
}

log.Output(1, err.Error())
os.Exit(1)
}

// Object represents a VS Code settings object.
type Object struct {
Type string `json:"type,omitempty"`
Type any `json:"type,omitempty"` // string | []string
MarkdownDescription string `json:"markdownDescription,omitempty"`
AdditionalProperties bool `json:"additionalProperties,omitempty"`
Enum []string `json:"enum,omitempty"`
Enum []any `json:"enum,omitempty"`
MarkdownEnumDescriptions []string `json:"markdownEnumDescriptions,omitempty"`
Default interface{} `json:"default,omitempty"`
Scope string `json:"scope,omitempty"`
Expand All @@ -395,6 +428,7 @@ type API struct {
Options map[string][]*Option
Lenses []*Lens
Analyzers []*Analyzer
Hints []*Hint
}

type Option struct {
Expand Down
Loading

0 comments on commit f19b641

Please sign in to comment.