{com.github.camsaul/whitespace-linter {:sha "d10134619f9a53127a17409a6037879452f01bc7"}}
Fast multithreaded and customizable linter that checks files for trailing whitespace, tabs, carriage returns, files that don't end in newlines, files that end in blank lines, Unicode characters that look maddeningly similar to ASCII ones, and invisible Unicode characters. Written in Clojure, but works on any sort of text file.
Requires the Clojure CLI (1.10.3.905 or higher). Install it using the instructions here if you haven't already.
clojure -Sdeps \
'{:aliases {:whitespace-linter {:deps {com.github.camsaul/whitespace-linter {:sha "d10134619f9a53127a17409a6037879452f01bc7"}}
:ns-default whitespace-linter}}}' \
-T:whitespace-linter lint
Add it to your deps.edn
:
{:aliases
{:whitespace-linter
{:deps {com.github.camsaul/whitespace-linter {:sha "d10134619f9a53127a17409a6037879452f01bc7"}}
:ns-default whitespace-linter}}}
and run it:
clj -T:whitespace-linter lint
The easiest way to use the whitespace linter with Leiningen is to create an alias.
(defproject my-project "1.0.0-SNAPSHOT"
:profiles
{:whitespace-linter
{:dependencies [[com.camsaul/whitespace-linter "2022.01.27.04.43" :exclusions [org.clojure/clojure]]]}}
:aliases
{"whitespace-linter"
["with-profile" "+whitespace-linter"
"run" "-m" "clojure.main" "-e" (do
(require 'whitespace-linter)
(whitespace-linter/lint {:paths ["src" "test"]
:include-patterns [#".clj[cs]?$"]}))]})
Then run it with
lein whitespace-linter
You can configure the linter by setting :exec-args
in the deps.edn
alias, or by passing them as arguments to
-T:whitespace-linter lint
:
clj -T:whitespace-linter lint :paths src
{:aliases
{:whitespace-linter
{:deps {com.github.camsaul/whitespace-linter {:sha "ddfcb3c8f4b3bedc6a0d536780840589ab5f0ec4"}}
:ns-default whitespace-linter
:exec-args {:paths ["src" "test" "resources"]
:include-patterns ["\\.clj.?$" "\\.jsx?$" "\\.edn$" "\\.yaml$" "\\.json$" "\\.html$"]
:exclude-patterns ["resources/i18n/.*\\.edn$"]}}}}
Several options are currently supported:
Option | Default | Description |
---|---|---|
:paths |
./ |
Directory(ies) or filename(s) to search for files to lint in. |
:include-patterns |
[#"."] |
File paths that don't match at least one of these patterns will be ignored. |
:exclude-patterns |
nil |
File paths that match any of these patterns will be ignored. |
:max-file-size-kb |
1024 |
Files over this size will be ignored. |
:paths
accepts either strings, symbols, or a collection of multiple strings/symbols.
:include-patterns
and :exclude-patterns
accept either Strings or regex literals (regex literals cannot be embedded in EDN, so use string equivalents instead).
Windows users: The backslash is not recognized as a file separator, use the forward slash /
for the file separator character in paths and patterns.
The code uses Methodical under the hood for easy extensibility. It takes
advantage of the
concat-method-combination
which calls every matching multimethod, and concatenates the results; and the
everything-dispatcher
,
which considers every method implementation to be matching regardless of the arguments passed in. This means all you need to do to extend it is write a new method implementation; Methodical will run it automatically along with the ones that ship out of the box.
To add new linters you should create a new namespace that you use in place of whitespace-linter
. Add method implementations to it:
;; linters/my_project/linters/whitespace_linter.clj
(ns my-project.linters.whitespace-linter
(:require [methodical.core :as m]
[whitespace-linter :as wsl]))
(m/defmethod wsl/lint-char ::no-capital-as
[ch options]
(when (= ch \A)
[{:message "No capital A's are allowed in this project!"
:linter ::no-capital-as}]))
(defn lint [options]
(wsl/lint options))
Linters should return a sequence of error maps with the keys :message
and :linter
for any errors they decide
exist. options
are those passed in to the command via the CLI or :exec-args
in the deps.edn
file.
Update
your deps.edn
to use your new namespace:
{:aliases
{:whitespace-linter
{:deps {com.github.camsaul/whitespace-linter {:sha "d10134619f9a53127a17409a6037879452f01bc7"}}
:ns-default my-project.linters.whitespace-linter
:paths ["linters"]}}}
That's it! Line and column information is added to the output automatically (where applicable):
$ clj -T:whitespace-linter lint :paths naughty.clj
Finding matching files...
Linting 1 files...
1/1 100% [==================================================] ETA: 00:00
Linted 1 files in 0.0 seconds.
Found 4 errors
naughty.clj:1:5 Found Unicode character \u1d21 'ᴡ' that looks way too similar to ASCII 'w' (:character/confusing-unicode-character)
naughty.clj:4:5 No capital A's are allowed in this project! (:my-project.linters.whitespace-linter/no-capital-as)
naughty.clj:4:9 No capital A's are allowed in this project! (:my-project.linters.whitespace-linter/no-capital-as)
naughty.clj:4:23 No capital A's are allowed in this project! (:my-project.linters.whitespace-linter/no-capital-as)
There are three methods you can implement (as many times as you want, of course) to add new linters, depending on which situation is most appropriate:
;; called once for each character
(lint-char ^Character ch options)
;; called once for each line. Line DOES NOT include newlines at the end -- use lint-file if you need those
(lint-line ^String line options)
;; called once for each file
(lint-file ^java.io.File file options)
You can also remove built-in linters using Methodical's remove-primary-method!
:
(m/remove-primary-method! #'wsl/lint-char :character/confusing-unicode-character)
There is not currently a way to selectively disable certain linters for certain files, altho it seems like it wouldn't be to hard to add... PRs are welcome.
Here's an example GitHub action configuration to add the whitespace linter to your project: https://github.com/metabase/metabase/blob/30b54faa9599bff8d17bb46bef1b838de5334135/.github/workflows/whitespace.yml
For interactive or programmatic usage, you can use whitespace-linter/lint-interactive
instead of lint
. This
displays REPL-friendly output (i.e., no progress bar), returns errors in a machine-friendly format, and skips calls to
System/exit
when linting is finished.
Copyright © 2021 Cam Saul.
Distributed under the Eclipse Public License, same as Clojure.