Skip to content

Commit 99889ca

Browse files
vemvbbatsov
authored andcommitted
Multiple fixes for refactor-nrepl.ns.slam.hound.search
* Handle `delay` correctly * Handle separators correctly for Windows * Use a classpath value that will work * Filter out more Clojure auto-generated classes * Remove namespaces functionality, we don't use it (as we use `all-ns` instead) so it was confusing. Fixes #335
1 parent 34351c3 commit 99889ca

File tree

3 files changed

+115
-55
lines changed

3 files changed

+115
-55
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
### Bugs fixed
2222

23+
* [#335](https://github.com/clojure-emacs/refactor-nrepl/issues/335): Strengthen `resolve-missing` against various edge cases.
2324
* [#289](https://github.com/clojure-emacs/refactor-nrepl/issues/289): Fix an edge-case with involving keywords that caused find-symbol to crash.
2425
* [#305](https://github.com/clojure-emacs/refactor-nrepl/issues/305): Don't put `:as` or `:refer` on their own lines in the ns form, when the libspec is so long it causes the line to wrap.
2526
* [clojure-emacs/clj-refactor.el#459](https://github.com/clojure-emacs/clj-refactor.el/issues/459): `clean-ns` should conform to the style guide: `(:require` in the ns form should be followed by a newline.

src/refactor_nrepl/ns/slam/hound/search.clj

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,32 @@
33
;;;; Distributed under the Eclipse Public License, the same as Clojure.
44
(ns refactor-nrepl.ns.slam.hound.search
55
"Search the classpath for vars and classes."
6-
(:require [orchard.java.classpath :as cp]
7-
[clojure.java.io :refer [file]]
8-
[clojure.string :as string])
6+
(:require
7+
[clojure.java.io :refer [file]]
8+
[clojure.string :as string]
9+
[refactor-nrepl.util :as util])
910
(:import
10-
[java.io File FilenameFilter]
11-
[java.util.jar JarFile JarEntry]
12-
java.util.regex.Pattern
13-
java.util.StringTokenizer))
14-
15-
;;; Mostly taken from leiningen.util.ns and swank.util.class-browse.
16-
17-
;; TODO: replace with bultitude? but that doesn't do classes
18-
19-
;;; Clojure namespaces
11+
(java.io File FilenameFilter)
12+
(java.util StringTokenizer)
13+
(java.util.jar JarEntry JarFile)
14+
(java.util.regex Pattern)))
2015

2116
(defn jar? [^File f]
2217
(and (.isFile f) (.endsWith (.getName f) ".jar")))
2318

2419
(defn class-file? [^String path]
2520
(.endsWith path ".class"))
2621

27-
(defn clojure-fn-file? [f]
28-
(re-find #"\$.*__\d+\.class" f))
22+
(defn clojure-fn-file? [^String file]
23+
;; originally this logic was: (re-find #"\$.*__\d+\.class" f)
24+
;; however that doesn't cover e.g. "clojure/spec/alpha$double_in.class"
25+
;; so we mimic the logic that e.g. Compliment has:
26+
(or (.contains file "__")
27+
(.contains file "$")))
2928

3029
(defn clojure-ns-file? [^String path]
3130
(.endsWith path "__init.class"))
3231

33-
;;; Java classes
34-
35-
;; could probably be simplified
36-
3732
(def jar-filter
3833
(proxy [FilenameFilter] []
3934
(accept [d n] (jar? (file n)))))
@@ -47,24 +42,22 @@
4742
(.. f getParentFile (list jar-filter))
4843
[f])))
4944

50-
(defn class-or-ns-name
51-
"Returns the Java class or Clojure namespace name for a class relative path."
45+
(def resource-separator
46+
"Please do not use File/separator see e.g. https://git.io/Jzig3"
47+
"/")
48+
49+
(defn class-name
5250
[^String path]
53-
(-> (if (clojure-ns-file? path)
54-
(-> path (.replace "__init.class" "") (.replace "_" "-"))
55-
(.replace path ".class" ""))
56-
(.replace File/separator ".")))
51+
(-> path
52+
(.replace ".class" "")
53+
(.replace resource-separator ".")))
5754

5855
(defmulti path-class-files
59-
"Returns a list of classes found on the specified path location
60-
(jar or directory), each comprised of a map with the following keys:
61-
:name Java class or Clojure namespace name
62-
:loc Classpath entry (directory or jar) on which the class is located
63-
:file Path of the class file, relative to :loc"
64-
(fn [^File f _]
65-
(cond (.isDirectory f) :dir
66-
(jar? f) :jar
67-
(class-file? (.getName f)) :class)))
56+
(fn [^File f _loc]
57+
(cond
58+
(.isDirectory f) :dir
59+
(jar? f) :jar
60+
(class-file? (.getName f)) :class)))
6861

6962
(defmethod path-class-files :default [& _] [])
7063

@@ -77,9 +70,13 @@
7770
(comp
7871
(map #(.getName ^JarEntry %))
7972
(filter class-file?)
80-
(map class-or-ns-name))
73+
(remove clojure-fn-file?)
74+
(map class-name))
8175
(enumeration-seq (.entries (JarFile. f))))
82-
(catch Exception _e [])))) ; fail gracefully if jar is unreadable
76+
(catch Exception e
77+
(util/maybe-log-exception e)
78+
;; fail gracefully if jar is unreadable:
79+
[]))))
8380

8481
(defmethod path-class-files :dir
8582
;; Dispatch directories and files (excluding jars) recursively.
@@ -93,46 +90,45 @@
9390
;; Build class info using file path relative to parent classpath entry
9491
;; location. Make sure it decends; a class can't be on classpath directly.
9592
[^File f ^File loc]
96-
(let [fp (str f), lp (str loc)
97-
loc-pattern (re-pattern (Pattern/quote (str "^" loc)))]
98-
(if (re-find loc-pattern fp) ; must be descendent of loc
93+
(let [fp (str f)
94+
lp (str loc)]
95+
(if (re-find (re-pattern (Pattern/quote (str "^" loc))) fp) ; must be descendent of loc
9996
(let [fpr (.substring fp (inc (count lp)))]
100-
[(class-or-ns-name fpr)])
97+
[(class-name fpr)])
10198
[])))
10299

103100
(defn path-entries-seq
104101
"Split a string on the 'path separator', i.e. ':'. Used for splitting multiple
105102
classpath entries."
106103
[path-str]
107-
(enumeration-seq
108-
(StringTokenizer. path-str File/pathSeparator)))
109-
110-
(defn all-classpath-entries []
111-
(mapcat cp/classpath-seq (cp/classpath)))
104+
(-> path-str
105+
(StringTokenizer. File/pathSeparator)
106+
enumeration-seq))
112107

113108
(defn- get-available-classes []
114109
(into ()
115-
(comp (mapcat path-entries-seq)
116-
(mapcat expand-wildcard)
117-
(mapcat #(path-class-files % %))
110+
(comp (mapcat expand-wildcard)
111+
(mapcat (fn [file]
112+
(path-class-files file file)))
118113
(remove clojure-fn-file?)
119114
(distinct)
120115
(map symbol))
121-
(all-classpath-entries)))
116+
;; We use `(System/getProperty "java.class.path")` (at least for the time being) because
117+
;; This code was originally written to handle that string, not a list
118+
;; (this code was broken for a while as `orchard.java.classpath` was being incompatibly used instead)
119+
(path-entries-seq (System/getProperty "java.class.path"))))
122120

123121
(def available-classes
124122
(delay (get-available-classes)))
125123

126-
(defn- get-available-classes-by-last-segment
127-
[]
128-
(delay
129-
(group-by #(symbol (peek (string/split (str %) #"\."))) @available-classes)))
124+
(defn- get-available-classes-by-last-segment []
125+
(group-by #(symbol (peek (string/split (str %) #"\."))) @available-classes))
130126

131127
(def available-classes-by-last-segment
132128
(delay (get-available-classes-by-last-segment)))
133129

134130
(defn reset
135131
"Reset the cache of classes"
136132
[]
137-
(alter-var-root #'available-classes (constantly (get-available-classes)))
138-
(alter-var-root #'available-classes-by-last-segment (constantly (get-available-classes-by-last-segment))))
133+
(alter-var-root #'available-classes (constantly (delay (get-available-classes))))
134+
(alter-var-root #'available-classes-by-last-segment (constantly (delay (get-available-classes-by-last-segment)))))
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
(ns refactor-nrepl.ns.slam.hound.search-test
2+
(:require
3+
[clojure.test :refer [deftest is]]
4+
[refactor-nrepl.ns.slam.hound.search :as sut]))
5+
6+
(def acceptable-error-messages
7+
#{"com/github/luben/zstd/ZstdInputStream"
8+
"org/brotli/dec/BrotliInputStream"
9+
"org/apache/tools/ant/Task"
10+
"com/sun/jdi/request/EventRequest"})
11+
12+
(def non-initializable-classes
13+
'#{org.mozilla.javascript.SecureCaller})
14+
15+
(defn resolve-class [sym]
16+
(try
17+
(Class/forName (str sym)
18+
false
19+
(-> (Thread/currentThread) .getContextClassLoader))
20+
(catch NoClassDefFoundError e
21+
;; there are only 4 in ~7922 classes that cause NoClassDefFoundError,
22+
;; see `#'acceptable-error-messages`.
23+
;; They don't have to do with classpath parsing so there's nothing to be fixed.
24+
(is (contains? acceptable-error-messages (.getMessage e))
25+
(-> e (.getMessage)))
26+
e)
27+
(catch UnsupportedClassVersionError e
28+
e)))
29+
30+
(defn result-can-be-ignored? [v]
31+
(or
32+
(instance? NoClassDefFoundError v)
33+
(instance? UnsupportedClassVersionError v)
34+
(contains? non-initializable-classes v)))
35+
36+
(defn ok []
37+
(is (< 3000 (count @sut/available-classes))
38+
"There are plenty of completions offered / these's a test corpus")
39+
(is (< 3000 (count @sut/available-classes-by-last-segment)))
40+
41+
(doseq [x @sut/available-classes
42+
:let [v (resolve-class x)]]
43+
(when-not (result-can-be-ignored? v)
44+
(is (class? v)
45+
(pr-str x))))
46+
47+
(doseq [[suffix classes] @sut/available-classes-by-last-segment]
48+
(is (seq classes))
49+
(doseq [c classes
50+
:let [v (resolve-class c)]]
51+
(when-not (result-can-be-ignored? v)
52+
(is (class? v)
53+
(pr-str c)))
54+
55+
(is (-> c str (.endsWith (str suffix))))))
56+
57+
(is (= '[clojure.lang.ExceptionInfo]
58+
(get @sut/available-classes-by-last-segment 'ExceptionInfo))))
59+
60+
(deftest works
61+
(ok)
62+
(sut/reset)
63+
(ok))

0 commit comments

Comments
 (0)