Skip to content

Instantly share code, notes, and snippets.

@borkdude
Forked from roman01la/analyze.clj
Created October 25, 2021 16:28
Show Gist options
  • Select an option

  • Save borkdude/a0b25f70e5fb9c5168d00495b74ce62c to your computer and use it in GitHub Desktop.

Select an option

Save borkdude/a0b25f70e5fb9c5168d00495b74ce62c to your computer and use it in GitHub Desktop.

Revisions

  1. @roman01la roman01la created this gist Mar 18, 2021.
    68 changes: 68 additions & 0 deletions analyze.clj
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    (ns analyze.core
    (:require [clj-kondo.core :as clj-kondo]
    [clojure.set :as set]))

    ;; checks re-frame's :<- syntax
    ;; to mark dependency subscriptions as used
    (def analyze-reg-sub
    "(require '[clj-kondo.hooks-api :as api])
    (fn [{node :node}]
    (let [[_ kw & children] (:children node)
    reg-sub-kw (api/reg-keyword! kw 're-frame.core/reg-sub)
    reg-dep-kw! #(-> % :children first (api/reg-keyword! 're-frame.core/subscribe))
    sub-kws (map #(if (api/vector-node? %) (reg-dep-kw! %) %)
    (butlast children))]
    {:node (api/list-node
    `(do ~reg-sub-kw ~@sub-kws ~(last children)))}))")

    ;; requires subscribtion name (keyword)
    ;; to be statically defined at the call site
    ;; i.e. `(subscribe [:my/sub])`
    (def analyze-subscribe
    "(require '[clj-kondo.hooks-api :as api])
    (fn [{node :node}]
    (let [[_ query] (:children node)
    [kw & children] (:children query)]
    {:node (api/list-node
    `(do ~(api/reg-keyword! kw 're-frame.core/subscribe)
    ~@children))}))")

    (def out
    (clj-kondo/run!
    {:lint ["src"]
    :config {:output {:analysis {:keywords true}}
    :hooks {:__dangerously-allow-string-hooks__ true
    :analyze-call {'re-frame.core/reg-sub analyze-reg-sub
    're-frame.core/subscribe analyze-subscribe}}}}))

    (defn get-keywords-usages [var-name]
    (->> (:analysis out)
    :keywords
    (filter #(= var-name (:def %)))
    (map #(assoc % :kw (if (:ns %)
    (keyword (str (:ns %)) (:name %))
    (keyword (:name %)))))))

    (defn find-unused-keywords [defs usages]
    (let [defs-set (into #{} (map :kw) defs)
    usages-set (into #{} (map :kw) usages)
    unused (set/difference defs-set usages-set)]
    (filter (comp unused :kw) defs)))

    (defn find-usage-of-undefined-keywords [defs usages]
    (let [defs-set (into #{} (map :kw) defs)
    usages-set (into #{} (map :kw) usages)
    undefined (set/difference usages-set defs-set)]
    (filter (comp undefined :kw) defs)))

    (defn fmt-message [warning-type {:keys [kw filename row]}]
    (-> (case warning-type
    :unused-subscription "Unused subscription")
    (str " " kw " at line " row " in " filename)))

    (let [subs [(get-keywords-usages 're-frame.core/reg-sub)
    (get-keywords-usages 're-frame.core/subscribe)]
    unused-subs (apply find-unused-keywords subs)
    undefined-subs (apply find-usage-of-undefined-keywords subs)]
    (doseq [sub unused-subs]
    (println (fmt-message :unused-subscription sub))))