/clj-kondo

A linter for Clojure code that sparks joy.

Primary LanguageClojureEclipse Public License 1.0EPL-1.0

Clojars Project Financial Contributors on Open Collective CircleCI Build status cljdoc badge project chat twitter

A 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

Rationale

Clj-kondo performs static analysis on Clojure, ClojureScript and EDN, without the need of a running REPL. It informs you about potential errors while you are typing.

Features

Clj-kondo detects:

  • inline def expressions
  • redundant do and let wrappings
  • arity errors:
    • within the same namespace and across namespaces
    • of static Java method calls
    • of local let and letfn binding calls
    • of recursive calls (including recur)
  • unused private vars
  • private and deprecated var usage
  • required but unused namespaces
  • unsorted required namespaces
  • referred but unused vars
  • duplicate requires
  • unused function arguments and let bindings
  • unused imports
  • redefined vars
  • unresolved symbols and namespaces
  • misplaced docstrings
  • duplicate map keys and set elements
  • missing map keys
  • invalid number of forms in binding vectors
  • missing assertions in clojure.test/deftest
  • alias consistency
  • type checking
  • Datalog syntax checking
  • format string argument mismatches

before your form hits the REPL.

It suggests several style guide recommendations, such as:

It has support for syntax of commonly used macros like clojure.core.async/alt!!, schema.core/defn and potemkin/import-vars.

It provides analysis data so you build your own custom linters.

This linter is:

  • compatible with .clj, .cljs, .cljc and .edn files
  • build tool and editor agnostic
  • a static code analyzer
  • compiled to native code using GraalVM

Try clj-kondo at the interactive playground.

Watch the talk:

Clj-kondo at ClojuTRE 2019

Usage

Command line

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)"

Project setup

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. 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:

$ clj-kondo --parallel --lint "<classpath>"

Build tool specific ways to get a classpath:

  • lein classpath
  • boot with-cp -w -f -
  • clojure -Spath

So for lein the entire command would be:

$ clj-kondo --parallel --lint "$(lein classpath)"

For boot the entire command would be:

$ clj-kondo --parallel --lint "$(boot with-cp -w -f -)"

And for clojure CLI, the entire command would be:

$ clj-kondo --parallel --lint "$(clojure -Spath)"

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

  • 0: no errors or warnings were found
  • 2: more than one warning was found
  • 3: more than one error was found

All other error codes indicate an unexpected error.

Companies using clj-kondo

Babashka pod

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}

Podcast

Thanks to:

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

Copyright © 2019 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.