feat(format): case_collapse uses line_width via Doc-IR

This commit is contained in:
Hein
2026-10-06 14:48:28 +02:00
parent 553609e988
commit ab9fe8893b
4 changed files with 25 additions and 13 deletions
+1 -1
View File
@@ -65,5 +65,5 @@ binary_op_align: false # Align =, <>, || etc. vertically in WHERE/exp
space_after_comma_in_calls: false # Space after , in function calls: func(a, b)
case_when_wrap: false # Each WHEN … THEN on its own line
case_end: new_line # END placement: same_line | new_line
case_collapse: false # Collapse short CASE expressions to one line
case_collapse: false # Collapse a CASE to one line when it fits line_width
record_space_before_paren: false # Space before ( in ROW(…) / record constructors
+4 -5
View File
@@ -167,7 +167,7 @@ Legend: ✅ done · 🚧 in progress · ⬜ not started
function call at the token level — formatted without space (known limitation).
- Note: SQL keywords inside PL/pgSQL function bodies remain lowercase (matching
the corpus golden files); casing is applied only to top-level DML.
- ✅ Doc-IR core (`pkg/format/doc.go`: Text/Line/SoftLine/Group/Indent/IfBreak + `Render`) and `line_width` (default 120, 0 = unlimited). First consumer: `where_wrap: when_long` (`dmlWhereWhenLong`). _Still TODO: move the remaining DML builders (select lists, CASE, joins, subqueries) from string assembly onto Doc._
- ✅ Doc-IR core (`pkg/format/doc.go`: Text/Line/SoftLine/Group/Indent/IfBreak + `Render`) and `line_width` (default 120, 0 = unlimited). First consumer: `where_wrap: when_long` (`dmlWhereWhenLong`). _Still TODO: move the remaining DML builders (select lists, joins, subqueries) from string assembly onto Doc._
- _Still TODO: LSP range formatting._
## ✅ Config expansion — DataGrip settings parity
@@ -272,10 +272,9 @@ Both the simple (`CASE x WHEN ...`) and searched (`CASE WHEN ...`) forms are sup
one level.
- `case_end` (default `new_line`): placement of the closing `END` when wrapped —
`new_line` on its own line, `same_line` glued to the last WHEN/ELSE line.
- `case_collapse` (default `false`): when `true` *and* the fully-inlined rendering is
≤ `caseCollapseWidth` (60 chars), keeps the wrapped CASE on one line anyway, overriding
`case_when_wrap`; longer CASEs still wrap. (No Doc-IR/line-width awareness exists yet, so
this is a fixed length threshold rather than a true "does it fit the line" check.)
- `case_collapse` (default `false`): when `true`, keeps the wrapped CASE on one line anyway if the
fully-inlined rendering fits in `line_width` (default 120), overriding `case_when_wrap`;
longer CASEs still wrap. Rendered through the Doc-IR (`Group(IfBreak(wrapped, inline))`).
- `record_space_before_paren` (default `false`): scoped to the `ROW` keyword specifically
(`ROW(1, 2)` vs `ROW (1, 2)`) — bare `(a, b)` record literals are indistinguishable from
grouping parens at the token level, so this setting only fires on an explicit `ROW(`.
+5 -7
View File
@@ -861,10 +861,6 @@ func alignSetItems(texts []string) []string {
return out
}
// caseCollapseWidth is the inline-length threshold under which case_collapse
// keeps a CASE expression on one line even when case_when_wrap is set.
const caseCollapseWidth = 60
// dmlIsCaseStart reports whether t is a CASE keyword token.
func dmlIsCaseStart(t cst.Tok) bool {
return t.Tok.Kind == lexer.Ident && lowerASCII(t.Tok.Text) == "case"
@@ -1001,10 +997,12 @@ func dmlFormatCase(toks []cst.Tok, st config.Style) string {
if !st.CaseWhenWrap {
return inline
}
if st.CaseCollapse && len(inline) <= caseCollapseWidth {
return inline
wrapped := dmlCaseWrapped(caseKw, operand, whens, elseKw, elseText, haveElse, endKw, st)
if st.CaseCollapse {
// Collapse when the one-line form fits in line_width.
return Render(Group(IfBreak(Text(wrapped), Text(inline))), st.LineWidth, st.Indent, st.Newline)
}
return dmlCaseWrapped(caseKw, operand, whens, elseKw, elseText, haveElse, endKw, st)
return wrapped
}
// dmlCaseInline renders a CASE expression on a single line.
+15
View File
@@ -632,3 +632,18 @@ func TestWhereWhenLong(t *testing.T) {
t.Errorf("broken when_long should equal always:\n%s\n---\n%s", long, want)
}
}
func TestDMLCaseCollapseUsesLineWidth(t *testing.T) {
st := config.Default()
st.CaseWhenWrap = true
st.CaseCollapse = true
src := "select case when a = 1 then 'one' when a = 2 then 'two' else 'other' end from t;"
st.LineWidth = 120
if got := File(parser.Parse(src), st); strings.Contains(got, "\n WHEN") {
t.Errorf("fits in 120, should collapse:\n%s", got)
}
st.LineWidth = 30
if got := File(parser.Parse(src), st); !strings.Contains(got, "\n WHEN") {
t.Errorf("exceeds 30, should wrap:\n%s", got)
}
}