A static analyzer and linter for Clojure code that sparks joy.
Thanks a lot for clj-kondo. It is like a companion for me. It has made clojure fun again.
— @geraldodev on Clojurians Slack
Clj-kondo performs static analysis on Clojure, ClojureScript and EDN. It informs you about potential errors while you are typing (without executing your program).
Clj-kondo detects:
def
expressionsdo
and let
wrappingslet
and letfn
binding callsrecur
)clojure.test/deftest
before your form hits the REPL.
It suggests several style guide recommendations, such as:
:else
as the catch-all test expression in cond
(see Clojure style guide)seq
instead of (not (empty? ,,,))
(see Clojure style guide)It has support for syntax of commonly used macros like
clojure.core.async/alt!!
, schema.core/defn
and potemkin/import-vars
.
It detects common errors in deps.edn
and bb.edn
It provides analysis data so you build your own custom linters.
View all available linters here.
This linter is:
.clj
, .cljs
, .cljc
and .edn
filesTry clj-kondo at the interactive playground.
Watch the talk:
You can support this project via Github Sponsors, OpenCollective, Ko-fi or indirectly via Clojurists Together.
Lint from stdin:
$ echo '(def x (def x 1))' | clj-kondo --lint -
<stdin>:1:8: warning: inline def
Lint a file:
$ echo '(def x (def x 1))' > /tmp/foo.clj
$ clj-kondo --lint /tmp/foo.clj
/tmp/foo.clj:1:8: warning: inline def
Lint a directory:
$ clj-kondo --lint src
src/clj_kondo/test.cljs:7:1: warning: redundant do
src/clj_kondo/calls.clj:291:3: error: Wrong number of args (1) passed to clj-kondo.calls/analyze-calls
Lint a project classpath:
$ clj-kondo --lint "$(lein classpath)"
To detect lint errors across namespaces in your project, a cache is needed. To
let clj-kondo know where to create one, make a .clj-kondo
directory in the
root of your project, meaning on the same level as your project.clj
,
deps.edn
or build.boot
:
$ mkdir -p .clj-kondo
A cache will be created inside of it when you run clj-kondo
. Before linting
inside your editor, it is recommended to lint the entire classpath to teach
clj-kondo
about all the libraries you are using, including Clojure and/or
ClojureScript itself. Some libraries come with configurations. To import them, first run:
$ clj-kondo --lint "<classpath>" --dependencies --copy-configs --skip-lint
The --copy-configs
flag will search and copy configurations from dependencies into the
.clj-kondo
directory, while linting (see
config.md).
With the configurations in place, now we can analyze the dependencies properly:
$ clj-kondo --lint "<classpath>" --dependencies --parallel
The --dependencies
flag indicates that clj-kondo is used to analyze sources to
populate the cache. When enabled, clj-kondo will suppress warnings and skips over
already linted .jar
files for performance.
The --parallel
option will use multiple threads to lint your sources, going through them faster.
NOTE: in the version after 2024.05.24
copying configs and linting dependencies can be done in one go using:
$ clj-kondo --lint "<classpath>" --dependencies --parallel --copy-configs
Build tool specific ways to get a classpath:
lein classpath
boot with-cp -w -f -
clojure -Spath
npx shadow-cljs classpath
So for lein
the entire command would be:
$ clj-kondo --lint "$(lein classpath)" --dependencies --parallel --copy-configs
Now you are ready to lint single files using editor integration. A simulation of what happens when you edit a file in your editor:
$ echo '(select-keys)' | clj-kondo --lang cljs --lint -
<stdin>:1:1: error: Wrong number of args (0) passed to cljs.core/select-keys
Since clj-kondo now knows about your version of ClojureScript via the cache,
it detects that the number of arguments you passed to select-keys
is
invalid. Each time you edit a file, the cache is incrementally updated, so
clj-kondo is informed about new functions you just wrote.
If you want to use a different directory to read and write the cache, use the
--cache-dir
option. To disable the cache even if you have a .clj-kondo
directory, use --cache false
.
Exit codes can be controlled by the --fail-level <level>
option. The
default fail level is warning
which returns exit codes as follows:
0
: no errors or warnings were found2
: one or more warnings were found3
: one or more errors were foundIf --fail-level error
is supplied, warnings do not lead to a non-zero exit code:
0
: no errors were found0
: one or more warnings were found3
: one or more errors were foundAll exit codes other than 0
, 2
and 3
indicate an error because of a bug in
clj-kondo or some other unexpected error beyond the control of clj-kondo.
As clj-kondo is a static analyzer is does not need a runtime (JVM, browser, Node.js, etc.). It doesn't execute your code. As such it can be a faster alternative to linters that do use a runtime, like eastwood. This approach comes with the limitation that clj-kondo cannot execute your macros as macros can use arbitrary features from a runtime. Clj-kondo has support for clojure core macros and some popular libraries from the community. Macros that are not supported out of the box can be supported using configuration. One of the ways to configure macros is to write hooks for them (also see this blogpost). For many libraries there is already a configuration available that you can import. Also check out clj-kondo configs which contains configurations for third party libraries.
Clj-kondo can be invoked as a babashka pod.
#!/usr/bin/env bb
(ns script
(:require [babashka.pods :as pods]))
(pods/load-pod "clj-kondo")
(require '[pod.borkdude.clj-kondo :as clj-kondo])
(clj-kondo/merge-configs
'{:linters {:unresolved-symbol {:exclude [(foo1.bar)]}}}
'{:linters {:unresolved-symbol {:exclude [(foo2.bar)]}}})
;;=> {:linters {:unresolved-symbol {:exclude [(foo1.bar) (foo2.bar)]}}}
(-> (clj-kondo/run! {:lint ["src"]})
:summary)
;;=> {:error 0, :warning 0, :info 0, :type :summary, :duration 779}
var-info.edn
and inspirationCopyright © 2019 - 2023 Michiel Borkent
Distributed under the EPL License, same as Clojure. See LICENSE.
The directory inlined
contains source from tools.reader
which is licensed under the EPL license.
The directory parser
contains modified source from rewrite-clj
which is licensed under the MIT license.
Can you improve this documentation? These fine people already did:
Michiel Borkent, mknoszlig, Taiju Aoki, Davide Taviani, sogaiu, Dominic Monroe, Isaak Uchakaev, Eric Dallo, Robert Stuttaford, Rick Moynihan, hoclun-rigsep, Walter Leibbrandt, Rachel Westmacott, Lee Read & jessEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close