Clj-kondo is a linter based on static analysis. This means it doesn’t eval any code. Therefore clj-kondo can be very fast, but it also comes with some limitations with regards to macros that have irregular syntax compared to core macros.
So far the solution to this has been to provide configuration for :lint-as
or the :unresolved-symbol
linter.
- https://github.com/borkdude/clj-kondo/blob/master/doc/config.md#lint-a-custom-macro-like-a-built-in-macro
- https://github.com/borkdude/clj-kondo/blob/master/doc/config.md#exclude-unresolved-symbols-from-being-reported
This works great for the majority of cases but especially with the :unresolved-symbol
config you lose a bit of linting feedback, i.e. false negatives.
What if we could have a DSL to inform clj-kondo about the syntax of a macro? And what if this DSL was just (a subset of) Clojure itself? So far clj-kondo did not eval any code, but what if we could stretch the limits of static analysis a bit?
I’ve come up with a way to integrate the Small Clojure Interpreter inside clj-kondo so you, as a user, can provide a function that expands a macro invocation into constructs that clj-kondo knows about.
A demo. What if we had a macro that is similar to next.jdbc/with-connection
:
(defmacro weird-macro [[_sym _val _opts] & _body]
::TODO)
An invocation looks like:
(foo/weird-macro
[x :foo {:weird-macro/setting true}] ;; x is unresolved
(inc x))
but alas, clj-kondo doesn’t recognize x
as a binding.
With the new feature a user can provide a transformation function that receives a map with :sexpr
, the invocation and returns a map with :sexpr
, the expanded invocation:
(ns bar
{:clj-kondo/config '{:macroexpand
{foo/weird-macro
"
(fn weird-macro [{:keys [:sexpr]}]
(let [[[sym val opts] & body] (rest sexpr)]
(when-not (and sym val)
(throw (ex-info \"No sym and val provided\" {})))
{:sexpr `(let [~sym ~val] ~@(cons opts body))}))
"}}}
(:require [foo]))
With this configuration clj-kondo understands the x
is a binding:
(foo/weird-macro
[x :foo {:weird-macro/setting true}]
(inc x)) ;; type error
It now even understands that x
is bound to a keyword, hence you get a type error when calling inc
on it.
This feature is not yet released, but you can experiment with it in the branch for this issue. I’d like your feedback on this.
See the issue: https://github.com/borkdude/clj-kondo/issues/811