Clj-kondo can provide data that was collected during linting, which enables writing tools and linters that are not yet in clj-kondo itself. To get this data, use the following configuration:
{:analysis true}
When using clj-kondo from the command line, the analysis data will be exported
with {:output {:format ...}}
set to :json
or :edn
.
Further analysis can be returned by providing :analysis
with a map of options:
{:analysis {... ...}
:locals
: when truthy return:locals
and:local-usages
described below:keywords
: when truthy return:keywords
described below:arglists
: when truthy return:arglist-strs
on:var-definitions
:protocol-impls
: when truthy return:protocol-impls
described below:symbols
: when truthy, return:symbols
described below:java-class-definitions
: when truthy, return:java-class-definitions
as described below.:java-class-usages
: when truthy, return:java-class-usages
as described below.:java-member-definitions
: when truthy, return:java-member-definitions
as described below.:instance-invocations
: when truthy, return:instance-invocations
as described below.
Clj-kondo returns common metadata such as :added
and :deprecated
.
You can request that it return all, or a specific set, of user-coded namespace or var metadata via:
:namespace-definitions
:meta
: return user coded metadata under:namespace-definitions
->:meta
, specify:true
: to return all- key sequence: return metadata matching specified keys, ex.
[:skip-wiki :integration-test]
:var-definitions
:meta
: return user coded metadata under:var-definitions
->:meta
, options are the same as for namespaces.
Built-in and custom hook can add arbitrary data to the analysis using a
:context
map. This context map will currently appear in :var-usages
and
:keywords
. You can opt-in to the entire context map using :context true
or
select certain keys using :context [:re-frame.core]
.
Similarly, analysis can be limited. This is useful to quickly scan a file or
project. When using these expert options, you should not expect linters to
behave correctly. As such, consider using them with :skip-lint true
.
:var-usages
: when falsy skip:var-usages
described below:var-definitions
:shallow true
: analyze:var-definitions
, but skip their bodies
If you analyze var definitions shallowly, anything within the body of a form
listed below will be skipped. That means that if you define a var within another
defn
(a discouraged
practice), it won't be analyzed.
- def
- defn
- defrecord
- defmethod
- plumatic schema: fn, def, defn, defmethod, defrecord
- protocol-impls
- fn
- letfn
- fn*
- bound-fn
The analysis output consists of a map with:
-
:namespace-definitions
, a list of maps with::filename
,:row
,:col
:name
: the name of the namespace
Optional:
:lang
: if definition occurred in a.cljc
file, the language in which the definition was done::clj
or:cljs
- common metadata values:
:deprecated
,:doc
,:author
,:added
,:no-doc
(used by codox). :meta
map of requested metadata for namespace
-
:namespace-usages
, a list of maps with::filename
,:row
,:col
:from
: the namespace which uses:to
: the used namespace:alias
: the alias of namespace, if used
Optional
:lang
: if usage occurred in a.cljc
file, the language in which it was resolved::clj
or:cljs
-
:var-definitions
, a list of maps with::filename
,:row
,:col
,end-row
,end-col
:ns
: the namespace of the var:name
: the name of the var:defined-by
: the namespaced symbol which defined this var
Optional:
:fixed-arities
: a set of fixed arities, unaffected by any:arglists
metadata overrides:varargs-min-arity
: the minimal number of arguments of a varargs signature, unaffected by any:arglists
metadata overrides- common metadata values:
:private
,:macro
,:deprecated
,:doc
,:added
:meta
map of requested metadata for var:lang
: if definition occurred in a.cljc
file, the language in which the definition was done::clj
or:cljs
:arglist-strs
: arg lists as written, or valid looking:arglists
metadata override, ex.["[x]" "[x y]"]
:protocol-ns
,:protocol-name
: the protocol namespace and name for a protocol method:callstack
: experimental, only provided with{:var-definitions {:callstack true}}
-
:var-usages
, a list of maps with::filename
:name
: the name of the used var:row
,col
: the start position of this usage, the parenthesis start location if a function call:end-row
,end-col
: the end position of this usage, the parenthesis end location if a function call:name-row
,:name-col
: the start position of the name of this usage:name-end-row
,:name-end-col
: the end position of the name of this usage:from
: the namespace from which the var was used:to
: the namespace of the used var:from-var
: the function name from which the var was used
Optional:
:arity
: if the usage was a function call, the amount of arguments passed:lang
: if usage occurred in a.cljc
file, the language in which the call was resolved::clj
or:cljs
:defmethod
: true when the usage is a defmethod:dispatch-val-str
: the dispatch val of a defmethod, represented as a string- several attributes of the used var:
:private
,:macro
,:fixed-arities
,:varargs-min-arity
,:deprecated
.
-
:locals
, a list of maps with::filename
,:row
,:col
,:end-row
,:end-col
:id
: an identification for this local,:local-usages
will reference this:name
: the name of the used local:str
: the as written string of the local from the file and position:scope-end-row
: the row in which this local will go out of scope:scope-end-col
: the column in which this local will go out of scope
-
:local-usages
, a list of maps with::filename
,:row
,:col
,:end-row
,:end-col
:name-row
,:name-col
,:name-end-row
,:name-end-col
:id
: an identification for this local, refers to the local declaration with the same:id
in:locals
:name
: the name of the used local
-
:keywords
, a list of maps with:-
:filename
,:row
,:col
,:end-row
,:end-col
-
:name
: the name of the used keyword as string -
:ns
: the namespace of the keyword.:kw
will have anil
ns.::kw
will have the current ns.:b/kw
will haveb
as a ns regardless ofrequire
ed namespaces.::b/kw
will be the aliased ns ofb
or:clj-kondo/unknown-namespace
ifb
is not an alias.
Keywords in namespaced maps:
#:b{:kw 1}
will haveb
as ns.#:b{:_/kw 1}
will have no ns#:b{:c/kw 1}
will havec
as ns.#:b{::kw 1}
will have the current ns.
-
:alias
: the alias used by the keyword. Only present when a valid, external alias.::a/kw
would have an alias ofa
.::kw
does not have an alias.
-
:auto-resolved
: if the keyword:ns
is auto resolved, example:::kw
-
:namespace-from-prefix
: if the keyword:ns
is from the namespaced map, example:::b{:kw 1}
-
:keys-destructuring
: if the keyword is within a:keys
vector. -
:reg
: can be added by:hooks
usingclj-kondo.hook-api/reg-keyword!
to indicate a registered definition location for the keyword. It should be the fully qualified call that registered it.
-
-
:protocol-impls
, a list of maps with:filename
:row
,:col
,:end-row
,:end-col
, the range of the implementation method.:name-row
,:name-col
,:name-end-row
,:name-end-col
, the range of the implementation method name.protocol-name
, the name of the protocol being implemented.protocol-ns
, the namespace of the protocol being implemented.impl-ns
, the namespace of the implementation of the protocol.method-name
, the method name of the implementation.defined-by
, the symbol that defines this, e.g.clojure.core/defrecord
.
-
:symbols
, a list of (namespaced) symbols that occur in quoted forms or EDN, i.e., they do not refer to vars or interop:filename
:row
,:col
,:end-row
,:end-col
, the range of the symbol.:symbol
: the symbol itself:from
: the namespace from which the var was used:name
: the name of the symbol:to
: the namespace the symbol might refer to, if the namespace of the symbol can be resolved in the:from
namespace (e.g. via an alias or via the namespace itself)
-
:java-class-definitions
, a list of maps with::class
: full qualified class name.:uri
: file URI of this class.filename
-
:java-member-definitions
, a list of maps with::class
: full qualified class name of this member.:uri
: file URI of the class.:name
: name of this member.:parameter-types
: list of strings of each parameter type if member is a method.:return-type
: a string representing the return type if member is a method.:type
: a string representing the type of this member if member is a field.:flags
: a set of keywords that classify this member, like final, static, field, method, public.
-
:java-class-usages
, a list of maps with::class
: full qualified class name.:uri
: file URI of this class.filename
:row
,:col
,:end-row
,:end-col
, the range of the usage.:name-row
,:name-col
,:name-end-row
,:name-end-col
the name range of the usage.
-
:instance-invocations
, a list of maps with:filename
:method-name
: name of the method.:name-row
,:name-col
,:name-end-row
,:name-end-col
the name range of the invocation.
Example output after linting this code:
(ns foo
"This is a useful namespace."
{:deprecated "1.3"
:author "Michiel Borkent"
:no-doc true}
(:require [clojure.set :as set]))
(defn- f [x]
(inc x))
(defmacro g
"No longer used."
{:added "1.2"
:deprecated "1.3"}
[x & xs]
`(comment ~x ~@xs))
$ clj-kondo --lint /tmp/foo.clj --config '{:output {:format :edn}, :analysis true}'
| jet --pretty --query ':analysis'
{:namespace-definitions [{:filename "/tmp/foo.clj",
:row 1,
:col 1,
:name foo,
:deprecated "1.3",
:doc "This is a useful namespace.",
:no-doc true,
:author "Michiel Borkent"}],
:namespace-usages [{:filename "/tmp/foo.clj",
:row 6,
:col 14,
:from foo,
:to clojure.set,
:alias set}],
:var-definitions [{:filename "/tmp/foo.clj",
:row 8,
:col 1,
:end-row 9,
:end-col 11,
:ns foo,
:name f,
:defined-by 'clojure.core/defn-
:private true,
:fixed-arities #{1}}
{:added "1.2",
:ns foo,
:name g,
:filename "/tmp/foo.clj",
:defined-by 'clojure.core/defmacro
:macro true,
:row 11,
:col 1,
:end-row 16,
:end-col 22,
:deprecated "1.3",
:varargs-min-arity 1,
:doc "No longer used."}],
:var-usages [{:fixed-arities #{1},
:name inc,
:filename "/tmp/foo.clj",
:from foo,
:col 4,
:from-var f,
:arity 1,
:row 9,
:to clojure.core}
{:name defn-,
:filename "/tmp/foo.clj",
:from foo,
:macro true,
:col 2,
:arity 3,
:varargs-min-arity 2,
:row 8,
:to clojure.core}
{:name comment,
:filename "/tmp/foo.clj",
:from foo,
:macro true,
:col 5,
:from-var g,
:varargs-min-arity 0,
:row 16,
:to clojure.core}
{:name defmacro,
:filename "/tmp/foo.clj",
:from foo,
:macro true,
:col 2,
:arity 5,
:varargs-min-arity 2,
:row 11,
:to clojure.core}]}
NOTE: breaking changes may occur as result of feedback in the next few weeks (2019-07-30).
These are examples of what you can do with the analysis data that clj-kondo provides as a result of linting your sources.
To run the tools on your system you will need the Clojure CLI tool version 1.10.1.466 or higher and then use this repo as a git dep:
{:deps {clj-kondo/tools {:git/url "https://github.com/clj-kondo/clj-kondo"
:sha "1ed3b11025b7f3a582e6db099ba10a888fe0fc2c"
:deps/root "analysis"}}}
Replace the :sha
with the latest SHA of this repo.
You can create an alias for a tool in your ~/.clojure/deps.edn
:
{
:aliases {:unused-vars
{:extra-deps {clj-kondo/tools {:git/url "https://github.com/clj-kondo/clj-kondo"
:sha "1ed3b11025b7f3a582e6db099ba10a888fe0fc2c"
:deps/root "analysis"}}
:main-opts ["-m" "clj-kondo.tools.unused-vars"]}
}
}
and then call it from anywhere in your system with:
$ clj -M:unused-vars src
$ clj -M -m clj-kondo.tools.unused-vars src
The following vars are unused:
clj-kondo.tools.namespace-graph/-main
clj-kondo.tools.unused-vars/-main
A planck port of this example is available in the
script
directory. You can invoke it like this:
script/unused_vars.cljs src
A variation on the above tool, which looks at private vars and reports unused private vars or illegally accessed private vars.
Example code:
(ns foo)
(defn- foo [])
(defn- bar []) ;; unused
(ns bar (:require [foo :as f]))
(f/foo) ;; illegal call
$ clj -M -m clj-kondo.tools.private-vars /tmp/private.clj
/tmp/private.clj:4:8 warning: foo/bar is private but never used
/tmp/private.clj:8:1 warning: foo/foo is private and cannot be accessed from namespace bar
A planck port of this example is available in the
script
directory. You can invoke it like this:
script/private_vars.cljs /tmp/private.clj
This example requires GraphViz. Install with e.g. brew install graphviz
.
$ clj -M -m clj-kondo.tools.namespace-graph src
$ clj -M -m clj-kondo.tools.find-var clj-kondo.core/run! src ../src
clj-kondo.core/run! is defined at ../src/clj_kondo/core.clj:51:7
clj-kondo.core/run! is used at ../src/clj_kondo/core.clj:120:12
clj-kondo.core/run! is used at ../src/clj_kondo/main.clj:81:44
clj-kondo.core/run! is used at src/clj_kondo/tools/find_var_usages.clj:8:29
clj-kondo.core/run! is used at src/clj_kondo/tools/namespace_graph.clj:7:29
clj-kondo.core/run! is used at src/clj_kondo/tools/unused_vars.clj:9:31
$ clj -M -m clj-kondo.tools.popular-vars 10 ../src
clojure.core/let: 196
clojure.core/defn: 183
clojure.core/when: 115
clojure.core/=: 86
clojure.core/if: 86
clojure.core/recur: 79
clojure.core/assoc: 70
clojure.core/or: 68
clojure.core/->: 68
clojure.core/first: 62
$ clj -M -m clj-kondo.tools.missing-docstrings ../src
clj-kondo.impl.findings/reg-finding!: missing docstring
clj-kondo.impl.findings/reg-findings!: missing docstring
clj-kondo.impl.namespace/reg-var!: missing docstring
clj-kondo.impl.namespace/reg-var-usage!: missing docstring
clj-kondo.impl.namespace/reg-alias!: missing docstring
...
Example code:
(ns a (:require b c))
(ns b (:require a)) ;; circular dependency
(ns c (:require a)) ;; circular dependency
$ clj -M -m clj-kondo.tools.circular-dependencies /tmp/circular.clj
/tmp/circular.clj:3:17: circular dependendy from namespace b to a
/tmp/circular.clj:5:17: circular dependendy from namespace c to a
See this repo.
Also see this gist.
Produce an AST from clj-kondo analysis and rewrite-clj nodes.
Example code:
(defn foo [x] x)
(defn bar [x] (foo x))
$ clj -M -m clj-kondo.tools.ast /tmp/example.clj
{:children
({:children
({:sexpr defn, :var-usage true}
{:sexpr foo, :var true}
{:children ({:sexpr x, :local true})}
{:sexpr x, :local-usage true})}
{:children
({:sexpr defn, :var-usage true}
{:sexpr bar, :var true}
{:children ({:sexpr x, :local true})}
{:children
({:sexpr foo, :var-usage true} {:sexpr x, :local-usage true})})})}