Clj-kondo is a linter for Clojure code that sparks joy. It analyzes your code without evaluating it and detects arity errors, redundant expressions, unused requires, missing test assertions and more. It provides fast editor feedback because it runs instantly as a native binary, not depending on a REPL in a JVM. In this talk we will see what it can do, how it is implemented and how it compares to other Clojure linters.
Linter Focus CLJS? Macroexpand? Execution
Eastwood General No core + user JVM
Kibit Idioms No No JVM
Joker General Yes core Go native
Cljfmt Formatting Yes n/a JVM
How to ns How to ns Yes n/a Clojure JVM
Clj-kondo General Yes core GraalVM native / JVM
Clj-kondo origin story
• I've been a happy joker user since 2017
• Wanted to add a feature:
(defn foo [x] (def x 1))
• Joker is written in Go, can it be done in Clojure?
• Tried with Clojure on GraalVM (native = fast startup like joker)
• Success with rewrite-clj which is based on tools.reader
• Writing more linting features was too much fun!
Invalid code is still data
$ clj-kondo --lint src
| grep "unresolved symbol c"
info: unresolved symbol c
Unused let and fn bindings
Redundant let + do
Missing test assertion
Style: don't refer :all
Ns form cleanup
Ns form cleanup
• Shared conﬁguration, intended for version control
• Automatically updated when changing code in editor
• Contains arity and other info about linted code
• Allows linter to reason about multiple namespaces
• Populate with lib info: clj-kondo --lint $(clj -Spath)
Clj-kondo doesn't know how to lint this macro
Who uses clj-kondo?
Add your company to the list: