From fb975c6c61f7e998b59677c806af3ce7d263e216 Mon Sep 17 00:00:00 2001 From: vemv Date: Tue, 8 Feb 2022 00:32:16 +0100 Subject: [PATCH] Honor clj-kondo `:unused-namespace` config, if present (#361) --- .clj-kondo/config.edn | 5 +- CHANGELOG.md | 3 ++ README.md | 9 ++-- src/refactor_nrepl/middleware.clj | 10 ++-- src/refactor_nrepl/ns/libspec_allowlist.clj | 49 +++++++++++++++++++ src/refactor_nrepl/ns/prune_dependencies.clj | 12 +++-- .../ns/libspec_allowlist_test.clj | 32 ++++++++++++ 7 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 src/refactor_nrepl/ns/libspec_allowlist.clj create mode 100644 test/refactor_nrepl/ns/libspec_allowlist_test.clj diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 08dd2a01..acb74eef 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -40,4 +40,7 @@ clojure.tools.namespace.track tracker}} :unresolved-symbol {:exclude [(refactor-nrepl.ns.ns-parser/with-libspecs-from [libspecs]) (refactor-nrepl.middleware/set-descriptor! [set-descriptor!])]} - :unresolved-namespace {:exclude [clojure.main]}}} + :unresolved-namespace {:exclude [clojure.main]} + ;; for integration tests: + :unused-namespace {:exclude [sample.unused.namespace + "more.unused.namespaces*"]}}} diff --git a/CHANGELOG.md b/CHANGELOG.md index 998c2da1..d6776161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* [#361](https://github.com/clojure-emacs/refactor-nrepl/pull/361) Honor clj-kondo `:unused-namespace` config, if present + * This piece of config can inform/complement refactor-nrepl's own config. + ## 3.2.2 (2022-01-29) * Fix a minor artifact in the previous release (the version would be reported as 0.0.0). diff --git a/README.md b/README.md index 304b1f87..458d7bea 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Be aware that this isn't the case if you connect to an already running REPL proc Add the following, either in your project's `project.clj`, or in the `:user` profile found at `~/.lein/profiles.clj`: ```clojure -:plugins [[refactor-nrepl "3.2.1"] +:plugins [[refactor-nrepl "3.3.0"] [cider/cider-nrepl "0.25.9"]] ``` @@ -37,7 +37,7 @@ Add the following in `~/.boot/profile.boot`: (require 'boot.repl) (swap! boot.repl/*default-dependencies* conj - '[refactor-nrepl "3.2.1"] + '[refactor-nrepl "3.3.0"] '[cider/cider-nrepl "0.25.9"]) (swap! boot.repl/*default-middleware* conj @@ -121,6 +121,7 @@ Configuration settings are passed along with each msg, currently the recognized ;; even if they're otherwise unused. ;; This seq of strings will be used as regexp patterns to match ;; against the libspec name. + ;; This value is automatically augmented with configured clj-kondo's :unused-namespace config. :libspec-whitelist ["^cljsjs"] ;; Regexes matching paths that are to be ignored @@ -370,12 +371,12 @@ If you want to use `mranderson` while developing locally with the REPL, the sour When you want to release locally to the following: - PROJECT_VERSION=3.2.1 make install + PROJECT_VERSION=3.3.0 make install And here's how to deploy to Clojars: ```bash -git tag -a v3.2.1 -m "3.2.1" +git tag -a v3.3.0 -m "3.3.0" git push --tags ``` diff --git a/src/refactor_nrepl/middleware.clj b/src/refactor_nrepl/middleware.clj index f87ab20c..5be23673 100644 --- a/src/refactor_nrepl/middleware.clj +++ b/src/refactor_nrepl/middleware.clj @@ -5,6 +5,7 @@ [clojure.walk :as walk] [refactor-nrepl.config :as config] [refactor-nrepl.core :as core] + [refactor-nrepl.ns.libspec-allowlist :as libspec-allowlist] [refactor-nrepl.ns.libspecs :refer [namespace-aliases]] [refactor-nrepl.stubs-for-interface :refer [stubs-for-interface]])) @@ -59,10 +60,11 @@ ~transport (response-for ~msg (err-info e# :refactor-nrepl-error)))))) (defmacro ^:private reply [transport msg & kvs] - `(with-errors-being-passed-on ~transport ~msg - (config/with-config ~msg - (transport/send ~transport - (response-for ~msg ~(apply hash-map kvs)))))) + `(libspec-allowlist/with-memoized-libspec-allowlist + (with-errors-being-passed-on ~transport ~msg + (config/with-config ~msg + (transport/send ~transport + (response-for ~msg ~(apply hash-map kvs))))))) (defn- bencode-friendly-data [data] ;; Bencode only supports byte strings, integers, lists and maps. diff --git a/src/refactor_nrepl/ns/libspec_allowlist.clj b/src/refactor_nrepl/ns/libspec_allowlist.clj new file mode 100644 index 00000000..5e706987 --- /dev/null +++ b/src/refactor_nrepl/ns/libspec_allowlist.clj @@ -0,0 +1,49 @@ +(ns refactor-nrepl.ns.libspec-allowlist + (:require + [clojure.java.io :as io] + [refactor-nrepl.config :as config]) + (:import + (java.util.regex Pattern))) + +(defn- libspec-allowlist* [] + (let [kondo-file (io/file ".clj-kondo" "config.edn") + exclude (when (.exists kondo-file) + (try + (-> kondo-file slurp read-string :linters :unused-namespace :exclude) + (catch Exception e + (when (System/getenv "CI") + (throw e)))))] + (->> exclude + (mapv (fn [entry] + (if (symbol? entry) + (str "^" (Pattern/quote (str entry)) "$") + entry))) + (into (:libspec-whitelist config/*config*))))) + +(def ^:private ^:dynamic *libspec-allowlist* nil) + +(defn with-memoized-libspec-allowlist* [f] + (binding [*libspec-allowlist* (memoize libspec-allowlist*)] + (f))) + +(defn libspec-allowlist + "Obtains a libspec allowlist, which is the result of merging clj-refactor's own `:libspec-whitelist` + with clj-kondo's `:unused-namespace` config. + + Uses a memoized version if available." + [] + (or *libspec-allowlist* + (libspec-allowlist*))) + +(defmacro with-memoized-libspec-allowlist + "Memoizes the libspec-allowlist internals while `body` is executing. + + _Temporary_ memoization is important because: + + * one does want to reload clj-kondo config if the user changes it, without needing a JVM restart; + * one doesn't want that to imply a performance hit in terms of repeatedly reading clj-kondo config files." + {:style/indent 0} + [& body] + `(with-memoized-libspec-allowlist* + (fn [] + ~@body))) diff --git a/src/refactor_nrepl/ns/prune_dependencies.clj b/src/refactor_nrepl/ns/prune_dependencies.clj index 80d52732..90af6f9e 100644 --- a/src/refactor_nrepl/ns/prune_dependencies.clj +++ b/src/refactor_nrepl/ns/prune_dependencies.clj @@ -1,9 +1,9 @@ (ns refactor-nrepl.ns.prune-dependencies (:require [cider.nrepl.middleware.info :as info] - [refactor-nrepl.config :as config] [refactor-nrepl.core :as core] [refactor-nrepl.find.symbols-in-file :as symbols-in-file] + [refactor-nrepl.ns.libspec-allowlist :as libspec-allowlist] [refactor-nrepl.util :as util])) (defn- lookup-symbol-ns @@ -108,11 +108,13 @@ ;; Some namespaces, e.g. those containing only protocol extensions, ;; are side-effecting at load but might look unused and otherwise be ;; pruned. -(defn- libspec-should-never-be-pruned? [libspec] +(defn libspec-should-never-be-pruned? + "Should `libspec` never be pruned away by the `clean-ns` op?" + [libspec] (let [ns-name (str (:ns libspec))] - (some (fn [^String pattern] - (re-find (re-pattern pattern) ns-name)) - (:libspec-whitelist config/*config*)))) + (boolean (some (fn [^String pattern] + (-> pattern re-pattern (re-find ns-name))) + (libspec-allowlist/libspec-allowlist))))) (defn- prune-libspec [symbols-in-file current-ns libspec] (if (libspec-should-never-be-pruned? libspec) diff --git a/test/refactor_nrepl/ns/libspec_allowlist_test.clj b/test/refactor_nrepl/ns/libspec_allowlist_test.clj new file mode 100644 index 00000000..cca1f40f --- /dev/null +++ b/test/refactor_nrepl/ns/libspec_allowlist_test.clj @@ -0,0 +1,32 @@ +(ns refactor-nrepl.ns.libspec-allowlist-test + (:require + [clojure.test :refer [are deftest is testing]] + [refactor-nrepl.ns.libspec-allowlist :as sut] + [refactor-nrepl.ns.prune-dependencies :as prune-dependencies])) + +(deftest libspec-allowlist + (testing "Takes into account refactor-nrepls own config, and .clj-kondo/config files alike, +merging their results" + (is (= [;; From refactor-nrepl's default config: + "^cljsjs" + ;; from our .clj-kondo file - symbols become quoted patterns: + "^\\Qsample.unused.namespace\\E$" + ;; from our .clj-kondo file - strings have 'regex' semantics so are kept as-is: + "more.unused.namespaces*"] + + (sut/libspec-allowlist))) + + (is (every? string? (sut/libspec-allowlist)) + "Items coming from different sources all have the same class, +ensuring they will be treated homogeneously by refactor-nrepl") + + (testing "`libspec-should-never-be-pruned?` is integrated with clj-kondo logic, +effecively parsing its config into well-formed regexes" + (are [input expected] (= expected + (prune-dependencies/libspec-should-never-be-pruned? {:ns input})) + 'sample.unused.namespace true + 'Asample.unused.namespace false + 'sample.unused.namespaceB false + 'more.unused.namespaces true + 'more.unused.namespacessss true + 'more.unused.namespac false))))