feat(format): skip non-plpgsql bodies; keep literals and embedded code safe

- Only restyle bodies for plpgsql/sql; skip pl* (plpython3u, plperl, ...), c and internal
- Keep LANGUAGE clause on DO blocks
- Carry multi-line string and dollar-quoted literals verbatim in bodies and DML
- Format dollar-quoted literals that contain code (declare/begin/select/...), never format() templates
- Fix code joined onto -- comments, early flush after raise exception, dropped comment before BEGIN
- Regenerate test_mm_proc golden; document in README, plan, todo
This commit is contained in:
Hein
2026-10-06 14:43:43 +02:00
parent 41fdaf415c
commit 4ff729eeeb
8 changed files with 729 additions and 228 deletions
+3
View File
@@ -67,6 +67,9 @@ to the intended style below.
on own line; `AS` then `$$` on its own line; body; closing `$$;` on its own line.
- PL/pgSQL: `DECLARE` alone, vars 2-space indented; `--Block--` comment markers preserved;
`BEGIN`/`END` at body level; `IF/THEN/ELSIF/ELSE/END IF`, loops, `CASE` indent their bodies.
- Procedural languages: bodies in any `pl*` language other than `plpgsql` (`plpython3u`,
`plperl`, `pltcl`, …) and in `c` / `internal` are emitted verbatim; `plpgsql`, `sql` and routines with no `LANGUAGE` clause (treated as `sql`) are formatted. The
function header is always formatted.
- Spacing: spaces around binary operators (`=`,`<>`,`||`,…) and `:=`; **no** space around
`::`, `->`, `->>`, array `[...]`, or before a call's `(`.
- Dollar-quote tags preserved verbatim (`$$`, `$S$`, `$Z$`, …).
+31 -2
View File
@@ -36,9 +36,9 @@ Legend: ✅ done · 🚧 in progress · ⬜ not started
- `parser_test.go`: small round-trips, CreateFunction shape assertions, Raw fallback, and
**corpus round-trip** — reconstructs all 4 files byte-for-byte; structures all 4 functions.
- **Status:** all tests pass.
- _Still TODO (later): DML/other-DDL structuring (currently Raw) for full formatting._
- _Note: DML/other-DDL still parse to `Raw`; they are formatted at token level by `pkg/format/dml.go`, not via structured CST nodes._
### 🚧 PL/pgSQL body parser ← NEXT (the remaining V1 piece)
### ✅ PL/pgSQL body parser
#### ✅ DECLARE section — `pkg/format/body.go`
- `formatBody` splits the dollar-quote tag, calls `formatBodyInner`.
@@ -243,6 +243,11 @@ Tests: `TestDMLInsertValues*`.
### ✅ Formatter — PL/pgSQL body settings (`pkg/format/body.go`)
- Language gate (`isPlpgsql` in `pkg/format/format.go`): bodies in a `pl*` language other than
`plpgsql` (`plpython3u`, `plperl`, …), `c` and `internal` are skipped and kept verbatim; `plpgsql`, `sql` and no
clause (treated as `sql`) are formatted. Header is always formatted. Test:
`TestNonPlpgsqlBodyVerbatim`.
- `plpgsql_max_blank_lines`: blank-line runs capped at the configured limit; default `1`.
- `plpgsql_declare_align_type` + `plpgsql_declare_align_eq`: two-pass declare formatter
measures name/type widths then pads for alignment; `writeDeclareAligned` helper. Both
@@ -286,3 +291,27 @@ not implemented.
## Open risks
- `go-pgquery` tracks PG17 (not PG18) — fine for lint; irrelevant to formatter path.
- Leading-comma + one-per-line is a first-class style option, not an afterthought.
### ✅ Literal & embedded-code safety (found via `temp/` corpus)
- Multi-line quoted strings and dollar-quoted literals inside bodies are carried verbatim
(never re-indented); only the code outside them is scanned for parens / terminators / block
depth (`formatBodyStatements`, `cut`/`tail` handling). Same for top-level DML
(`litNL` sentinel in `dml.go`).
- Dollar-quoted literals whose content starts like code (`declare`/`begin` → PL/pgSQL block;
`select`/`insert`/`update`/`delete`/`with` → DML) are formatted recursively
(`formatEmbedded`); anything else (fragments, prose, other languages) stays verbatim. Literals
containing `format()` placeholders (`%s`, `%I`, `%L`, `%1$s`) are never touched, and a result
that fails `SemanticallyEqual`/`CommentsPreserved` is discarded.
- `DO LANGUAGE x $$…$$` keeps its `LANGUAGE` clause (it was being dropped).
- Code is never joined onto a line that ends in a `--` comment.
- A line ending in a literal or non-keyword no longer counts as ending in `EXCEPTION`/`THEN`/….
- Comments between the last DECLARE variable and `BEGIN` are kept.
- Golden `testdata/corpus/test_mm_proc.pgsql` regenerated: nested dollar-quoted dynamic SQL is no
longer re-flowed.
## Local corpus (`temp/`)
`temp/` holds real-world routines (plpgsql, plpython3u, triggers) used for local testing only
(not committed). Run `pgtidy fmt` over it to find safety-gate failures. See "Language gate" above
for how non-plpgsql bodies are handled.