From 430088242964215b3e0c6ab6faffef6609e389b0 Mon Sep 17 00:00:00 2001 From: sneak Date: Sun, 4 Oct 2026 15:19:58 +0000 Subject: [PATCH] Check Go formatting with gofumpt, as make fmt writes it (closes #70) The Go format check ran plain gofmt, which accepts code that gofumpt, the formatter script/fmt runs, rewrites; and the two covered different files. Both now go through script/gofumpt, which runs one pinned gofumpt version over every Go file in --write or --check mode, as script/prettier does for Markdown. It replaces script/fmt-check-go; make fmt-check-go and the Docker lint stage call it. go run builds the pinned version, so nothing has to install gofumpt, and the check fails when gofumpt cannot run instead of passing. Generated mfer/mf.pb.go passes: gofumpt holds generated files to gofmt's rules. The check is not redundant: .golangci.yml enables no golangci-lint formatters. Model: opus-5-5 --- Dockerfile | 3 ++- Makefile | 2 +- README.md | 13 ++++++++----- script/fmt | 5 +++-- script/fmt-check | 4 ++-- script/fmt-check-go | 18 ------------------ script/gofumpt | 44 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 60 insertions(+), 29 deletions(-) delete mode 100755 script/fmt-check-go create mode 100755 script/gofumpt diff --git a/Dockerfile b/Dockerfile index 4ca4ac3..5110e92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,8 @@ RUN go mod download COPY . . # Go half of fmt-check only: this image has no node, so no prettier. The -# markdown half runs in the mdfmt stage below. +# markdown half runs in the mdfmt stage below. The image has no gofumpt +# either; script/gofumpt builds the version it pins with `go run`. RUN make fmt-check-go # The linter directly, not `make lint`: script/lint builds this stage, and # there is no docker inside this build. diff --git a/Makefile b/Makefile index 8ba3c71..fc8f6eb 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ fmt-check: # Halves of fmt-check, for environments that have only one toolchain: # the Docker lint stage has Go but no node, the markdown stage the reverse. fmt-check-go: - @script/fmt-check-go + @script/gofumpt --check fmt-check-md: @script/prettier --check diff --git a/README.md b/README.md index edb1f46..be73a8e 100644 --- a/README.md +++ b/README.md @@ -91,16 +91,19 @@ provide: - `script/lint` — run `golangci-lint` in Docker: builds only the `lint` stage of the `Dockerfile` (the Go format check, then the linter), uncached so it runs every time, then removes the image -- `script/fmt` — format all code and docs (writes): `gofumpt` and +- `script/fmt` — format all code and docs (writes): `script/gofumpt --write` and `script/prettier --write` +- `script/gofumpt` — run `gofumpt` over every Go file in the repository in the + given mode, `--write` or `--check`, at the one version it pins (built on + demand by `go run`, so nothing installs it); `script/fmt`, `script/fmt-check` + and the Docker lint stage (`make fmt-check-go`) all go through it, so they + cannot disagree about Go formatting - `script/prettier` — run prettier over the repository's canonical file set (Markdown and JSON, minus `.prettierignore`) in the given mode, `--write` or `--check`; the single definition of that file set, so `script/fmt` and `script/fmt-check` cannot disagree about it -- `script/fmt-check` — check formatting without writing: `script/fmt-check-go` - plus `script/prettier --check` -- `script/fmt-check-go` — the Go half of `script/fmt-check`, on its own, for the - Docker lint stage, whose image has no node +- `script/fmt-check` — check formatting without writing: + `script/gofumpt --check` plus `script/prettier --check` - `script/check` — run `script/test`, `script/lint`, and `script/fmt-check` - `script/docker` — build the Docker image tagged with the project name - `script/cibuild` — CI entrypoint: builds the image with the same command as diff --git a/script/fmt b/script/fmt index fd81efa..f091088 100755 --- a/script/fmt +++ b/script/fmt @@ -7,8 +7,9 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" main() { cd "$ROOT" - gofumpt -l -w mfer internal cmd - # Markdown and JSON, over the same file set script/fmt-check verifies. + # Go, then Markdown and JSON, through the same scripts script/fmt-check + # uses, so both see the same files and the same tool versions. + "$SCRIPT_DIR/gofumpt" --write "$SCRIPT_DIR/prettier" --write } diff --git a/script/fmt-check b/script/fmt-check index 1bd6882..b11390f 100755 --- a/script/fmt-check +++ b/script/fmt-check @@ -1,13 +1,13 @@ #!/bin/sh # script/fmt-check: check formatting (read-only). Same scope as -# script/fmt, but fails instead of writing: Go via script/fmt-check-go, +# script/fmt, but fails instead of writing: Go via script/gofumpt, # Markdown and JSON via script/prettier. set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" main() { - "$SCRIPT_DIR/fmt-check-go" + "$SCRIPT_DIR/gofumpt" --check "$SCRIPT_DIR/prettier" --check } diff --git a/script/fmt-check-go b/script/fmt-check-go deleted file mode 100755 index e0248d4..0000000 --- a/script/fmt-check-go +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -# script/fmt-check-go: check Go formatting (read-only). Split out from -# script/fmt-check so the Docker lint stage, whose image has no node and -# therefore no prettier, can run the Go half on its own. -set -eu - -ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" - -main() { - cd "$ROOT" - if [ -n "$(gofmt -l .)" ]; then - echo "gofmt: files need formatting:" >&2 - gofmt -l . >&2 - exit 1 - fi -} - -main "$@" diff --git a/script/gofumpt b/script/gofumpt new file mode 100755 index 0000000..66dba19 --- /dev/null +++ b/script/gofumpt @@ -0,0 +1,44 @@ +#!/bin/sh +# script/gofumpt: run gofumpt over this repo's Go files. +# +# Takes exactly one mode argument, --write or --check, and runs the same +# gofumpt version over the same files in both modes. script/fmt and +# script/fmt-check both go through here, and so does the Docker lint +# stage (make fmt-check-go), so what gets formatted and what gets +# verified cannot drift apart. +set -eu + +ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" + +# gofumpt v0.12.0, 2026-10-04. `go run` fetches and builds exactly this +# version, so neither a developer machine nor the lint image needs +# gofumpt installed. +GOFUMPT="mvdan.cc/gofumpt@v0.12.0" + +usage() { + echo "usage: script/gofumpt --write|--check" >&2 + exit 2 +} + +main() { + [ "$#" -eq 1 ] || usage + cd "$ROOT" + # Every Go file in the repo. gofumpt holds generated files, such as + # mfer/mf.pb.go, to gofmt's rules only. + case "$1" in + --write) go run "$GOFUMPT" -l -w . ;; + --check) + # Own line: a failing command inside `[ -n "$(...)" ]` does + # not trip `set -e`, so a gofumpt that never ran would pass. + unformatted="$(go run "$GOFUMPT" -l .)" + if [ -n "$unformatted" ]; then + echo "gofumpt: files need formatting (run make fmt):" >&2 + echo "$unformatted" >&2 + exit 1 + fi + ;; + *) usage ;; + esac +} + +main "$@"