Skip to content

fix(pretty_print): align CASE branches in expanded layout - #884

Merged
psteinroe merged 1 commit into
supabase-community:mainfrom
edjubert:edjubert/case-when-layout
Oct 21, 2026
Merged

psteinroe merged 1 commit into
supabase-community:mainfrom
edjubert:edjubert/case-when-layout

Conversation

@edjubert

Copy link
Copy Markdown
Contributor

Problem and resulting behavior

In expanded layout, the first WHEN stays on the CASE line, and END loses its alignment with CASE when the expression follows a leading comma or a THEN.

This change puts every WHEN on a new line and aligns continuation lines relative to the column where each CASE starts. WHEN and ELSE use one extra indentation level, and THEN uses another. Nested expressions keep their own alignment.

For example, with four-space indentation and leading commas:

SELECT
    id
    , CASE
          WHEN bank_id IS NOT NULL
              THEN 'REMOVED'
          ELSE change_reason
      END AS reason
    , other_value
FROM
    leases
;

Implementation and coverage

  • Add scoped alignment events to the renderer and use them for expanded CASE expressions.
  • Cover simple and searched forms, leading commas, nesting, and expressions without ELSE, using tabs and spaces at line widths of 80 and 200.
  • Assert formatting idempotence and retain semantic round-trip checks.
  • Update the affected comment tests and snapshots. The fit layout retains its existing behavior.

Validation

  • All 691 pgls_pretty_print tests pass.
  • cargo clippy --all-targets --all-features passes.
  • cargo fmt --all --check passes.
  • git diff --check passes.

@psteinroe
psteinroe merged commit 2c59c80 into supabase-community:main Oct 21, 2026
11 checks passed
psteinroe added a commit that referenced this pull request Oct 26, 2026
Stacked on #893.

Follow-up to #884. Expanded CASE expressions lost their alignment when `CASE` started left of the current indent, and a broken `WHEN` condition continued at the `WHEN` column.

**Alignment from where CASE starts**

The renderer stored an alignment as an offset from the indent of the level it started at, and clamped a negative offset to zero. When the text on a line starts left of that indent, the offset is negative. That happens for `select case` once the select list has opened its indent, or `on case` in a join, and `WHEN` and `END` then drifted right:

```sql
-- layout: expanded, clauseBodyStyle: compact, indentSize: 8
-- before
select case
                when a
                        then 1
                else 2
        end
from t;
-- after
select case
               when a
                       then 1
               else 2
       end
from t;
```

An alignment now keeps the column `CASE` starts at together with the indent level at that point. A continuation line starts at that column plus one indent per level opened since. When the offset is not negative this gives the same columns as before, so the existing expanded CASE output is unchanged.

With `indentStyle: "tabs"` an aligned line is written as one tab per indent level and spaces for the rest, as before. When the column is left of the indent, it uses only as many tabs as fit before the column: `\t       when` instead of `\t\t when`.

**WHEN conditions are indented**

A `WHEN` condition that breaks continued at the column of `WHEN`, so `and` read as a sibling of `when`. In expanded layout every AND/OR breaks, so this affected every multi-operand condition:

```sql
-- before
case
  when aaaa = 1
  and bbbb = 2
    then 'x'
-- after
case
  when aaaa = 1
    and bbbb = 2
    then 'x'
```

The condition is now one indent level in from `WHEN`, the same way a WHERE condition is. Fit layout does the same once the condition breaks. Expanded layout still breaks at every AND/OR in a WHEN condition. I left that alone because it is what expanded layout does everywhere else, and the reporter of #884 uses it that way.

**Docs**

`docs/features/formatting.md` now describes the expanded CASE layout and what tabs do to alignment: the columns only line up when the editor's tab width equals `indentSize`. A test checks that the example in the docs is what the formatter prints.

**Tests and snapshot changes**

New tests in `case_layout.rs`: CASE after `on` with `indentSize: 4`, `select case` with `indentSize: 8` in spaces and tabs, the exact tab and space output after a leading comma, and WHEN condition continuation in expanded and fit layout. Two tests in `comment_layout.rs` now expect the `AND` of a WHEN condition, and the comment before it, one tab further in. The only snapshot that changes is `expanded_case_expr_200`, where the AND lines of two WHEN conditions move one level in.

**Not covered**

A CASE nested in the THEN of another CASE still moves right by about 9 columns per level in expanded layout. Twenty levels reach 184 columns at width 100. Three or four levels stay within the width, so I didn't change the layout for this.

**Follow-ups:** The nested CASE drift in expanded layout is fixed in #935.
psteinroe added a commit that referenced this pull request Oct 26, 2026
Stacked on #934.

In expanded layout, a CASE nested in the THEN of another CASE moved right by `then ` plus two indent levels per level of nesting. #884 aligns each CASE's branches with the column it starts at, and a nested CASE starts after `then `. With the default indent, twenty levels reached 184 columns at width 100. Finding P16 of the release review.

**What changed**

A CASE written as the result of a `THEN` or `ELSE` no longer aligns with its own column. It is indented from the line of its keyword: its branches one level in, and `END` under `THEN` or `ELSE`. Each level now costs two indents, the minimum for a WHEN and its THEN:

```sql
-- layout: expanded, indentSize: 4
-- before
case status
    when 'A'
        then case
                 when enabled
                     then 'active'
                 else 'paused'
             end
    else 'closed'
end
-- after
case status
    when 'A'
        then case
            when enabled
                then 'active'
            else 'paused'
        end
    else 'closed'
end
```

`else case when ... else case ...` chains read the same way. A CASE anywhere else, after `select` or a leading comma for instance, still aligns with the column it starts at, which is what #884 was about. Fit layout doesn't align, so it is unchanged.

This changes the nested layout that #884's `expanded_nested_case_keeps_each_branch_at_its_own_indent` test asserted, and the test now expects the new layout. I preferred this over breaking after `then` for every nested CASE: that would add a line and a level for every nested CASE, also the shallow ones that fit today. The docs section on expanded CASE describes the nested layout.

**Tests and snapshot changes**

- `case_layout.rs`: the updated nested test, a CASE nested in ELSE, and twenty levels nested in THEN staying within width 100.
- The only snapshot change is the nested CASE in `expanded_case_alignment` (added in #912), whose inner branches move left to the new indent.
- The option matrix passes over the fixtures and the regression suite.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants