tengen icon indicating copy to clipboard operation
tengen copied to clipboard

Hook for linting support with clj-kondo

Open formsandlines opened this issue 2 years ago • 0 comments

Not really an issue with your codebase, but I wanted to share my efforts to provide better linting support for the def-cmptfn macro.

Here is my hook for clj-kondo. It is not perfect (I just got started with the hooks API), but works fine for me. Maybe some users will find it helpful or can improve on it to provide more useful error messages.

(ns hooks.tengen
  (:require [clj-kondo.hooks-api :as api]))

(defn def-cmptfn
  [{:keys [:node]}]
  (let [[var-name-node & more] (rest (:children node))

        _ (when-not (api/token-node? var-name-node)
            (throw (ex-info "Missing function name!" {})))

        [docstring-node
         params-node & body] (if (api/string-node? (first more))
                               more
                               (cons nil more))

        _ (when-not (api/vector-node? params-node)
            (throw (ex-info "Missing function parameters!" {})))

        let-node (let [body-map (->> body
                                     (partition 2)
                                     (map (fn [[k v]] [(api/sexpr k) v]))
                                     (into {}))
                       _ (when-not (== (count body) (* (count body-map) 2))
                           (throw (ex-info "Missing key or value argument!" {})))
                       _ (when-not (every? keyword? (keys body-map))
                           (throw (ex-info "Arguments must be key-value pairs!" {})))

                       bindings (api/vector-node
                                  (concat 
                                    ;; bind “magic bindings” before others
                                    (mapv api/token-node
                                          ['this-mounting? nil
                                           'this-cmpt nil])
                                    (:children (:let-mount body-map))
                                    (:children (:let-render body-map))))
                       exprs (remove nil? 
                                     ;; to prevent “unused bindings” warning
                                     [(api/vector-node
                                        [(api/token-node 'this-mounting?)
                                         (api/token-node 'this-cmpt)])
                                      (:render body-map)
                                      (:post-render body-map)
                                      (:unmount body-map)])]
                   (api/list-node
                     (list* (api/token-node 'let)
                            bindings
                            exprs)))

        fn-node (api/list-node
                  (list (api/token-node 'fn)
                        params-node
                        let-node))

        new-node (api/list-node
                   (remove nil?
                           (list (api/token-node 'def)
                                 var-name-node
                                 docstring-node
                                 fn-node)))]
    {:node new-node}))

I put it in hooks/tengen.clj and wire it up in my config like this:

{...
 :hooks {:analyze-call {taoensso.tengen.reagent/def-cmptfn
                        hooks.tengen/def-cmptfn}}}

formsandlines avatar May 16 '22 11:05 formsandlines