Automatically format Clojure buffers

Contrary to my experience with free or open source projects, when it comes to the proprietary software I have been developing for work since late 2004, code formatting has never been matter of discussion. There was a team, back in my days in the UK, that enforced some strict rules via Ant, but until today that was the sole exception.

Luckily the people I am coding with nowadays are more open to this kind of problem. Yes, I used the word problem. As I explained to a co-worker when I raised the question of formatting tools, I read code as I read a book. I cannot imagine sitting through different books each written according to the authors’ own ideas of stylistic conventions. It’s about having the text, whether words or code1, accessible to anyone by making the assumption that the first step to understanding is ease of legibility.

The team agreed on using cljfmt2 for this task. Most of the developers here use Clojure LSP and, from what I understand, the integration with cljfmt is already there for them. I, on the other hand, went with a more manual approach. I first built the cljfmt binary with GraalVM thanks to Rune Juhl Jacobsen’s useful cljfmt-graalvm3. Rune offers a snippet for use-package and Projectile users, but I leveraged project.el and a CIDER hook instead.

(defun mu-cljfmt-format-buffer ()
  "Format current buffer with `cljfmt'."
  (when (derived-mode-p 'clojure-mode)
    (let* ((pr (project-root (project-current)))
           (cfg-dir (locate-dominating-file pr "cljfmt.edn"))
           (cfg-edn (expand-file-name "cljfmt.edn" cfg-dir))
           (cmd (if (file-exists-p cfg-edn)
                    (format "cljfmt -c %s %s" cfg-edn buffer-file-name)
                  (format "cljfmt %s" buffer-file-name))))
      (shell-command-to-string cmd))
    (revert-buffer t t)))

(defun mu-cider-format-buffer ()
  "Automatically format current buffer after saving."
  (add-hook 'after-save-hook #'mu-cljfmt-format-buffer nil t))

(add-hook 'cider-mode-hook #'mu-cider-format-buffer)

Some things worth considering:

  • the location and the name of the cljfmt.edn file matter, so check your projects if you follow my approach. For instance, the snippet provided by Rune uses .cljfmt.edn;

  • clojurec-mode and clojurescript-mode are both derived from clojure-mode. This explains the use of derived-mode-p;

  • in buffers handled by clojure-mode I always have CIDER enabled, even if not necessarily jacked-in. For a more general approach, clojure-mode-hook should be preferred;

  • CIDER provides an integration with cljfmt4. Depending on your needs that may be already enough.

With a tool tweaked to format code idiomatically5 for us, now it’s time to argue about names. Wait, let me refresh my hadoukens first.

  1. By the way, looking at code as text is the subject of Sense and Structure: Towards a Textual Analysis of Software, a recent talk by Zach Tellman. 

  2. See: cljfmt

  3. See: cljfmt-graalvm

  4. See: Formatting Code with cljfmt

  5. cljfmt follows the Clojure Style Guide by default.