This page contains an overview of all available linters and their corresponding configuration. For general configurations options, go here.
Table of contents:
- Cond-else
- Conflicting-alias
- Consistent-alias
- Datalog syntax
- Deprecated var
- Deps.edn
- Duplicate map key
- Duplicate require
- Duplicate set key
- File
- Format
- Inline def
- Invalid arity
- Misplaced docstring
- Missing body in when
- Missing clause in try
- Missing docstring
- Missing else branch
- Missing map value
- Missing test assertion
- Not empty?
- Private call
- Redefined var
- Redundant do
- Redundant let
- Refer
- Refer all
- Single key in
- Single operand comparison
- Shadowed var
- Syntax
- Type mismatch
- Unbound destructuring default
- Unused binding
- Unreachable code
- Unused import
- Unresolved namespace
- Unresolved symbol
- Unsorted required namespace
- Unused namespace
- Unused private var
- Unused referred var
- Use
Keyword: :cond-else
.
Description: warn on cond
with a different constant for the else branch than :else
.
Default level: :warning
.
Example trigger: (cond (odd? (rand-int 10)) :foo :default :bar)
.
Example message: use :else as the catch-all test expression in cond
.
Keyword: :conflicting-alias
.
Description: warn on conflicting alias.
Default level: :error
.
Example trigger:
(require '[clojure.string :as s]
'[clojure.spec.alpha :as s])
Example message: Conflicting alias for clojure.spec.alpha
.
Keyword: :consistent-alias
Description: Sometimes it's desirable to have a consistent alias for certain
namespaces in a project. E.g. in the below code it could be desirable if every
alias for old.api
was old-api
:
Default level: :warning
.
Example trigger:
(ns foo (:require [new.api :as api]))
(ns bar (:require [old.api :as old-api]))
(ns baz (:require [old.api :as api]))
Config:
The consistent alias linter needs pre-configured aliases for namespaces that should have a consistent alias. This configuration:
{:linters {:consistent-alias {:aliases {old.api old-api}}}}
will produce this warning:
Inconsistent alias. Expected old-api instead of api.
Keyword: :datalog-syntax
.
Description: warn on invalid datalog syntax. This linter is implemented using io.lambdaforge/datalog-parser. Also see this blog post.
Default level: :error
.
Example trigger:
(ns user (:require [datahike.api :refer [q]]))
(q '[:find ?a :where [?b :foo _]] 42)
Example message: Query for unknown vars: [?a]
.
Keyword: :deprecated-var
.
Description: warn on usage of var that is deprecated.
Default level: :warning
.
Example trigger: (def ^:deprecated x) x
Example warning: #'user/x is deprecated
.
Config:
Say you have the following function:
(ns app.foo)
(defn foo {:deprecated "1.9.0"} [])
and you still want to be able to call it without getting a warning, for example in function in the same namespace which is also deprecated:
(defn bar {:deprecated "1.9.0"} []
(foo))
or in test code:
(ns app.foo-test
(:require
[app.foo :refer [foo]]
[clojure.test :refer [deftest is]]))
(deftest foo-test [] (is (nil? (foo))))
To achieve this, use this config:
{:linters
{:deprecated-var
{:exclude
{app.foo/foo
{:defs [app.foo/bar]
:namespaces [app.foo-test]}}}}}
A regex is also permitted, e.g. to exclude all test namespaces:
{:linters {:deprecated-var {:exclude {app.foo/foo {:namespaces [".*-test$"]}}}}}
Keyword: :deps.edn
Description: warn on common errors in a deps.edn
.
Default level: :warning
Example trigger:
deps.edn
:
{:deps {foo/bar "2020.10.11"}}
Example message:
Expected map, found: java.lang.String
Keyword: :duplicate-map-key
.
Description: warn on duplicate key in map.
Default level: :error
.
Example trigger: {:a 1 :a 2}
Example message: duplicate key :a
.
Keyword: :duplicate-require
.
Description: warns on namespace that has been required more than once within a namespace.
Example trigger:
(ns foo
(:require [clojure.string :as str]
[clojure.string :as str]))
Example message: duplicate require of clojure.string
Keyword: :duplicate-set-key
.
Description: similar to :duplicate-map-key
but for sets.
Example trigger: #{:a :a}
Example message: duplicate set element :a
.
Keyword: :file
.
Description: warn on error while reading file.
Default level: :error
.
Example trigger: clj-kondo --lint foo.clje
.
Example message: file does not exist
.
Keyword: :format
.
Description: warn on unexpected amount of arguments in format
.
Default level: :error
.
Example trigger: (format "%s" 1 2)
.
Example message: Format string expects 1 arguments instead of 2.
.
Keyword: :inline-def
.
Description: warn on non-toplevel usage of def
(and defn
, etc.).
Default level: :warning
.
Example trigger: (defn foo [] (def x 1))
.
Example message: inline def
.
Keyword: :invalid-arity
.
Description: warn when a function (or macro) is called with an invalid amount of arguments.
Default level: :error
.
Example trigger: (inc)
.
Example message: clojure.core/inc is called with 0 args but expects 1
.
Config:
Some macros rewrite their arguments and therefore can cause false positive arity errors. Imagine the following silly macro:
(ns silly-macros)
(defmacro with-map [m [fn & args]]
`(~fn ~m ~@args))
which you can call like:
(silly-macros/with-map {:a 1 :d 2} (select-keys [:a :b :c])) ;;=> {:a 1}
Normally a call to this macro will give an invalid arity error for (select-keys [:a :b :c])
, but not when you use the following configuration:
{:linters {:invalid-arity {:skip-args [silly-macros/with-map]}}}
Keyword: :misplaced-docstring
.
Description: warn when docstring appears after argument vector instead of before.
Default level: :warning
.
Example trigger: (defn foo [] "cool fn" 1)
.
Example message: Misplaced docstring.
Keyword: :missing-body-in-when
.
Description: warn when when
is called only with a condition.
Default level: :warning
.
Example trigger: (when true)
.
Example message: Missing body in when
.
Keyword: :missing-clause-in-try
.
Description: warn when try
expression misses catch
or finally
clause.
Default level: :warning
.
Example trigger: (try 1)
.
Example message: Missing catch or finally in try.
Keyword: :missing-docstring
.
Description: warn when public var misses docstring.
Default level: :off
.
Example trigger: (defn foo [] 1)
.
Example message: Missing docstring.
Keyword: :missing-else-branch
.
Description: warns about missing else branch in if
expression.
Default level: :warning
.
Example trigger: (if :foo :bar)
.
Example message: Missing else branch..
Keyword: :missing-map-value
.
Description: warn on key with uneven amount of elements, i.e. one of the keys misses a value.
Default level: :error
.
Example trigger: {:a 1 :b}
Example message: missing value for key :b
.
Keyword: :missing-test-assertion
.
Description: warn on deftest
expression without test assertion.
Default level: :warning
.
Example trigger:
(require '[clojure.test :as test])
(test/deftest foo (pos? 1))
Example message: missing test assertion
.
Keyword: :not-empty?
Description: warn on (not (empty? ...))
idiom. According to the docstring of empty?
seq
is prefered.
Default level: :warning
.
Example trigger: (not (empty? []))
Example message: use the idiom (seq x) rather than (not (empty? x))
.
Keyword :private-call
.
Description: warn when private var is used. The name of this linter should be renamed to "private usage" since it will warn on usage of private vars and not only inside calls.
Default level: :error
.
Example trigger:
(ns foo) (defn- f [])
(ns bar (:require [foo]))
(foo/f)
Example message: #'foo/f is private
.
To suppress the above message, refer to foo/f
using the var #'foo/f
or write:
#_{:clj-kondo/ignore [:private-call]}
(foo/f)
Keyword: :redefined-var
.
Description: warn on redefind var.
Default level: :warning
.
Example trigger: (def x 1) (def x 2)
Example message: redefined var #'user/x
.
Keyword: :redundant-do
.
Description: warn on usage of do that is redundant. The warning usually arises because of an explicit or implicit do as the direct parent s-expression.
Default level: :warning
.
Example trigger: (defn foo [] (do 1))
.
Example message: redundant do
.
Keyword: :redundant-let
.
Description: warn on usage of let that is redundant. The warning usually arises because directly nested lets.
Default level: :warning
.
Example trigger: (let [x 1] (let [y 2] (+ x y)))
.
Example message: Redundant let expression.
Keyword: :refer
Description: warns when :refer
is used. This can be used when one wants to
enforce usage of aliases.
Default level: :off
.
Example trigger: (ns foo (:require [clojure.set :refer [union]]))
.
Example warning: require with :refer
.
Keyword: :refer-all
Description: warns when :refer :all
is used.
Default level: :warning
.
Example trigger: (ns foo (:require [clojure.set :refer :all]))
.
Example message: use alias or :refer
.
Config: to suppress the above warning:
{:linters {:refer-all {:exclude [clojure.set]}}}
Keyword: :single-key-in
.
Description: warn on associative path function with a single value path.
Default level: :off
.
Example trigger: (get-in {:a 1} [:a])
.
Example message: get-in with single key.
Keyword: :single-operand-comparison
.
Description: warn on comparison with only one argument.
Default level: :warning
.
Example trigger: (< 1)
.
Example message: Single operand use of clojure.core/< is always true.
Keyword: :shadowed-var
.
Description: warn on var that is shadowed by local.
Default level: :off
.
Example trigger: (def x 1) (let [x 2] x)
.
Example message: Shadowed var: user/x.
Config:
{:linters {:shadowed-var {:level :warning
:exclude [ns]
:suggest {name nom}}}}
(fn [name] name)
^--- Shadowed var: clojure.core/name. Suggestion: nom
Use :exclude
to suppress warnings for specific binding names. Use :include
to warn only for specific names.
To avoid shadowing core vars you can also use :refer-clojure
+ :exclude
in
the ns
form.
Keyword: :syntax
.
Description: warn on invalid syntax.
Default level: :warning
.
Example trigger: [)
.
Example messages:
Mismatched bracket: found an opening [ and a closing ) on line 1
Mismatched bracket: found an opening [ on line 1 and a closing )
Keyword: :type-mismatch
.
Description: warn on type mismatches, e.g. passing a keyword where a number is expected.
Default level: :error
.
Example trigger: (inc :foo)
Example message: Expected: number, received: keyword.
Config:
You can add or override type annotations. See types.md.
Keyword: :unbound-destructuring-default
.
Description: warn on binding in :or
which does not occur in destructuring.
Default level: :warning
.
Example trigger: (let [{:keys [:i] :or {i 2 j 3}} {}] i)
Example message: j is not bound in this destructuring form
.
Keyword: :unused-binding
.
Description: warn on unused binding.
Default level: :warning
.
Example trigger: (let [x 1] (prn :foo))
Example message: unused binding x
.
Config:
To exclude unused bindings from being reported, start their names with
underscores: _x
.
To exclude warnings about key-destructured function arguments, use:
{:linters {:unused-binding {:exclude-destructured-keys-in-fn-args true}}}
This will disable warnings for the following example:
(defn f [{:keys [:a :b :c]} d])
To disable warnings about :as
bindings (which can be useful for
documentation), use:
{:linters {:unused-binding {:exclude-destructured-as true}}}
This will disable the warning in:
(defn f [{:keys [a b c] :as g}] a b c)
Keyword: :unreachable-code
.
Description: warn on unreachable code.
Default level: :warning
.
Example trigger: (cond :else 1 (odd? 1) 2)
.
Example message: unreachable code
.
Keyword: :unused-import
.
Description: warn on unused import.
Default level: :warning
.
Example trigger: (ns foo (:import [java.util UUID]))
.
Example message: Unused import UUID.
Keyword: :unresolved-namespace
.
Default level: :error
.
Example trigger: foo.bar/baz
.
Example message: Unresolved namespace foo.bar. Are you missing a require?
Config: use :exclude [foo.bar]
to suppress the above warning.
Keyword: :unresolved-symbol
.
Default level: :error
.
Example trigger: x
.
Example message: unresolved symbol x
.
Config:
In the following code streams
is a macro that assigns a special meaning to the
symbol where
, so it should not be reported as an unresolved symbol:
(ns foo
(:require [riemann.streams :refer [streams]]))
(def email (mailer {:host "mail.relay"
:from "riemann@example.com"}))
(streams
(where (and (= (:service event) “my-service”)
(= (:level event) “ERROR”))
,,,))
This is the config for it:
{:linters
{:unresolved-symbol
{:exclude [(riemann.streams/streams [where])]}}}
To exclude all symbols in calls to riemann.streams/streams
write :exclude [(riemann.streams/streams)]
, without the vector.
To exclude a symbol from being reported as unresolved globally in your project, e.g. foo
, you can use :exclude [foo]
.
Sometimes vars are introduced by executing macros, e.g. when using HugSQL's def-db-fns
. You can suppress warnings about these vars by using declare
. Example:
(ns hugsql-example
(:require [hugsql.core :as hugsql]))
(declare select-things)
;; this will define a var #'select-things:
(hugsql/def-db-fns "select_things.sql")
(defn get-my-things [conn params]
(select-things conn params))
If the amount of symbols introduced by HugSQL becomes too unwieldy, consider
introducing a separate namespace in which HugSQL generates the vars:
foo.db.hugsql
. You can then refer to this namespace from foo.db
with
(require '[foo.db.hugsql :as sql]) (sql/insert! ...)
and clj-kondo will not
complain about this.
Furthermore, the :lint-as
option can help treating certain macros like
built-in ones. This is in clj-kondo's own config:
:lint-as {me.raynes.conch/programs clojure.core/declare
me.raynes.conch/let-programs clojure.core/let}
and helps preventing false positive unresolved symbols in this code:
(ns foo (:require [me.raynes.conch :refer [programs let-programs]]))
(programs rm mkdir echo mv)
(let-programs [clj-kondo "./clj-kondo"]
,,,)
Keyword: :unsorted-required-namespace
.
Description: warns on non-alphabetically sorted libspecs in ns
and require
forms.
Default level: :off
.
Example trigger: (ns foo (:require b a))
.
Example message: Unsorted namespace: a
.
Keyword: :unused-namespace
.
Description: warns on required but unused namespace.
Default level: :warning
.
Example trigger: (ns foo (:require [bar :as b]))
.
Example message: namespace bar is required but never used
.
Config:
Given this example:
(ns foo (:require [foo.specs :as specs]))
you will get a warning about foo.specs
being unused.
To suppress this, you can either leave out the alias specs
if it isn't used
anywhere in the namespace or use this config:
{:linters {:unused-namespace {:exclude [foo.specs]}}}
A regex is also supported:
{:linters {:unused-namespace {:exclude [".*\\.specs$"]}}}
This will exclude all namespaces ending with .specs
.
Keyword: :unused-private-var
.
Description: warns on unused private vars.
Default level: :warning
.
Example trigger: (ns foo) (defn- f [])
Example message: Unused private var foo/f
Config:
To suppress the above warning:
{:linters {:unused-private-var {:exclude [foo/f]}}}
Keyword: :unused-referred-var
.
Description: warns about unused referred vars.
Default level: :warning
.
Example trigger: (ns foo (:require [clojure.set :refer [union]]))
.
Example message: #'clojure.set/union is referred but never used
.
Config:
Imagine you want to have taoensso.timbre/debug
available in all of your
namespaces. Even when you don't use it, you don't want to get a warning about
it. That can be done as follows:
{:linters {:unused-referred-var {:exclude {taoensso.timbre [debug]}}}}
Keyword: :use
.
Description: warns about :use
or use
.
Default level: :warning
.
Example trigger: (ns foo (:use [clojure.set]))
.
Example message: use :require with alias or :refer
.