Skip to content

Commit b4512c6

Browse files
authored
Strengthen/refine resolve-missing (#403)
Fixes #401 Fixes #402
1 parent 4267c2a commit b4512c6

File tree

9 files changed

+219
-88
lines changed

9 files changed

+219
-88
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## Unreleased
44

5+
## 3.9.0
6+
7+
* [#401](https://github.com/clojure-emacs/refactor-nrepl/issues/401) `resolve-missing` no longer returns the wrong type
8+
* [#401](https://github.com/clojure-emacs/refactor-nrepl/issues/401) `resolve-missing` no longer returns non- `import`able classes.
9+
* [#402](https://github.com/clojure-emacs/refactor-nrepl/issues/402) `resolve-missing` now flags with `:already-interned true` the candidates that are already interned into the calling namespace.
10+
* This helps clients avoiding the suggestion/insertion of candidates which are redundant
11+
* e.g. `+` is already interned, so is `Thread`
12+
* same for `:refer`s and `:rename`s
13+
514
## 3.8.0
615

716
* [#396](https://github.com/clojure-emacs/refactor-nrepl/pull/396) Handle the analyzing and parsing of Clojure code from .cljc files.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ deploy: check-env .inline-deps
4141
jar: .inline-deps
4242
lein with-profile -user,-dev,+$(VERSION),+plugin.mranderson/config jar
4343

44-
# Usage: PROJECT_VERSION=3.8.0 make install
44+
# Usage: PROJECT_VERSION=3.9.0 make install
4545
# PROJECT_VERSION is needed because it's not computed dynamically
4646
install: check-install-env .inline-deps
4747
LEIN_JVM_OPTS="-Dmranderson.internal.no-parallelism=true" lein with-profile -user,-dev,+$(VERSION),+plugin.mranderson/config install

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Be aware that this isn't the case if you connect to an already running REPL proc
2626
Add the following, either in your project's `project.clj`, or in the `:user` profile found at `~/.lein/profiles.clj`:
2727

2828
```clojure
29-
:plugins [[refactor-nrepl "3.8.0"]
29+
:plugins [[refactor-nrepl "3.9.0"]
3030
[cider/cider-nrepl "0.31.0"]]
3131
```
3232

@@ -202,11 +202,17 @@ classpath. This symbol can be qualified, e.g. `walk/postwalk` or
202202
is a qualified reference to a clojure var and the second a reference
203203
to a static java method.
204204

205+
The op also now expects (although does not require) `ns`, the current namespace expressed as a string.
206+
205207
The return value `candidates` is a list of `({:name candidate.ns :type :ns} {:name candidate.package :type :type} ...)` where type is in `#{:type :class :ns
206-
:macro}` so we can branch on the various ways to make the symbol
207-
available. `:type` means the symbol resolved to a var created by
208-
`defrecord` or `deftype`, `:class` is for Java classes but also includes interfaces. `:macro`
209-
is only used if the op is called in a cljs context and means the var resolved to macro.
208+
:macro}` so we can branch on the various ways to make the symbol available.
209+
210+
* `:type` means the symbol resolved to a var created by `defrecord` or `deftype`
211+
* `:class` is for Java classes but also includes interfaces.
212+
* `:macro` is only used if the op is called in a cljs context and means the var resolved to macro.
213+
214+
The additional property `:already-interned` (boolean) indicates if the current namespace (as passed as `ns`) already had the given suggested
215+
interned (e.g.`Thread` and `Object` are interned by default in all JVM clojure namespaces). This helps avoiding the insertion of redundant requires/imports.
210216

211217
### hotload-dependency
212218

@@ -365,7 +371,7 @@ When you want to release locally to the following:
365371
And here's how to deploy to Clojars:
366372

367373
```bash
368-
git tag -a v3.8.0 -m "3.8.0"
374+
git tag -a v3.9.0 -m "3.9.0"
369375
git push --tags
370376
```
371377

src/refactor_nrepl/ns/class_search.clj

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,25 @@
33
44
Formerly known as `refactor-nrepl.ns.slam.hound.search`."
55
(:require
6+
[clojure.java.io :as io]
67
[clojure.string :as string]
7-
[compliment.utils]))
8+
[compliment.utils])
9+
(:import
10+
(java.io File)))
811

912
(defn- get-available-classes []
10-
(->> (dissoc (compliment.utils/classes-on-classpath)
11-
"")
12-
(vals)
13-
(reduce into)
14-
(mapv symbol)))
13+
(let [classes (->> (dissoc (compliment.utils/classes-on-classpath)
14+
"")
15+
(vals)
16+
(reduce into))]
17+
(into []
18+
(comp (keep (fn [s]
19+
;; https://github.com/alexander-yakushev/compliment/issues/105
20+
(when (io/resource (-> s (string/replace "." File/separator) (str ".class")))
21+
s)))
22+
(distinct)
23+
(map symbol))
24+
classes)))
1525

1626
(def available-classes
1727
(delay (get-available-classes)))

src/refactor_nrepl/ns/imports_and_refers_analysis.clj

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,18 @@
2121
(defn- symbols->ns-syms
2222
([]
2323
(symbols->ns-syms {} (all-ns)))
24+
2425
([init namespaces]
25-
(reduce
26-
(fn [m ns] (let [ns-sym (ns-name ns)]
27-
(reduce
28-
(fn [m k]
29-
(assoc m k (conj (or (m k) #{}) ns-sym)))
30-
m (keys (ns-publics ns)))))
31-
init namespaces)))
26+
(reduce (fn [m ns]
27+
(let [ns-sym (ns-name ns)]
28+
(reduce (fn [m k]
29+
(assoc m k (conj (or (m k) #{})
30+
(with-meta ns-sym
31+
{:refactor-nrepl/symbol k}))))
32+
m
33+
(keys (ns-publics ns)))))
34+
init
35+
namespaces)))
3236

3337
(defn- ns-import-candidates
3438
"Search (all-ns) for imports that match missing-sym, returning a set of
@@ -38,14 +42,18 @@
3842
[missing-sym]
3943
(reduce (fn [s imports]
4044
(if-let [^Class cls (get imports missing-sym)]
41-
(conj s (symbol (.getCanonicalName cls)))
45+
(->> cls .getCanonicalName symbol (conj s))
4246
s))
4347
#{} (vals (all-ns-imports))))
4448

4549
(defn candidates
4650
"Return a set of class or ns symbols that match the given constraints."
4751
[type missing _body _old-ns-map]
4852
(case type
49-
:import (into (ns-import-candidates missing)
50-
(get @class-search/available-classes-by-last-segment missing))
51-
:refer (get (symbols->ns-syms) missing)))
53+
:import (into #{}
54+
(map (fn [s]
55+
(with-meta s
56+
{:refactor-nrepl/is-class true})))
57+
(reduce into #{} [(ns-import-candidates missing)
58+
(get @class-search/available-classes-by-last-segment missing)]))
59+
:refer (get (symbols->ns-syms) missing)))

src/refactor_nrepl/ns/resolve_missing.clj

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,72 @@
22
"Resolve a missing symbol on the classpath."
33
(:require
44
[cider.nrepl.middleware.util.cljs :as cljs]
5-
[clojure.string :as str]
5+
[clojure.string :as string]
66
[orchard.cljs.analysis :as cljs-ana]
7-
[orchard.info :refer [info]]
7+
[orchard.info]
88
[refactor-nrepl.core :refer [prefix suffix]]
99
[refactor-nrepl.ns.imports-and-refers-analysis :as imports-and-refers-analysis]
1010
[refactor-nrepl.util :refer [self-referential?]]))
1111

1212
(defn- candidates [sym]
1313
(reduce into
14+
[]
1415
[(when-let [p (prefix sym)]
1516
(imports-and-refers-analysis/candidates :import (symbol p) [] {}))
1617
(imports-and-refers-analysis/candidates :import (symbol (suffix sym)) [] {})
1718
(imports-and-refers-analysis/candidates :refer (symbol (suffix sym)) [] {})]))
1819

19-
(defn- get-type [sym]
20-
(let [info (info 'user sym)]
21-
(if-let [_clazz (:class info)]
22-
(cond
23-
((set (:interfaces info)) 'clojure.lang.IType) :type
24-
((set (:interfaces info)) 'clojure.lang.IRecord) :type
25-
:else :class) ; interfaces are included here
26-
:ns)))
20+
(defn- get-type [maybe-ns-sym sym]
21+
(let [info (orchard.info/info (or maybe-ns-sym 'user)
22+
sym)
23+
found? (:class info)
24+
interfaces (if found?
25+
(some-> info :interfaces set)
26+
#{})]
27+
(cond
28+
(interfaces 'clojure.lang.IType) :type
29+
(interfaces 'clojure.lang.IRecord) :type
30+
found? :class ;; interfaces are included here
31+
(-> sym meta :refactor-nrepl/is-class) :class
32+
:else :ns)))
2733

28-
(defn- collate-type-info
29-
[candidates]
30-
(map (fn [candidate]
31-
(try
32-
{:name candidate :type (get-type candidate)}
34+
(defn- collate-type-info [maybe-ns-sym candidates]
35+
(mapv (fn [candidate]
36+
(let [found-ns (some-> maybe-ns-sym find-ns)]
3337

34-
;; This happends when class `candidate` depends on a class that is
35-
;; not available on the classpath.
36-
(catch NoClassDefFoundError e
37-
(refactor-nrepl.util/maybe-log-exception e)
38-
{:name candidate :type :class})))
39-
candidates))
38+
(try
39+
(cond-> {:name candidate
40+
:type (get-type maybe-ns-sym candidate)}
41+
found-ns
42+
(assoc :already-interned (if (-> candidate meta :refactor-nrepl/is-class)
43+
(boolean (when-let [imports (some-> found-ns ns-imports)]
44+
(get imports (-> candidate str (string/split #"\.") last symbol))))
45+
(boolean (when-let [refers (some-> found-ns ns-refers)]
46+
(let [var-namespace= (fn [var-ref]
47+
(-> var-ref
48+
meta
49+
:ns
50+
ns-name
51+
(= candidate)))
52+
candidate-symbol (-> candidate meta :refactor-nrepl/symbol)]
53+
(or (some-> refers ;; refer
54+
(get candidate-symbol)
55+
(var-namespace=))
56+
(some->> refers ;; refer + rename
57+
(some (fn [[k v]]
58+
(when (and (var-namespace= v)
59+
(-> v
60+
meta
61+
:name
62+
(= candidate-symbol)))
63+
k)))))))))))
64+
65+
;; This happends when class `candidate` depends on a class that is
66+
;; not available on the classpath.
67+
(catch NoClassDefFoundError e
68+
(refactor-nrepl.util/maybe-log-exception e)
69+
{:name candidate :type :class}))))
70+
candidates))
4071

4172
(defn- ns-publics-cljs [env ns-name]
4273
(->> ns-name (cljs-ana/public-vars env) keys))
@@ -60,19 +91,24 @@
6091
(apply merge-with into))]
6192
(merge-with into ns-by-vars ns-by-macros)))
6293

63-
(defn resolve-missing [{sym :symbol :as msg}]
64-
(when (or (not (string? sym)) (str/blank? sym))
94+
(defn resolve-missing [{sym :symbol
95+
ns-str :ns
96+
jvm? :refactor-nrepl.internal/force-jvm?
97+
:as msg}]
98+
(when (or (not (string? sym)) (string/blank? sym))
6599
(throw (IllegalArgumentException.
66-
(str "Invalid input to resolve missing: '" sym "'"))))
67-
(if-let [env (cljs/grab-cljs-env msg)]
68-
(some->> sym
69-
suffix
70-
symbol
71-
(get (cljs-vars-to-namespaces env))
72-
pr-str)
73-
(some->> sym
74-
symbol
75-
candidates
76-
(remove self-referential?)
77-
collate-type-info
78-
pr-str)))
100+
(str "Invalid input to resolve-missing: '" sym "'"))))
101+
(let [ns-sym (some-> ns-str not-empty symbol)]
102+
(if-let [env (and (not jvm?)
103+
(cljs/grab-cljs-env msg))]
104+
(some->> sym
105+
suffix
106+
symbol
107+
(get (cljs-vars-to-namespaces env))
108+
pr-str)
109+
(some->> sym
110+
symbol
111+
candidates
112+
(remove self-referential?)
113+
(collate-type-info ns-sym)
114+
pr-str))))

test/refactor_nrepl/ns/imports_and_refers_analysis_test.clj

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@
44
[refactor-nrepl.ns.imports-and-refers-analysis :as sut]))
55

66
(deftest works
7-
(is (contains? '#{#{java.lang.Thread sun.jvm.hotspot.runtime.Thread}
8-
#{java.lang.Thread}}
9-
(sut/candidates :import 'Thread [] {})))
7+
8+
(is (= '#{java.lang.Thread}
9+
(sut/candidates :import 'Thread [] {})))
10+
11+
(is (-> (sut/candidates :import 'Thread [] {}) first meta :refactor-nrepl/is-class))
12+
13+
(is (= '#{java.io.File}
14+
(sut/candidates :import 'File [] {})))
15+
16+
(is (contains? #{'#{com.sun.tools.javac.util.List java.awt.List java.util.List}
17+
'#{com.sun.xml.internal.bind.v2.schemagen.xmlschema.List java.awt.List java.util.List}}
18+
(sut/candidates :import 'List [] {})))
1019

1120
(is (contains? '#{#{clojure.core}
1221
#{clojure.core cljs.core}}

0 commit comments

Comments
 (0)