devfmt

Source: github.com/red-oxide/devfmt

One formatting setup for all my repos: a flake-parts module built on treefmt-nix, with formatters and linters for the languages I use and a strict rule that every file in a repo is formatted by something or explicitly excluded.

A flake that uses it gets nix fmt to format the whole tree, and a treefmt check in nix flake check that fails when anything isn't formatted.

Using it

Add devfmt as an input and build the flake with devfmt.lib.mkFlake:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    devfmt = {
      url = "github:chrisppy/devfmt";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs =
    inputs:
    inputs.devfmt.lib.mkFlake { inherit inputs; } {
      imports = [ (inputs.devfmt.lib.importTree ./nix) ];
    };
}

Then:

nix fmt                 # format everything
nix flake check         # includes checks.<system>.treefmt, run on a copy

What it provides

OutputWhat it is
lib.mkFlake args moduleflake-parts' mkFlake with devfmt included, systems from nix-systems/default, and pkgs taken from your nixpkgs input. module can be one module or a list of them.
lib.importTree dirimport-tree: every .nix file under dir as a flake-parts module. Files there must be modules; paths containing /_ (such as nix/_options.nix) are skipped, for plain Nix helpers.
flakeModules.defaultThe module itself, to add to a flake that already uses flake-parts: imports = [ inputs.devfmt.flakeModules.default ];.
formatter.<system>devfmt's own treefmt wrapper, for formatting devfmt.

Flakes using the module get formatter.<system> (for nix fmt) and checks.<system>.treefmt from treefmt-nix's defaults. Turn either off with perSystem.treefmt.flakeFormatter = false or flakeCheck = false.

Formatters

treefmt runs every formatter that matches a file, in priority order.

FilesFormatters
Nixdeadnix, statix, nixf-diagnose, then nixfmt (nixfmt-rs)
Rustrustfmt
C, C++clang-format, clang-tidy
Gogofmt, gofumpt, goimports, golangci-lint
Shell, fishshellcheck, fish_indent
Swiftswift-format
Kotlin (.kt, .kts)ktfmt
QMLqmlformat
TOMLtaplo, with aligned entries and sorted keys
justfilesjust --fmt
LaTeXlatexindent, texfmt
XML, property lists (.plist, .entitlements)xmllint
Markdowndeno fmt, then mdformat
JSON, JSONC, YAML, CSS, SCSS, Sassdeno fmt
HTMLdjlint (Tera profile), which also lints

Every file must match

on-unmatched is fatal: a file no formatter handles fails nix fmt and the check, so new file types can't slip in unformatted. These are excluded everywhere:

  • LICENSE, LICENSE-*, and LICENSES/** (license texts, which REUSE keeps verbatim)
  • .git, .gitattributes, .gitignore, **/.gitkeep
  • images and fonts: *.avif, *.gif, *.ico, *.jpeg, *.jpg, *.png, *.svg, *.webp, *.woff, *.woff2
  • the Gradle wrapper and settings: gradlew, gradlew.bat, gradle/wrapper/**, gradle.properties
  • *.patch, **/qmldir, secrets/**

To exclude more in a project, add to the list (it merges with devfmt's):

perSystem.treefmt.settings.global.excludes = [ "vendor/**" ];

Other treefmt-nix options work the same way, for example perSystem.treefmt.programs.<name>.enable to add a formatter.