Files
PgTidy/todo.md
T
2026-06-23 16:59:50 +02:00

5.6 KiB

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.