feat(format): case_collapse uses line_width via Doc-IR
This commit is contained in:
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user