# PgTidy β€” TODO / Progress Tracks what is done and what remains. See `plan.md` for the full design and rationale. Legend: βœ… done Β· 🚧 in progress Β· ⬜ not started --- ## V1 β€” Formatter + CLI (current milestone) ### βœ… Scaffold module + repo hygiene - `go.mod` (`module github.com/hein/pgtidy`, go 1.26). - Replaced WkMailSync boilerplate: `AGENTS.md` (PgTidy architecture + invariants), `CLAUDE.md`, `Makefile` (`build`/`test`/`vet`/`fmt`/`lint`/`clean`). - Directory layout created (`cmd/`, `pkg/...`, `testdata/`). - Copied 4 real-world procedures into `testdata/corpus/*.pgsql` (safety harness). ### βœ… Lossless lexer β€” `pkg/lexer` - `token.go`: `Kind` enum (trivia, words, literals, operators, punctuation) + `Token`. - `lexer.go`: full PG token coverage β€” dollar-quoted strings (`$tag$`, nested-tag aware), `--` / nestable `/* */` comments, standard/escape/bit/hex/unicode strings, numbers (decimal, exponent, leading dot, `0x/0o/0b`, `_` separators), positional params (`$1`), operator runs with PostgreSQL's trailing `+/-` rule, `::` / `:=` / `:`, punctuation. Tracks line/col per token. - `lexer_test.go`: unit tests (kinds, operator trailing rule, dollar quotes, line/col) + **corpus round-trip** asserting `emit(Lex(src)) == src` byte-for-byte. - **Status:** all tests pass; round-trips all 4 corpus files. Invariant #1 satisfied. ### βœ… CST node model + parser β€” `pkg/cst`, `pkg/parser` - `pkg/cst`: lossless node model β€” `Tok` (significant token + leading trivia), `Attach`, `File.Source()` (byte-exact reconstruction), `Raw` (verbatim fallback), `CreateFunction` (Head/Name/Params/Options/As/Body/Tail/Semi), `Param` (with separator comma). - `pkg/parser`: statement splitting at top-level `;`; structures CREATE FUNCTION/PROCEDURE (head, qualified name, comma-split params, option clauses split by keyword, AS + body); everything else β†’ `Raw`. Graceful degradation is a property of the data model. - `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._ ### ⬜ 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. ### βœ… 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**. - _Note: not a full Wadler Doc-IR yet β€” fixed-layout printer. Doc-IR for width-based expression wrapping can come when DML structuring lands._ ### βœ… CLI `fmt` + safety harness β€” `cmd/pgtidy` - `pgtidy fmt` (gofmt model): default stdinβ†’stdout; `-w`/`--write`, `-l`/`--list`, `--check` (CI exit codes); `version`/`help`. _`.pgtidy.yaml` discovery + `-d` diff: TODO._ - `fmt_test.go`: stdin, --check (un/formatted), -w idempotence, unknown-command. - Safety invariants #2 (semantic equivalence), #3 (idempotence), #4 (graceful degradation) are tested in `pkg/format` over the corpus. --- ## V2 β€” Linter (later) - `pkg/pgast`: `go-pgquery` (WASM, no cgo) wrapper β†’ real PG AST. - `pkg/lint`: rule engine + packs β€” style/consistency, **migration safety** (locks, unsafe ALTER/ADD COLUMN, non-CONCURRENTLY index, blocking constraints), naming, correctness. - `pkg/diagnostics`: shared diagnostic type (CLI + LSP). - `pgtidy lint` subcommand; `--fix` for autofixable rules. ## V3 β€” LSP + VSCode (later) - `pkg/lsp`: formatting + range formatting, publishDiagnostics, codeAction quick-fixes. - `editors/vscode`: TS extension (`vscode-languageclient`) launching bundled `pgtidy lsp`; per-platform VSIX matrix in CI (rust-analyzer model) + target-less fallback. ## V4 β€” DataGrip (later) - `editors/datagrip`: integrate via free **LSP4IJ** plugin. --- ## Build / release (cross-cutting) - ⬜ Add goreleaser for the multi-platform binary matrix (clean: no cgo). - `make_release.sh` retained from boilerplate (generic version tagging). ## Core invariants (must always hold β€” tested) 1. βœ… Lossless lex: `emit(Lex(src)) == src` (corpus round-trip). 2. βœ… Semantic equivalence: formatting changes only trivia/layout (corpus token-stream check). 3. βœ… Idempotence: `fmt(fmt(x)) == fmt(x)` (corpus + CLI tests). 4. βœ… Graceful degradation: unparsable spans pass through verbatim (Raw nodes + verbatim body). ## Open risks - Lossless PL/pgSQL recursive-descent parser is the largest effort; pass-through fallback bounds risk and allows shipping construct-by-construct. - `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.