简体   繁体   中英

Can I execute a clj-kondo hook before any macro expansions occur?

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. eg 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?

There is already a linter for this:

https://github.com/clj-kondo/clj-kondo/blob/master/doc/linters.md#redundant-call

You just have to enable it with :level:warning .

The reason you're getting non-locations is because metadata is missing on generated nodes. This should fixed in a newer version of clj-kondo. What version were you using?

If you return nil from your hook, clj-kondo continues running with the un-expanded call.

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))))))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM