lean-fmt — Lean 4 formatter and linter

lean-fmt formats Lean 4 source into one canonical style and lints it for common problems: duplicate imports, unused variables, stray set_options, bidirectional control characters.

-- this
def foo(x:Nat):Nat:=x+1

-- becomes this
def foo (x : Nat) : Nat :=
  x + 1

It will not break your code. If lean-fmt cannot prove the formatted output is the same program, it leaves the file alone.

Install

One release per Lean toolchain, tagged exactly as the toolchain. Read yours:

cat lean-toolchain      # leanprover/lean4:v4.34.0-rc1

and require the tag of the same name:

require «lean-fmt» from git
  "https://github.com/jcreinhold/lean-fmt" @ "v4.34.0-rc1"
lake update «lean-fmt»

Lake builds lean-fmt from source against your toolchain. Nothing in your project imports it, so adding the dependency never rebuilds your own modules.

To put lean-fmt on your PATH instead of calling it through lake exe:

git clone --branch v4.34.0-rc1 https://github.com/jcreinhold/lean-fmt.git && cd lean-fmt && make install

With elan on PATH this installs the matching toolchain itself. PREFIX=/usr/local overrides the install location, DESTDIR stages, make uninstall removes.

Windows is untested; nothing in lean-fmt targets a Unix-only interface, and a report either way is useful.

Use

lake exe lean-fmt format          # rewrite files to the canonical layout
lake exe lean-fmt format --check  # preview: which files would change
lake exe lean-fmt format --diff   # preview: the changes as a patch
lake exe lean-fmt check           # report lint findings
lake exe lean-fmt check --fix     # apply the safe fixes

Each runs over the Lake project in the working directory; --root PATH points elsewhere, named files narrow it, and --changed / --changed-since REV / --staged follow git. A preview never writes; a write is atomic and validated first.

Exit codes are the script interface: 0 clean, 1 findings or drift, 2 the run itself failed.

lean-fmt rules lists every rule, lean-fmt explain FMT003 describes one, and lean-fmt <command> --help lists that command's options. Further commands: organize (canonical import headers), lsp, compiler (the optional plugin), config show PATH, clean.

To make lake lint run lean-fmt — which is how leanprover/lean-action picks it up in CI — add two lines to your package:

package myproject where
  lintDriver := "«lean-fmt»/«lean-fmt»"
  lintDriverArgs := #["check"]

Guillemets are required: lean-fmt is not a legal Lean identifier. lean-fmt reports files that do not compile as broken, so exclude any you keep on purpose (linter fixtures, draft notes).

Editors

lean-fmt lsp is a language server offering formatting, range formatting, code actions, and diagnostics. It runs alongside Lean's own server, which offers no formatting. Setup for VS Code, Neovim, and Emacs: docs/editor-setup.md.

Configure

Optional. With no config file, everything is checked with defaults. To configure, add .lean-fmt.toml:

exclude = ["Generated/**"]

[format]
line-width = 100

[lint]
select = ["all"]
ignore = ["FMT004"]

The config file closest to each source file governs it; configs do not merge. lean-fmt honors git ignore files and rejects unknown keys and rule codes. lean-fmt config show PATH prints a file's effective settings and where each came from. docs/configuration.md is the full reference.

Stability

Output will change before 1.0, and a Lean release can change it too, since lean-fmt renders through Lean's pretty-printer. Pin a tag and land a reformat as its own commit.

The line width is a target for breakable syntax, not a guarantee: a string literal, URL, or long identifier can exceed it because no break placement would shorten it.

Documentation

  • The manual — layout, rules, and configuration, worked through with compiled examples.
  • docs/style.md — the canonical style, decision by decision.
  • docs/configuration.md — every config key, selection, streaming, workers, the cache.
  • docs/ci.md — CI recipes, caching, upgrading.
  • docs/editor-setup.md — the language server.
  • docs/maintenance.md — the release policy and what happens when Lean moves.
  • CHANGELOG.md — what changed in each release.

For contributors: docs/adding-a-rule.md, docs/toolchain-upgrade.md, docs/flaky-tests.md, docs/upstream-defects/.

Develop

lake build
lake test              # unit tier plus non-slow suites
lake test -- --all     # everything
lake lint              # the formatter on itself