Skip to content

Commit 5ae9e86

Browse files
Stop depending on Compliment
1 parent b267983 commit 5ae9e86

File tree

5 files changed

+132
-38
lines changed

5 files changed

+132
-38
lines changed

CHANGELOG.md

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

33
## Unreleased
44

5+
* [#415](https://github.com/clojure-emacs/refactor-nrepl/issues/415): Remove Compliment dependency.
6+
57
## 3.11.0 (2025-05-04)
68

79
* Use `tools.analyzer.jvm` 1.3.2.

project.clj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
:license {:name "Eclipse Public License"
88
:url "https://www.eclipse.org/legal/epl-v10.html"}
99
:dependencies [[nrepl "1.3.1" :exclusions [org.clojure/clojure]]
10-
^:inline-dep [compliment "0.7.0"]
1110
^:inline-dep [http-kit "2.5.0"]
1211
^:inline-dep [org.clojure/data.json "2.5.0"]
1312
^:inline-dep [org.clojure/tools.analyzer.jvm "1.3.2"]
1413
^:inline-dep [org.clojure/tools.namespace "1.5.0" :exclusions [org.clojure/tools.reader]]
1514
^:inline-dep [org.clojure/tools.reader "1.5.2"]
16-
^:inline-dep [cider/orchard "0.34.3" :exclusions [org.clojure/clojure]]
15+
^:inline-dep [cider/orchard "0.35.0" :exclusions [org.clojure/clojure]]
1716
^:inline-dep [cljfmt "0.9.2" :exclusions [rewrite-clj rewrite-cljs]]
1817
^:inline-dep [clj-commons/fs "1.6.310"]
1918
^:inline-dep [rewrite-clj "1.1.49"]

src/refactor_nrepl/ns/class_search.clj

Lines changed: 120 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,128 @@
44
Formerly known as `refactor-nrepl.ns.slam.hound.search`."
55
(:require
66
[clojure.java.io :as io]
7-
[clojure.string :as string]
8-
[compliment.utils])
7+
[clojure.string :as string])
98
(:import
10-
(java.io File)))
11-
12-
(defn- get-available-classes []
13-
(let [classes (compliment.utils/classes-on-classpath)]
14-
(into []
15-
(comp (keep (fn [s]
16-
;; https://github.com/alexander-yakushev/compliment/issues/105
17-
(when (io/resource (-> s (string/replace "." File/separator) (str ".class")))
18-
s)))
19-
(distinct)
20-
(map symbol))
21-
classes)))
22-
23-
(def available-classes
24-
(delay (get-available-classes)))
9+
(java.io File)
10+
(java.nio.file Files)
11+
(java.util.concurrent.locks ReentrantLock)
12+
(java.util.function Function Predicate)
13+
(java.util.jar JarEntry JarFile)
14+
(java.util.stream Collectors)))
2515

26-
(defn- get-available-classes-by-last-segment []
27-
(group-by #(symbol (peek (string/split (str %) #"\."))) @available-classes))
16+
(def ^:private simple-cache (atom {}))
17+
18+
(defn- classpath-strings []
19+
(into [] (keep #(System/getProperty %))
20+
["sun.boot.class.path" "java.ext.dirs" "java.class.path"]))
2821

29-
(def available-classes-by-last-segment
30-
(delay (get-available-classes-by-last-segment)))
22+
(let [lock (ReentrantLock.)]
23+
(defn- recompute-if-classpath-changed [value-fn]
24+
(.lock lock)
25+
(try (let [cache @simple-cache
26+
cp-hash (reduce hash-combine 0 (classpath-strings))
27+
same-cp? (= cp-hash (:classpath-hash cache))
28+
cached-value (:files-on-classpath cache)]
29+
(if (and (some? cached-value) same-cp?)
30+
cached-value
31+
(let [value (value-fn)]
32+
(reset! simple-cache {:classpath-hash cp-hash
33+
:files-on-classpath value})
34+
value)))
35+
(finally (.unlock lock)))))
3136

32-
(defn reset
33-
"Reset the cache of classes"
37+
(defn- classpath
38+
"Returns a sequence of File objects of the elements on the classpath."
3439
[]
35-
(alter-var-root #'available-classes (constantly (delay (get-available-classes))))
36-
(alter-var-root #'available-classes-by-last-segment (constantly (delay (get-available-classes-by-last-segment)))))
40+
(mapcat #(.split ^String % File/pathSeparator) (classpath-strings)))
41+
42+
(defn- file-seq-nonr
43+
"A tree seq on java.io.Files, doesn't resolve symlinked directories to avoid
44+
infinite sequence resulting from recursive symlinked directories."
45+
[dir]
46+
(tree-seq
47+
(fn [^File f] (and (.isDirectory f) (not (Files/isSymbolicLink (.toPath f)))))
48+
(fn [^File d] (seq (.listFiles d)))
49+
dir))
50+
51+
(defn- list-files
52+
"Given a path (either a jar file, directory with classes or directory with
53+
paths) returns all files under that path."
54+
[^String path, scan-jars?]
55+
(cond (.endsWith path "/*")
56+
(for [^File jar (.listFiles (File. path))
57+
:when (.endsWith ^String (.getName jar) ".jar")
58+
file (list-files (.getPath jar) scan-jars?)]
59+
file)
60+
61+
(.endsWith path ".jar")
62+
(if scan-jars?
63+
(try (-> (.stream (JarFile. path))
64+
(.filter (reify Predicate
65+
(test [_ entry]
66+
(not (.isDirectory ^JarEntry entry)))))
67+
(.map (reify Function
68+
(apply [_ entry]
69+
(.getName ^JarEntry entry))))
70+
(.collect (Collectors/toList)))
71+
(catch Exception _))
72+
())
73+
74+
(= path "") ()
75+
76+
(.exists (File. path))
77+
(let [root (File. path)
78+
root-path (.toPath root)]
79+
(for [^File file (file-seq-nonr root)
80+
:when (not (.isDirectory file))]
81+
(let [filename (str (.relativize root-path (.toPath file)))]
82+
(cond-> filename
83+
;; Replace Windows backslashes with slashes.
84+
(not= File/separator "/") (.replace File/separator "/")
85+
(.startsWith filename "/") (.substring filename 1)))))))
86+
87+
(defmacro list-jdk9-base-classfiles
88+
"Because on JDK9+ the classfiles are stored not in rt.jar on classpath, but in
89+
modules, we have to do extra work to extract them."
90+
[]
91+
(when (try (ns-resolve *ns* 'java.lang.module.ModuleFinder) (catch Exception _))
92+
`(-> (.findAll (java.lang.module.ModuleFinder/ofSystem))
93+
(.stream)
94+
(.flatMap (reify Function
95+
(apply [_ mref#]
96+
(.list (.open ^java.lang.module.ModuleReference mref#)))))
97+
(.collect (Collectors/toList)))))
98+
99+
(defn- all-files-on-classpath*
100+
"Given a list of files on the classpath, returns the list of all files,
101+
including those located inside jar files."
102+
[classpath]
103+
(let [seen (java.util.HashMap.)
104+
seen? (fn [x] (.putIfAbsent seen x x))]
105+
(-> []
106+
(into (comp (map #(list-files % true)) cat (remove seen?)) classpath)
107+
(into (remove seen?) (list-jdk9-base-classfiles)))))
108+
109+
(defn- classes-on-classpath* [files]
110+
(let [filename->classname
111+
(fn [^String file]
112+
(when (.endsWith file ".class")
113+
(when-not (or (.contains file "__")
114+
(.contains file "$")
115+
(.equals file "module-info.class"))
116+
(let [c (-> (subs file 0 (- (.length file) 6)) ;; .class
117+
;; Resource separator is always / on all OSes.
118+
(.replace "/" "."))]
119+
;; https://github.com/alexander-yakushev/compliment/issues/105
120+
(when (io/resource (-> c (string/replace "." File/separator) (str ".class")))
121+
c)))))]
122+
(into [] (comp (keep filename->classname) (distinct) (map symbol)) files)))
123+
124+
(defn available-classes []
125+
(classes-on-classpath* (all-files-on-classpath* (classpath))))
126+
127+
(defn- get-available-classes-by-last-segment []
128+
(group-by #(symbol (peek (string/split (str %) #"\."))) (available-classes)))
129+
130+
(defn available-classes-by-last-segment []
131+
(recompute-if-classpath-changed #(get-available-classes-by-last-segment)))

src/refactor_nrepl/ns/imports_and_refers_analysis.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@
5555
(with-meta s
5656
{:refactor-nrepl/is-class true})))
5757
(reduce into #{} [(ns-import-candidates missing)
58-
(get @class-search/available-classes-by-last-segment missing)]))
58+
(get (class-search/available-classes-by-last-segment) missing)]))
5959
:refer (get (symbols->ns-syms) missing)))

test/refactor_nrepl/ns/class_search_test.clj

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,22 @@
6666
(contains? non-initializable-classes v)))
6767

6868
(defn ok []
69-
(is (< 3000 (count @sut/available-classes))
69+
(is (< 3000 (count (sut/available-classes)))
7070
"There are plenty of completions offered / these's a test corpus")
7171

72-
(is (some #{'java.nio.channels.FileChannel} @sut/available-classes))
73-
(is (some #{'java.io.File} @sut/available-classes))
74-
(is (some #{'java.lang.Thread} @sut/available-classes))
72+
(is (some #{'java.nio.channels.FileChannel} (sut/available-classes)))
73+
(is (some #{'java.io.File} (sut/available-classes)))
74+
(is (some #{'java.lang.Thread} (sut/available-classes)))
7575

76-
(is (< 3000 (count @sut/available-classes-by-last-segment)))
76+
(is (< 3000 (count (sut/available-classes-by-last-segment))))
7777

78-
(doseq [x @sut/available-classes
78+
(doseq [x (sut/available-classes)
7979
:let [v (resolve-class x)]]
8080
(when-not (result-can-be-ignored? v)
8181
(is (class? v)
8282
(pr-str x))))
8383

84-
(doseq [[suffix classes] @sut/available-classes-by-last-segment]
84+
(doseq [[suffix classes] (sut/available-classes-by-last-segment)]
8585
(is (seq classes))
8686
(doseq [c classes
8787
:let [v (resolve-class c)]]
@@ -92,9 +92,7 @@
9292
(is (-> c str (.endsWith (str suffix))))))
9393

9494
(is (= '[clojure.lang.ExceptionInfo]
95-
(get @sut/available-classes-by-last-segment 'ExceptionInfo))))
95+
(get (sut/available-classes-by-last-segment) 'ExceptionInfo))))
9696

9797
(deftest works
98-
(ok)
99-
(sut/reset)
10098
(ok))

0 commit comments

Comments
 (0)