Skip to content

Commit 56ac8e2

Browse files
authored
Don't prune require forms if they are needed for a given import to work (#367)
Fixes #194
1 parent 0b3e68f commit 56ac8e2

File tree

8 files changed

+157
-18
lines changed

8 files changed

+157
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
* [#173](https://github.com/clojure-emacs/refactor-nrepl/issues/173): `rename-file-or-dir`: rename more kinds of constructs in dependent namespaces: namespace-qualified maps, fully-qualified functions, metadata.
6+
* [#194](https://github.com/clojure-emacs/refactor-nrepl/issues/194): Don't prune `require` forms if they are needed for a given `import` to work.
67

78
## 3.3.1
89

src/refactor_nrepl/ns/prune_dependencies.clj

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
(ns refactor-nrepl.ns.prune-dependencies
22
(:require
33
[cider.nrepl.middleware.info :as info]
4+
[clojure.set :as set]
5+
[clojure.string :as string]
46
[refactor-nrepl.core :as core]
57
[refactor-nrepl.find.symbols-in-file :as symbols-in-file]
68
[refactor-nrepl.ns.libspec-allowlist :as libspec-allowlist]
@@ -116,18 +118,77 @@
116118
(-> pattern re-pattern (re-find ns-name)))
117119
(libspec-allowlist/libspec-allowlist)))))
118120

119-
(defn- prune-libspec [symbols-in-file current-ns libspec]
120-
(if (libspec-should-never-be-pruned? libspec)
121+
(defn imports->namespaces
122+
"Given a collection of `:import` clauses, returns the set of namespaces denoted by them, as symbols.
123+
124+
Some of those namespace symbols may not refer to actual namespaces.
125+
e.g. a `java.io.File` import would return `java.io`, which isn't a Clojure namespace."
126+
[imports]
127+
(into #{}
128+
(map (fn [import]
129+
(-> (if (sequential? import)
130+
(first import)
131+
(->> (-> import str (string/split #"\."))
132+
(butlast)
133+
(string/join ".")))
134+
str
135+
(string/replace "_" "-")
136+
symbol)))
137+
imports))
138+
139+
(defn libspec->namespaces
140+
"Given a libspec, returns the namespaces denoted by it (typically one, but possibly multiple,
141+
if prefix notation was used), as symbols."
142+
[libspec]
143+
(cond
144+
(symbol? libspec)
145+
[libspec]
146+
147+
;; Check if it doesn't denote prefix notation:
148+
(and (sequential? libspec)
149+
(or (-> libspec count #{1})
150+
(some keyword? libspec)))
151+
[(first libspec)]
152+
153+
:else
154+
(let [suffixes (->> libspec
155+
rest
156+
(map (fn [suffix]
157+
(cond-> suffix
158+
(sequential? suffix) first))))]
159+
(map (fn [prefix suffix]
160+
(symbol (str prefix "." suffix)))
161+
(repeat (first libspec))
162+
suffixes))))
163+
164+
(defn imports-contain-libspec?
165+
"Do `import-namespaces` contain at least one namespace that is denoted by `libspec`?
166+
167+
This is useful for keeping requires that emit classes (i.e. those defining deftypes/defrecords),
168+
which are imported via `:import`."
169+
[imports-namespaces libspec]
170+
{:pre [(set? imports-namespaces)]}
171+
(let [require-namespaces (set (libspec->namespaces libspec))]
172+
(some? (seq (set/intersection imports-namespaces require-namespaces)))))
173+
174+
(defn- prune-libspec [symbols-in-file current-ns imports-namespaces libspec]
175+
(cond
176+
(libspec-should-never-be-pruned? libspec)
177+
libspec
178+
179+
(imports-contain-libspec? imports-namespaces (:ns libspec))
121180
libspec
181+
182+
:else
122183
(some->> libspec
123184
(remove-unused-renamed-symbols symbols-in-file)
124185
(remove-unused-requires symbols-in-file current-ns))))
125186

126187
(defn- prune-libspecs
127-
[libspecs symbols-in-file current-ns]
128-
(->> libspecs
129-
(map (partial prune-libspec symbols-in-file current-ns))
130-
(filter (complement nil?))))
188+
[libspecs symbols-in-file current-ns imports]
189+
(let [imports-namespaces (imports->namespaces imports)]
190+
(keep (partial prune-libspec symbols-in-file current-ns imports-namespaces)
191+
libspecs)))
131192

132193
(defn- prune-imports
133194
[imports symbols-in-file]
@@ -141,15 +202,17 @@
141202
symbols-in-file (->> (symbols-in-file/symbols-in-file path parsed-ns
142203
dialect)
143204
(map str)
144-
set)]
145-
{dialect (merge {:require
146-
(prune-libspecs required-libspecs symbols-in-file current-ns)
147-
:import (prune-imports (some-> parsed-ns dialect :import)
148-
symbols-in-file)}
205+
set)
206+
;; `imports` are calculated before `requires`, because
207+
;; the former's needs affect whether the latter can be pruned:
208+
imports (prune-imports (some-> parsed-ns dialect :import)
209+
symbols-in-file)
210+
requires (prune-libspecs required-libspecs symbols-in-file current-ns imports)]
211+
{dialect (merge {:require requires
212+
:import imports}
149213
(when (= dialect :cljs)
150214
{:require-macros
151-
(prune-libspecs required-macro-libspecs symbols-in-file
152-
current-ns)}))}))
215+
(prune-libspecs required-macro-libspecs symbols-in-file current-ns #{})}))}))
153216

154217
(defn- prune-cljc-dependencies [parsed-ns path]
155218
(merge

test-resources/defines_deftype.clj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(ns defines-deftype)
2+
3+
(defrecord SomeDefType [])

test-resources/ns1.clj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
(clojure data edn)
1515
[clojure.pprint :refer [get-pretty-writer formatter cl-format]]
1616
clojure.test.junit
17-
[clojure.xml])
17+
[clojure.xml]
18+
[defines-deftype])
1819
(:use clojure.test
1920
clojure.test
2021
[clojure.string :rename {replace foo reverse bar} :reload-all true :reload true])
21-
(:import java.util.Random
22+
(:import [defines_deftype SomeDefType]
23+
java.util.Random
2224
java.io.PushbackReader
2325
java.io.PushbackReader
2426
java.io.FilenameFilter
@@ -30,6 +32,8 @@
3032
SomeClass$InnerClass$InnerInnerClassThree]
3133
(java.util Date Calendar)))
3234

35+
(SomeDefType.)
36+
3337
(defmacro tt [writer]
3438
(Random.)
3539
`(get-pretty-writer ~writer))

test-resources/ns1_cleaned.clj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
[test :refer :all]
1616
[walk :refer [postwalk prewalk]]
1717
xml]
18-
clojure.test.junit)
18+
clojure.test.junit
19+
[defines-deftype])
1920
(:import
21+
[defines_deftype SomeDefType]
2022
[java.io Closeable FilenameFilter PushbackReader]
2123
[java.util Calendar Date Random]
2224
[refactor.nrepl

test-resources/ns1_cleaned_and_pprinted

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
[clojure.test :refer :all]
1818
[clojure.test.junit]
1919
[clojure.walk :refer [postwalk prewalk]]
20-
[clojure.xml])
20+
[clojure.xml]
21+
[defines-deftype])
2122
(:import
23+
(defines_deftype SomeDefType)
2224
(java.io Closeable FilenameFilter PushbackReader)
2325
(java.util Calendar Date Random)
2426
(refactor.nrepl SomeClass$InnerClass$InnerInnerClassOne SomeClass$InnerClass$InnerInnerClassTwo)))

test-resources/ns1_cleaned_and_pprinted_prefix_notation

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
[string :refer :all :reload-all true]
1616
[test :refer :all]
1717
[walk :refer [postwalk prewalk]]]
18-
[clojure.test.junit])
18+
[clojure.test.junit]
19+
[defines-deftype])
1920
(:import
21+
(defines_deftype SomeDefType)
2022
(java.io Closeable FilenameFilter PushbackReader)
2123
(java.util Calendar Date Random)
2224
(refactor.nrepl SomeClass$InnerClass$InnerInnerClassOne SomeClass$InnerClass$InnerInnerClassTwo)))
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
(ns refactor-nrepl.ns.prune-dependencies-test
2+
(:require
3+
[clojure.test :refer [are deftest]]
4+
[refactor-nrepl.ns.prune-dependencies :as sut]))
5+
6+
(deftest imports->namespaces
7+
(are [input expected] (= expected
8+
(sut/imports->namespaces input))
9+
['java.io.File] #{'java.io}
10+
['my_ns.File] #{'my-ns}
11+
['[java.io File]] #{'java.io}
12+
['[java.io File FileReader]] #{'java.io}
13+
['(java.io File)] #{'java.io}
14+
['java.io.File
15+
'my_ns.File
16+
'[java.io File]
17+
'(java.io File)] #{'java.io 'my-ns}))
18+
19+
(deftest libspec->namespaces
20+
(are [input expected] (= expected
21+
(sut/libspec->namespaces input))
22+
'foo.bar
23+
'[foo.bar],
24+
25+
'[foo.bar]
26+
'[foo.bar],
27+
28+
'[foo.bar :as bar]
29+
'[foo.bar],
30+
31+
'[clojure data edn
32+
[instant :as inst :reload true]
33+
[pprint :refer [cl-format formatter get-pretty-writer]]
34+
[string :refer :all :reload-all true]
35+
[test :refer :all]
36+
[walk :refer [postwalk prewalk]]
37+
xml]
38+
'[clojure.data clojure.edn clojure.instant clojure.pprint clojure.string clojure.test clojure.walk clojure.xml]))
39+
40+
(deftest imports-contain-libspec?
41+
(are [imports libspec expected] (= expected
42+
(sut/imports-contain-libspec? (sut/imports->namespaces imports)
43+
libspec))
44+
#_imports #_libspec #_expected
45+
'[] 'foo.bar false
46+
'[foo.bar.SomeType] 'foo.bar true
47+
'[foo_bar.SomeType] 'foo-bar true
48+
'[[foo.bar SomeType]] 'foo.bar true
49+
'[[foo_bar SomeType]] 'foo-bar true
50+
'[foo.bar.SomeType] 'foo.baz false
51+
'[] '[foo.bar] false
52+
'[foo.bar.SomeType] '[foo.bar] true
53+
'[foo.bar.SomeType] '[foo.bar :as f] true
54+
'[[foo.bar SomeType]] '[foo.bar] true
55+
'[[foo_bar SomeType]] '[foo-bar :as f] true
56+
'[foo_bar.SomeType] '[foo-bar :as f] true
57+
'[[foo_bar SomeType]] '[foo-bar] true
58+
'[[foo_bar SomeType]] '[foo-bar :as f] true
59+
'[foo.bar.SomeType] '[foo.baz] false
60+
'[clojure.data.Data] '[clojure data] true
61+
'[clojure.data.Data] '[clojure [data :as d]] true
62+
'[clojure.data.Data] '[clojure foo] false))

0 commit comments

Comments
 (0)