I've got a clj-kondo hook which tells me when I'm threading a value through only one form:
;; .clj-kondo/config.edn
{
...
:hooks {:analyze-call {clojure.core/-> peter.analyzers/superfluous-arrow
clojure.core/->> peter.analyzers/superfluous-arrow}}}
}
;; ./.clj-kondo/peter/analyzers.clj
(ns peter.analyzers
(:require
[clj-kondo.hooks-api :as api]))
(defn superfluous-arrow
[{:keys [node]}]
(let [[arrow _data & forms] (:children node)]
(when (= 1 (count forms))
(api/reg-finding!
(assoc (meta node)
:message (format "%s: no need to thread a single form - %s (meta %s)" arrow node (meta node))
:type :peter.analyzers/superfluous-arrow)))))
When I run clj-kondo I get some false positives. e.g. if I run the above on this file:
;; bogus.clj
(ns bogus)
;; from
(defn do-stuff
[coll {:keys [map-fn max-num-things batch-size]}]
(cond->> coll
map-fn (map map-fn)
max-num-things (take max-num-things)
batch-size (partition batch-size)))
I get the following warnings:
bogus.clj::: warn: clojure.core/->>: no need to thread a single form - (clojure.core/->> G__4 (map map-fn))
bogus.clj::: warn: clojure.core/->>: no need to thread a single form - (clojure.core/->> G__4 (take max-num-things))
bogus.clj::: warn: clojure.core/->>: no need to thread a single form - (clojure.core/->> G__4 (partition batch-size))
linting took 37ms, errors: 0, warnings: 0
It looks like this is because the cond->>
macro is getting expanded then the hook is running on the expanded code.
Is there a way to ensure that my hooks run on the verbatim nodes in the source files, rather than after macro expansion, to avoid this problem?
Since version v2022.12.08
, clj-kondo has a generated-node?
function in the hooks API that checks if a node was generated by a macro.
So if you want your hook to only execute on verbatim code from your source files, guard your hook code with (when-not (generated-node? node) ...)
. So in the case of the hook in the question, you could do this:
(ns peter.analyzers
(:require
[clj-kondo.hooks-api :as api]))
(defn superfluous-arrow
[{:keys [node]}]
(when-not (api/generated-node? node)
(let [[arrow _data & forms] (:children node)]
(when (= 1 (count forms))
(api/reg-finding!
(assoc (meta node)
:message (format "%s: no need to thread a single form - %s" arrow node)
:type :peter.analyzers/superfluous-arrow))))))