Skip to content

Instantly share code, notes, and snippets.

@roman01la
Created March 18, 2021 01:42
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save roman01la/c6a2e4db8d74f89789292002794a7142 to your computer and use it in GitHub Desktop.
Save roman01la/c6a2e4db8d74f89789292002794a7142 to your computer and use it in GitHub Desktop.
analyzing unused and undefined re-frame subscriptions via clj-kondo
(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))))
@olivergeorge
Copy link

I have been wanting this for sometime. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment