Skip to content

Code Formatting

Moe Aboulkheir edited this page Jul 2, 2020 · 3 revisions

Emacs Config

Below is a minimal config which'll remove trailing whitespace, collapse contiguous blanks and perform aligned indentation when saving Clojure buffers. Extraneous whitespace is also highlighted. It assumes use-package is available.

(setq-default require-final-newline t
              fill-column           80
              indent-tabs-mode      nil)

(use-package whitespace
  :init   (setq whitespace-style '(face trailing tab-mark lines-tail empty))
  :config (global-whitespace-mode 1))

(use-package clojure-mode :ensure t
  :mode ("\\.edn$" . clojure-mode))
  :init
  (setq clojure-indent-style              'always-align
        clojure-align-forms-automatically t
        clojure-use-backtracking-indent   t)
  (add-hook 'clojure-mode-hook
            (lambda ()
              (add-hook 'before-save-hook
                        (lambda ()
                          (save-excursion
                            (whitespace-cleanup)
                            (indent-region (point-min) (point-max) nil)
                            (goto-char (point-min))
                            (while (re-search-forward "\n\n\n+" nil "move")
                              (replace-match "\n\n"))))
                        nil
                        'local)))
  :config
  (dolist (sym '(assoc into ex-info))
    (put-clojure-indent sym :defn)))

"defn" Indents

The cals to put-clojure-indent override the always-align argument indentation behaviour such that:

(into []         ;; => (into []
      '(1 2 3))  ;;      '(1 2 30)) 

And so on for the other forms in the quoted list (assoc, etc.).

Manual Formatting/Organization

Automated tools can't easily handle edge cases, like formatting rows of vectors with variable numbers of columns / entries. These currently must be manually aligned / indented.

(:require)

Namespaces should be arranged such that:

  • :as appears before :refer
  • :refer is preceded by a newline
  • :as keywords are aligned
  • Packages are contiguous by shared prefix (e.g. All foo.* requires are on adjacent lines)
  • clojure.* namespaces appear first

Example

(ns invariant.datahike-test
  (:refer-clojure :exclude [+])
  (:require [clojure.test
             :refer [deftest testing is] :as test]
            [invariant.datahike          :as invariant.d
             :refer [+]]
            [invariant.test.common       :as common]
            [invariant.query
             :refer [assert-valid-query assert-safe-query]]
            [invariant.backend           :as backend]
            [invariant.test.util
             :refer [read-resource]]
            [datahike.api                :as d
             :refer [q]]
            [datahike.core               :as dc]))

(:as)

  • Vectors, not lists
  • Newline after each package name
  • Newline after each imported var

Example

(ns invariant.datomic
  (:import [a.b.c.d
            E
            F
            G]))

Datalog Queries

Values should be aligned vertically, with long, or multi-clause values (e.g. :where) occurring on a new line. This is done recursively for all subvectors.

Example

(def invariant-query '[:find ?q .
                       :in   $ ?a
                       :where
                       [?e :invariant/rule  ?a]
                       [?e :invariant/query ?q]])

Note the whitespace after :in, such that its value aligns with :find's, and after the first :invariant/rule, such that ?a aligns with ?q.

Clone this wiki locally