From 6fc98d970edb39579da6894c35ad1489e90fb0f5 Mon Sep 17 00:00:00 2001 From: karitham Date: Sat, 8 Aug 2026 00:44:55 +0200 Subject: [PATCH] formatter: add sets construct to per-construct options --- README.md | 6 ++++-- formatter/format.go | 12 ++++++++++-- formatter/format_test.go | 19 ++++++++++++++++++ formatter/value.go | 42 +++++++++++++++++++++++++++++----------- options/options_test.go | 2 +- 5 files changed, 65 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0e667b9..8480daf 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,8 @@ the file being formatted or the workspace root (like Biome). Set the "arguments": "comma", "throws": "comma", "lists": "comma", - "maps": "comma" + "maps": "comma", + "sets": "comma" }, "break": { "structs": true, @@ -240,7 +241,8 @@ the file being formatted or the workspace root (like Biome). Set the "exceptions": true, "enums": true, "lists": true, - "maps": true + "maps": true, + "sets": true }, "includePaths": ["/path/to/base"], "logLevel": 3 diff --git a/formatter/format.go b/formatter/format.go index b2139ca..a5cfed8 100644 --- a/formatter/format.go +++ b/formatter/format.go @@ -60,6 +60,7 @@ const ( ConstructThrows ConstructList ConstructMap + ConstructSet ) // PerConstruct holds one option value per construct. The JSON tags make the @@ -74,6 +75,7 @@ type PerConstruct[T any] struct { Throws T `json:"throws"` Lists T `json:"lists"` Maps T `json:"maps"` + Sets T `json:"sets"` } // Get returns the value for the construct. @@ -93,6 +95,8 @@ func (p PerConstruct[T]) Get(c Construct) T { return p.Lists case ConstructMap: return p.Maps + case ConstructSet: + return p.Sets } return p.Structs @@ -115,6 +119,8 @@ func (p *PerConstruct[T]) Set(c Construct, v T) { p.Lists = v case ConstructMap: p.Maps = v + case ConstructSet: + p.Sets = v default: p.Structs = v } @@ -124,7 +130,7 @@ func (p *PerConstruct[T]) Set(c Construct, v T) { var AllConstructs = []Construct{ ConstructStruct, ConstructUnion, ConstructException, ConstructEnum, ConstructArguments, ConstructThrows, - ConstructList, ConstructMap, + ConstructList, ConstructMap, ConstructSet, } // String returns the config key of the construct. @@ -144,6 +150,8 @@ func (c Construct) String() string { return "lists" case ConstructMap: return "maps" + case ConstructSet: + return "sets" } return "structs" @@ -645,7 +653,7 @@ func (f *formatter) constant(v *syntax.Const) doc.Doc { // outside the value's own group. parts = append(parts, f.ownLineComments(value.TokStart())...) - parts = append(parts, f.constValue(value, value.TokEnd() == v.TokEnd())) + parts = append(parts, f.constValue(value, value.TokEnd() == v.TokEnd(), containerConstruct(v.Type))) if value.TokEnd() < v.TokEnd() { // Same-line comments after the value render at the value // boundary, outside the value's own group, before the stray diff --git a/formatter/format_test.go b/formatter/format_test.go index 7a91ce1..a7511f6 100644 --- a/formatter/format_test.go +++ b/formatter/format_test.go @@ -1365,6 +1365,25 @@ func TestFormatConstsOptions(t *testing.T) { }), want: "const map m = {\n \"a\": 1,\n \"b\": 2,\n}\n", }, + { + // A set value uses the list literal syntax, but its declared + // type selects the sets construct. + name: "sets forced comma with break", + src: "const set s = [1, 2]", + opts: opts(func(o *Options) { + o.Separator.Set(ConstructSet, SeparatorComma) + o.Break.Set(ConstructSet, true) + }), + want: "const set s = [\n 1,\n 2,\n]\n", + }, + { + name: "sets semicolon separators", + src: "const set s = [1, 2]", + opts: opts(func(o *Options) { + o.Separator.Set(ConstructSet, SeparatorSemicolon) + }), + want: "const set s = [1; 2; ]\n", + }, { name: "trailing separator never leaves a blank before the close", src: "const list a = [\n 1,\n 2,\n]", diff --git a/formatter/value.go b/formatter/value.go index 1d774a1..1511b4b 100644 --- a/formatter/value.go +++ b/formatter/value.go @@ -11,20 +11,40 @@ func isListSep(kind syntax.TokenKind) bool { return kind == syntax.TokenComma || kind == syntax.TokenSemicolon } +// containerConstruct returns the per-construct key of a declared container +// type. Set values are written with the list literal syntax, so the +// declared type is the only way to select the sets construct. +func containerConstruct(t *syntax.FieldType) Construct { + if t == nil { + return ConstructList + } + + switch t.Kind { + case syntax.TypeSet: + return ConstructSet + case syntax.TypeMap: + return ConstructMap + default: + return ConstructList + } +} + // constValue formats a constant value. Scalars render as a token run; // lists and maps are groups that stay on one line when they fit and break -// with one entry per line otherwise. Every segment is a token run, so -// comments inside the value are preserved. isLast reports whether the -// value ends the enclosing declaration, in which case its trailing trivia -// belongs to the declaration's trailing comments. -func (f *formatter) constValue(v *syntax.ConstValue, isLast bool) doc.Doc { +// with one entry per line otherwise. c is the construct whose separator +// and break options apply to list literals — the declared container type +// at the top level, the list construct for nested values. Every segment is +// a token run, so comments inside the value are preserved. isLast reports +// whether the value ends the enclosing declaration, in which case its +// trailing trivia belongs to the declaration's trailing comments. +func (f *formatter) constValue(v *syntax.ConstValue, isLast bool, c Construct) doc.Doc { if v == nil { return f.Concat() } switch v.Kind { case syntax.ValueList: - return f.constList(v, isLast) + return f.constList(v, isLast, c) case syntax.ValueMap: return f.constMap(v, isLast) default: @@ -34,15 +54,15 @@ func (f *formatter) constValue(v *syntax.ConstValue, isLast bool) doc.Doc { } } -// constList formats "[ items ]" as a foldable group honoring the lists -// separator and break options. -func (f *formatter) constList(v *syntax.ConstValue, isLast bool) doc.Doc { +// constList formats "[ items ]" as a foldable group honoring the c +// construct's separator and break options. +func (f *formatter) constList(v *syntax.ConstValue, isLast bool, c Construct) doc.Doc { items := make([]constItem, len(v.List)) for i, item := range v.List { - items[i] = constItem{start: item.TokStart(), end: item.TokEnd(), doc: f.constValue(item, false)} + items[i] = constItem{start: item.TokStart(), end: item.TokEnd(), doc: f.constValue(item, false, ConstructList)} } - return f.constItems(items, v.TokStart(), v.TokEnd(), ConstructList, isLast) + return f.constItems(items, v.TokStart(), v.TokEnd(), c, isLast) } // constMap formats "{ key: value, ... }" as a foldable group honoring the diff --git a/options/options_test.go b/options/options_test.go index 411d6ef..69fcd83 100644 --- a/options/options_test.go +++ b/options/options_test.go @@ -274,7 +274,7 @@ func TestPatchSeparatorModes(t *testing.T) { p := Patch{Separators: &Separators{ Structs: &tt.value, Unions: &tt.value, Exceptions: &tt.value, Enums: &tt.value, Arguments: &tt.value, Throws: &tt.value, - Lists: &tt.value, Maps: &tt.value, + Lists: &tt.value, Maps: &tt.value, Sets: &tt.value, }} o, err := p.Formatter() -- 2.51.2