feat(format): implement PL/pgSQL body formatting
* add formatBody and formatBodyInner functions for DECLARE section * update needSpace to handle LBracket correctly * enhance semanticallyEqual to compare dollar-quoted bodies * add test data for broken layout scenarios
This commit is contained in:
@@ -38,22 +38,61 @@ Legend: ✅ done · 🚧 in progress · ⬜ not started
|
||||
- **Status:** all tests pass.
|
||||
- _Still TODO (later): DML/other-DDL structuring (currently Raw) for full formatting._
|
||||
|
||||
### ⬜ PL/pgSQL body parser ← NEXT (the remaining V1 piece)
|
||||
- Parse `$$ ... $$` bodies: DECLARE/BEGIN/END, IF/ELSIF/ELSE/END IF, LOOP/FOR/WHILE,
|
||||
CASE, assignments (`:=` / `=`), RAISE, nested SQL statements, EXCEPTION blocks.
|
||||
- The crux for stored-procedure formatting (primary use case). Currently the body is
|
||||
emitted verbatim; this milestone formats inside it. Reuse `inline`/spacing/casing from
|
||||
`pkg/format` and keep verbatim fallback for unparsable constructs.
|
||||
### 🚧 PL/pgSQL body parser ← NEXT (the remaining V1 piece)
|
||||
|
||||
#### ✅ DECLARE section — `pkg/format/body.go`
|
||||
- `formatBody` splits the dollar-quote tag, calls `formatBodyInner`.
|
||||
- `formatBodyInner` locates `DECLARE` and `BEGIN` at depth 0, formats the declare block,
|
||||
then emits `BEGIN` onwards verbatim.
|
||||
- `formatDeclareVars`: each variable declaration collapsed to one line
|
||||
(` name type [= expr];`), `--Block--` comment markers preserved on their own lines,
|
||||
mid-declaration block comments trigger verbatim fallback.
|
||||
- `needSpace` fixed for `LBracket` — no space before `[` after ident/closing bracket
|
||||
(fixes `citext[]`, array subscripts).
|
||||
- `semanticallyEqual` in tests updated to recurse into dollar-quoted body tokens so
|
||||
whitespace normalization inside the body does not falsely fail the semantic check.
|
||||
- Added `testdata/corpus/test_a_broken.pgsql` — a CRLF corpus file with intentionally
|
||||
broken layout (split-line variables + split-line body statements) used as a formatting
|
||||
target; `testdata/corpus/test_a.pgsql` is the golden output.
|
||||
- **Status:** all tests pass; DECLARE section formats correctly.
|
||||
|
||||
#### ⬜ Body statement formatter — `pkg/format/body.go` (next step)
|
||||
Two-phase formatter for the `BEGIN … END` block:
|
||||
|
||||
1. **Block-depth tracker** — recognise `BEGIN`/`END`, `IF`/`THEN`/`ELSIF`/`ELSE`/`END IF`,
|
||||
`LOOP`/`END LOOP`, `FOR`/`END LOOP`, `CASE`/`END CASE`, `EXCEPTION`/`WHEN` to maintain
|
||||
current indent depth (`depth × 2` spaces).
|
||||
|
||||
2. **Split-line join** — within each statement (tokens up to `;` at depth 0), a physical
|
||||
line whose first non-whitespace token is at column 0 is a broken continuation; join it to
|
||||
the preceding line with a single space. Exceptions: SQL clause keywords (`FROM`, `WHERE`,
|
||||
`INTO`, `HAVING`, `GROUP`, `ORDER`, `RETURNING`) stay on their own line.
|
||||
|
||||
3. **Base-indent normalisation** — after joining, each logical line is re-emitted at
|
||||
`depth × 2` spaces, stripping its original leading whitespace and replacing it with the
|
||||
computed indent. Internal relative indentation of multi-line expressions is preserved.
|
||||
|
||||
4. **Blank-line preservation** — blank lines between statements in the original are kept
|
||||
(they carry author intent about logical grouping).
|
||||
|
||||
5. **Verbatim fallback** — any construct the formatter cannot classify cleanly passes
|
||||
through verbatim, upholding invariant #4.
|
||||
|
||||
Acceptance: `pgtidy fmt testdata/corpus/test_a_broken.pgsql` produces output matching
|
||||
`testdata/corpus/test_a.pgsql` (new golden-file test).
|
||||
|
||||
### ✅ Printer + style config — `pkg/format`, `pkg/config`
|
||||
- `pkg/config`: `Style` struct + `Default()` = house style (UPPERCASE keywords, lowercase
|
||||
types, 2-space indent, leading commas, spacing rules).
|
||||
- `pkg/format`: formats CREATE FUNCTION/PROCEDURE **headers** to house style (params
|
||||
one-per-line leading-comma, option clauses each on own line, AS/`$$` own lines); body and
|
||||
`Raw` statements emitted verbatim. Spacing engine (`needSpace`, tight ops `:: : -> ->>`)
|
||||
+ casing (`keywords`/`typeNames` sets). Comment-safety: verbatim fallback if a header
|
||||
carries comments it cannot relocate.
|
||||
- Tests: golden header, idempotence, corpus idempotence + **semantic equivalence**.
|
||||
one-per-line leading-comma, option clauses each on own line, AS/`$$` own lines); DECLARE
|
||||
section formatted (see body parser entry); `Raw` statements emitted verbatim.
|
||||
Spacing engine (`needSpace`, tight ops `:: : -> ->>`, array `[]`) + casing
|
||||
(`keywords`/`typeNames` sets). Comment-safety: verbatim fallback if a header carries
|
||||
comments it cannot relocate.
|
||||
- `pkg/format/body.go`: DECLARE section formatter (see body parser entry).
|
||||
- Tests: golden header, idempotence, corpus idempotence + **semantic equivalence**
|
||||
(updated to recurse into dollar-quoted body tokens).
|
||||
- _Note: not a full Wadler Doc-IR yet — fixed-layout printer. Doc-IR for width-based
|
||||
expression wrapping can come when DML structuring lands._
|
||||
|
||||
|
||||
Reference in New Issue
Block a user