Standard Clojure Style - a formatter for Clojure code
npm install @chrisoakman/standard-clojure-styleA JavaScript library to format Clojure code according to Standard Clojure Style.
I gave a 10-minute lightning talk at [Clojure/conj 2024] about this project:
alt="Introduction to Standard Clojure Style video preview" width="260" height="195" border="10" />
[Clojure/conj 2024]:https://2024.clojure-conj.org/
Please see [Issue #1] for an explanation of this project's genesis.
Standard Clojure Style aspires to be the obvious, [boring choice] for formatting Clojure source code.
* No config options
* All projects using Standard Clojure Style follow the same rules and should have a similar look and feel
* Produces idiomatic-looking Clojure code while not impairing the unique expressiveness of Lisp syntax
* Follows the rules from Niki Tonsky's [Better clojure formatting]
* With the addition of ["Rule 3"] to allow vertical alignment of forms
* All ns forms are pretty-printed from scratch and have a consistent format
* Easy to use and integrate with existing tooling
* continuous integration systems, text editors, web browsers, etc
* Single-file implementation in under 5,000 lines of code
* requires no external libraries
* works everywhere (node.js, web browsers, etc)
* Fast!
* can format ~100,000 lines of code in under 2 seconds
[Issue #1]:https://github.com/oakmac/standard-clojure-style-js/issues/1
[boring choice]:https://boringtechnology.club/
["Rule 3"]:https://github.com/clj-commons/formatter/issues/9#issuecomment-446167649
Try online using Squint playground.
> No purchase necessary. Side effects may include formatted Clojure code, sudden
urges to REPL, and a strange satisfaction of consistently formatted namespaces.
Not responsible for increased productivity due to reduced bikeshedding with coworkers.
Calling all adventurous Clojure developers! Please run this library on your codebase and report bugs.
``shgo to a Clojure project directory
cd your-clojure-project/
npx @chrisoakman/standard-clojure-style fix src-clj/ src-cljs/ test/
`
See the Command Line Usage section below for more options.
Please [open an issue] if Standard Clojure Style breaks your code :upside_down_face:
[open an issue]:https://github.com/oakmac/standard-clojure-style-js/issues/new
It is a goal of Standard Clojure Style to "meet you where you are". ie: in
your editor, on the web, CLI tooling, etc.
- Example Emacs usage in this post
- Neovim plugin
- [Standard Clojure Style in Lua]
- a port in pure Java is almost ready as of Feb 2025
- a Python port is planned as of Nov 2024
[Standard Clojure Style in Lua]:https://github.com/oakmac/standard-clojure-style-lua/tree/master
As of April 2025, this formatter is ready for most Clojure codebases. There
are still [some outstanding bugs] that I want to fix before releasing v1.0.0,
but I do not want this project to live in "pre-1.0" forever.
[some outstanding bugs]:https://github.com/oakmac/standard-clojure-style-js/labels/v1%20blocker
The @chrisoakman/standard-clojure-style npm package exposes a command-line
tool to help format your Clojure projects. You may wish to run this as a git
hook, via continuous integration, an editor integration, etc.
If you have Node.js installed on your system, you can try out Standard Clojure
Style with the npx command:
`shNOTE: the "fix" command will change your files on disk!
Please ensure a clean git working tree or new branch as necessary
If you plan to use the library frequently you may wish to install it globally:
`sh
Installs "standard-clj" globally onto your system via npm
npm install --global @chrisoakman/standard-clojure-style
`#### Quick Reference
`sh
use the "list" command to see which files standard-clj will analyze
standard-clj list src/use the "check" command to see which files need formatting
standard-clj check src-clj/ src-cljs/use the "fix" command to format files with Standard Clojure Style
standard-clj fix src/ test/ project.cljyou can pass a glob pattern for more control over which files are formatted
standard-clj fix --include "src/*/.{clj,cljs,cljc}"ignore files or folders with the --ignore flag
standard-clj fix --include "src/*/.{clj,cljs,cljc}" --ignore "src/com/example/some_weird_file.clj"standard-clj will look for a .standard-clj.edn or .standard-clj.json file in the directory where
the command is run from (likely the root directory for your project)
echo '{:include ["src-clj//.clj" "src-cljs//.cljs"]}' > .standard-clj.edn
standard-clj fixor pass a config file explicitly using the --config argument
standard-clj list --config /home/user1/my-project/my-standard-cfg.jsonpipe code directly to the fix command using "-"
echo '(ns my.company.core (:require [clojure.string :as str]))' | standard-clj fix -
`####
list commandUse
standard-clj list to see which files will be effected by the check and
fix commands. This command is useful in order to test your --include
glob patterns or .standard-clj.edn config files.`sh
prints each filename that will be effected by the "check" and "fix" commands
standard-clj list src/output the same file list in various data formats
standard-clj list src/ --output json
standard-clj list src/ --output json-pretty
standard-clj list src/ --output edn
standard-clj list src/ --output edn-pretty
`####
check commandUse
standard-clj check to see if files are already formatted with Standard
Clojure Style. Useful for continuous integration. This command will not write
to any files on disk.Returns exit code 0 if all files are already formatted, 1 otherwise.
`sh
check to see if files are already formatted with Standard Clojure Style
standard-clj check src-clj/ src-cljs/ test/runs the same check, but only prints files that need fixing
standard-clj check src-clj/ src-cljs/ test/ --log-level=ignore-already-formatted
`####
fix commandUse
standard-clj fix to format files according to Standard Clojure Style.
This command will write to files on disk, so please ensure a clean git
working tree or new branch as necessary. The changes made by this command
cannot be undone by this program.Returns exit code 0 if all files have been formatted, 1 otherwise.
`sh
format files according to Standard Clojure Style
standard-clj fix src/ test/ deps.edn
`####
fix - command (stdin / stdout)Use
standard-clj fix - to pipe code directly via stdin.Prints the formatted code to stdout with error code 0 if successful. Prints an
error message to stderr with error code 1 otherwise.
`sh
echo '(ns my.company.core (:require [clojure.string :as str]))' | standard-clj fix -
`#### Which files will be formatted?
standard-clj accepts several ways to know which files to format:* pass filenames directly as arguments
* pass directories directly as arguments
* pass a [glob pattern] with the
--include option`sh
will fix:
- dev/user.clj (single file argument)
- project.clj (single file argument)
- all .clj, .cljs, .cljc, .edn files in the src-clj/ directory and subdirectories (directory argument)
- all .edn files in the resources/ directory and subdirectories (glob pattern argument)
standard-clj fix dev/user.clj project.clj src-clj/ test/ --include "resources/*/.edn"
`--include or --ignore arguments passed via command line will supercede any
--include or --ignore arguments found via config file.You can always use the
list command to see which files will be formatted by standard-clj.#### Other options
-
--config or -c - pass a filepath of a config file to use for options to the standard-clj program.
- --ignore or -ig - exclude files from list, check, or fix commands. Accepts individual files or directories.
- --include or -in - include files for the list, check, or fix commands. Accepts a [glob pattern].
- --log-level or -l - specify a logging level
- "everything" or 0 - prints everything to either stdout or stderr. This is the default.
- "ignore-already-formatted" or 1
- For the check command, will only print files that need formatting.
- For the fix command, will only print files that were formatted or have errors.
- This option can be less noisy in your terminal if you have a project with many files and only
want to see the ones that need formatting.
- "quiet" or 5 - will not print anything to stdout or stderr for the check or fix commands[glob pattern]:https://github.com/isaacs/node-glob?tab=readme-ov-file#glob-primer
#### Options via config file
By default,
standard-clj will look for a .standard-clj.edn or
.standard-clj.json file located in the directory where the command is run.
Most projects that use standard-clj regularly will want to commit this file
to their git repo for convenience.`sh
create a .standard-clj.edn file
echo '{:include ["src-clj//.clj" "src-cljs//.cljs"]}' > .standard-clj.ednrun the "fix" command with options from that file
standard-clj fix
`You can use the
--config or -c flag to specify a different file location:`sh
run the "fix" command with options from ./my-config-file.edn
standard-clj fix --config ./my-config-file.edn
`Ignore a file or form
You can instruct Standard Clojure Style to ignore the next form by using
#_:standard-clj/ignore`clj
#_:standard-clj/ignore
[:the
:formatter
:will :ignore :me
]
`Or ignore an entire file by placing
#_:standard-clj/ignore-file before the (ns) form.`clj
#_:standard-clj/ignore-file(ns com.example.some-weird-file)
;; ...
`Please note that
#_:standard-clj/ignore will not work inside of the ns form, but it can be used
to tell Standard Clojure Style to "ignore the ns form entirely":`clj
;; this will NOT work(ns com.example.my-app
(:require
#_:standard-clj/ignore
[clojure.string :as string]))
``clj
;; this WILL work#_:standard-clj/ignore
(ns com.example.my-app
(:require
[clojure.string :as string]))
`It is recommended to use
#_:standard-clj/ignore sparingly, and ideally not
at all. However, there are always edge case exceptions where it makes sense
to ignore formatting.I recommend ignoring whole forms at the top-level (ie: forms that start on
column 0, like
defn or ns), instead of "some formatted outside, some ignored inside".`clj
;; recommended, sparingly:#_:standard-clj/ignore
(defn some-weird-fn []
...)
``clj
;; not recommended:(defn some-weird-fn []
(let [a "a"
b "b"]
#_:standard-clj/ignore ...))
`Creating a binary
[Bun] has a neat feature where you can [create an executable binary] from JavaScript source:
`sh
create a binary for Standard Clojure Style
bun build ./cli.mjs --compile --outfile standard-cljrun your binary
./standard-clj check /home/user1/my-project/srcmove the binary to somewhere on your path
mv standard-clj /usr/local/bin
`[Bun]:https://bun.sh/
[create an executable binary]:https://bun.sh/docs/bundler/executables
Formatting Rules
> NOTE: this is an incomplete list. I am working on a website that will document all of the formatting rules. 20 Sep 2024
- trim trailing whitespace (ie:
rtrim every line)
- convert all "\r\n" to "\n"
- convert all tab characters to spaces (except tab characters inside of Strings)
- ensure a single newline character (\n) at the end of the file
- [cljfmt option] :remove-surrounding-whitespace? = true
- [cljfmt option] :remove-trailing-whitespace? = true
- [cljfmt option] :insert-missing-whitespace? = true
- [cljfmt option] :remove-consecutive-blank-lines? = true
- format and sort ns forms according to Stuart Sierra's [how to ns]
- indentation follows the guide from Niki Tonsky's [Better clojure formatting]
- with the addition of Rule 3 as proposed by Shaun Lebron
- Use #_ :standard-clj/ignore or #_ :standard-clj/ignore-file to disable the formatter for certain special cases[how to ns]:https://stuartsierra.com/2016/clojure-how-to-ns.html
[cljfmt option]:https://github.com/weavejester/cljfmt#formatting-options
[Better clojure formatting]:https://tonsky.me/blog/clojurefmt/
Things that Standard Clojure Style does NOT do
- no config options
- all projects using Standard Clojure Style follow the same rules
- From cljfmt:
> "It is not the goal of the project to provide a one-to-one mapping between a Clojure syntax tree and formatted text; rather the intent is to correct formatting errors with minimal changes to the existing structure of the text.
> If you want format completely unstructured Clojure code, the zprint project may be more suitable.
- no enforced max line length
- text editors have the ability to wrap lines if you desire
- vertical alignment of
let forms and map literals are allowed
- the choice is up to the author
- [cljfmt option] :remove-multiple-non-indenting-spaces? = false
- I have seen too many code examples where vertical alignment adds clarity
- no configuration or special rules for indentation
- the rules from [Better clojure formatting] are simple, easy to learn, and produce consistent-looking code
- 100% compatible with [Parinfer] users
- avoids the complexity of the [cljfmt :indents option]
- avoids the complexity of different rules for different forms (ie: no [semantic indentation])[Parinfer]:https://shaunlebron.github.io/parinfer/
[cljfmt
:indents option]:https://github.com/weavejester/cljfmt/blob/master/docs/INDENTS.md
[semantic indentation]:https://guide.clojure.style/#body-indentationReferences
- https://clojureverse.org/t/clj-commons-building-a-formatter-like-gofmt-for-clojure/3240/95
- https://github.com/clj-commons/formatter/issues/9
- https://tonsky.me/blog/clojurefmt/
- https://github.com/parinfer/parindent
- emoji length article
- https://github.com/weavejester/cljfmt/issues/36
- https://github.com/weavejester/cljfmt/pull/251
- https://github.com/weavejester/cljfmt/commit/23daaf0020526aaaaab1cd6363288e79091a97ba
Coding Style
The coding style for this library is intentionally very simple in order to make
porting the algorithm to multiple languages easier. This is informed by my
experience porting [parinfer.js] to multiple languages ([parinfer-lua], [parinfer.py],
and others).
Here are some rules to follow:
* each line should be one simple statement
* do not use ternary operators
* do not use variadic functions
* no
for loops, only use while
* do not use ++ or -- operators (wrap with function calls)
* wrap all String and Array methods with function calls
* do not early return from functionsNote: this should not be considered a definitive list. I will add to this as I come across additional cases.
[parinfer.js]:https://github.com/parinfer/parinfer.js
[parinfer.py]:https://github.com/oakmac/parinfer.py
[parinfer-lua]:https://github.com/oakmac/parinfer-lua
Development
Make sure that either [Node.js] or [bun] are installed (both should work).
`sh
run unit tests
bun testtest a single file
bun run jest format.test.jslint JS
bun run lint
`[Node.js]:https://nodejs.org/
[bun]:https://bun.sh/
Notes / Misc
* ns order is:
1.
:refer-clojure
1. :require-macros
1. :require
1. :import
* Note that [how to ns] does not include guidance for :require-macros
* ClojureScript source (1, 2, 3) consistently places :require-macros above :require, so let's go with that
* reader conditionals are placed at the bottom of the relevant ns section
* sorted alphabetically except for :default` (if it exists), which is last[how to ns]:https://stuartsierra.com/2016/clojure-how-to-ns.html