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[: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 bodiesIf 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.
The analysis output consists of a map with:
:namespace-definitions, a list of maps with:
:filename, :row, :col:name: the name of the namespaceOptional:
:lang: if definition occurred in a .cljc file, the language in which the
definition was done: :clj or :cljs: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 usedOptional
: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 varOptional:
: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: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: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 usedOptional:
: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: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 a nil ns.::kw will have the current ns.:b/kw will have b as a ns regardless of requireed namespaces.::b/kw will be the aliased ns of b or :clj-kondo/unknown-namespace if b is not an alias.Keywords in namespaced maps:
#:b{:kw 1} will have b as ns.#:b{:_/kw 1} will have no ns#:b{:c/kw 1} will have c 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 of a.::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 using clj-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})})})}
Can you improve this documentation? These fine people already did:
Michiel Borkent, Eric Dallo, Case Nelson, Lee Read, Jacob Maine, Sean Poulter, sogaiu & Dominic MonroeEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |