Skip to content

Commit b78e354

Browse files
authored
Introduce with-suppressed-errors (#317)
Fixes #291
1 parent 8457b03 commit b78e354

23 files changed

+217
-95
lines changed

CHANGELOG.md

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

33
## Unreleased
44

5+
#### Changes
6+
* [#291](https://github.com/clojure-emacs/refactor-nrepl/issues/291): The `:ignore-errors` option will be honored in more places, making refactor-nrepl more robust in face of files not particularly meant to be part of the AST corpus.
7+
* Examples: WIP files, Moustache template files, scripts.
8+
* Upgrade Orchard
9+
* Worth emphasizing: now the following options are available https://github.com/clojure-emacs/orchard/tree/v0.7.0#configuration-options
10+
* They can make the refactor-nrepl experience simpler / more robust.
11+
* Reliability improvement: try using `require` prior to `find-ns`
12+
* This increases the chances that a namespace will be found, which in turns makes refactor-nrepl more complete/accurate.
13+
* Replace Cheshire with `clojure.data.json`
14+
* Build ASTs more robustly (by using locks and ruling out certain namespaces like refactor-nrepl itself)
15+
516
### Bugs fixed
617
* [#289](https://github.com/clojure-emacs/refactor-nrepl/issues/289): Fix an edge-case with involving keywords that caused find-symbol to crash.
718
* [#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.

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ Configuration settings are passed along with each msg, currently the recognized
8181

8282
:debug false
8383

84+
;; if `true`:
85+
;; * files that can't be `read`, `require`d or analyzed (with `tools.analyzer`) will be ignored,
86+
;; instead of aborting the early phases of refactor-nrepl execution.
87+
;; * ops like `find-symbol` will carry on even if there is broken namespace which we can not build AST for.
88+
;; Setting `false` will be more strict, yielding possibly more correct usage,
89+
;; but it also needs that `:ignore-paths` is correctly set, that all namespaces are valid,
90+
;; that tools.analyzer is able to analyze all of them, etc
91+
:ignore-errors true
92+
8493
;; When true `clean-ns` will remove unused symbols, otherwise just
8594
;; sort etc
8695
:prune-ns-form true

eastwood.clj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(disable-warning
2+
{:linter :unused-ret-vals-in-try
3+
:if-inside-macroexpansion-of #{'clojure.test/deftest
4+
'clojure.core/assert}})

project.clj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,13 @@
6666
with-debug-bindings [[:inner 0]]
6767
merge-meta [[:inner 0]]
6868
try-if-let [[:block 1]]}}}]
69-
:eastwood {:plugins [[jonase/eastwood "0.7.0"]]
69+
:eastwood {:plugins [[jonase/eastwood "0.7.1"]]
7070
:eastwood {;; vendored - shouldn't be tweaked for satisfying linters:
7171
:exclude-namespaces [refactor-nrepl.ns.slam.hound.regrow]
7272
;; :implicit-dependencies would fail spuriously when the CI matrix runs for Clojure < 1.10,
7373
;; because :implicit-dependencies can only work for a certain corner case starting from 1.10.
74-
:exclude-linters [:implicit-dependencies]}}
74+
:exclude-linters [:implicit-dependencies]
75+
:config-files ["eastwood.clj"]}}
7576
:clj-kondo [:test
7677
{:dependencies [[clj-kondo "2021.06.18"]]}]}
7778

src/refactor_nrepl/analyzer.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
"OK"))))))
133133

134134
(defn warm-ast-cache []
135-
(doseq [f (tracker/project-files-in-topo-order)]
135+
(doseq [f (tracker/project-files-in-topo-order true)]
136136
(try
137137
(ns-ast (slurp f))
138138
(catch Throwable th

src/refactor_nrepl/config.clj

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

99
:debug false
1010

11+
;; if `true`:
12+
;; * files that can't be `read`, `require`d or analyzed (with `tools.analyzer`) will be ignored,
13+
;; instead of aborting the early phases of refactor-nrepl execution.
14+
;; * ops like `find-symbol` will carry on even if there is broken namespace which we can not build AST for.
15+
;; Setting `false` will be more strict, yielding possibly more correct usage,
16+
;; but it also needs that `:ignore-paths` is correctly set, that all namespaces are valid,
17+
;; that tools.analyzer is able to analyze all of them, etc
18+
:ignore-errors true
19+
1120
;; When true `clean-ns` will remove unused symbols, otherwise just
1221
;; sort etc
1322
:prune-ns-form true

src/refactor_nrepl/find/find_macros.clj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@
8181
(defn- find-macro-definitions-in-project
8282
"Finds all macros that are defined in the project."
8383
[ignore-errors?]
84-
(->> (core/find-in-project (some-fn core/cljc-file? core/clj-file?))
84+
(->> (core/find-in-project (util/with-suppressed-errors
85+
(some-fn core/cljc-file? core/clj-file?)
86+
ignore-errors?))
8587
(mapcat #(try
8688
(get-macro-definitions-in-file-with-caching %)
8789
(catch Exception e
@@ -215,7 +217,7 @@
215217
(when (fully-qualified-name? fully-qualified-name)
216218
(let [all-defs (find-macro-definitions-in-project ignore-errors?)
217219
macro-def (first (filter #(= (:name %) fully-qualified-name) all-defs))
218-
tracker (tracker/build-tracker)
220+
tracker (tracker/build-tracker (util/with-suppressed-errors tracker/default-file-filter-predicate ignore-errors?))
219221
origin-ns (symbol (core/prefix fully-qualified-name))
220222
dependents (tracker/get-dependents tracker origin-ns)]
221223
(some->> macro-def

src/refactor_nrepl/find/find_symbol.clj

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -135,20 +135,22 @@
135135
fully-qualified-name (if (= namespace "clojure.core")
136136
var-name
137137
(str/join "/" [namespace var-name]))
138-
referred-syms (libspecs/referred-syms-by-file&fullname)]
138+
referred-syms (libspecs/referred-syms-by-file&fullname ignore-errors)]
139139
(->> (core/dirs-on-classpath)
140-
(mapcat (partial core/find-in-dir (every-pred (some-fn core/clj-file? core/cljc-file?)
141-
(fn [f]
142-
(try
143-
(let [n (some-> f
144-
core/read-ns-form
145-
parse/name-from-ns-decl)]
146-
(if-not n
147-
false
148-
(not (self-referential? n))))
149-
(catch Exception e
150-
(util/maybe-log-exception e)
151-
false))))))
140+
(mapcat (partial core/find-in-dir (util/with-suppressed-errors
141+
(every-pred (some-fn core/clj-file? core/cljc-file?)
142+
(fn [f]
143+
(try
144+
(let [n (some-> f
145+
core/read-ns-form
146+
parse/name-from-ns-decl)]
147+
(if-not n
148+
false
149+
(not (self-referential? n))))
150+
(catch Exception e
151+
(util/maybe-log-exception e)
152+
false))))
153+
ignore-errors)))
152154
(mapcat (partial find-symbol-in-file fully-qualified-name ignore-errors referred-syms)))))
153155

154156
(defn- get&read-enclosing-sexps

src/refactor_nrepl/middleware.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@
157157
(delay
158158
(require-and-resolve 'refactor-nrepl.rename-file-or-dir/rename-file-or-dir)))
159159

160-
(defn- rename-file-or-dir-reply [{:keys [transport old-path new-path] :as msg}]
161-
(reply transport msg :touched (@rename-file-or-dir old-path new-path)
160+
(defn- rename-file-or-dir-reply [{:keys [transport old-path new-path ignore-errors] :as msg}]
161+
(reply transport msg :touched (@rename-file-or-dir old-path new-path (= ignore-errors "true"))
162162
:status :done))
163163

164164
(defn- namespace-aliases-reply [{:keys [transport] :as msg}]

src/refactor_nrepl/ns/libspecs.clj

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(ns refactor-nrepl.ns.libspecs
22
(:require [refactor-nrepl.core :as core]
3-
[refactor-nrepl.ns.ns-parser :as ns-parser])
3+
[refactor-nrepl.ns.ns-parser :as ns-parser]
4+
[refactor-nrepl.util :as util])
45
(:import [java.io File]))
56

67
;; The structure here is {path {lang [timestamp value]}}
@@ -46,13 +47,19 @@
4647
4748
{:clj {util com.acme.util str clojure.string
4849
:cljs {gstr goog.str}}}"
49-
[]
50-
{:clj (->> (core/find-in-project (some-fn core/clj-file? core/cljc-file?))
51-
(map (partial get-libspec-from-file-with-caching :clj))
52-
aliases-by-frequencies)
53-
:cljs (->> (core/find-in-project (some-fn core/cljs-file? core/cljc-file?))
54-
(map (partial get-libspec-from-file-with-caching :cljs))
55-
aliases-by-frequencies)})
50+
([]
51+
(namespace-aliases false))
52+
([ignore-errors?]
53+
{:clj (->> (core/find-in-project (util/with-suppressed-errors
54+
(some-fn core/clj-file? core/cljc-file?)
55+
ignore-errors?))
56+
(map (partial get-libspec-from-file-with-caching :clj))
57+
aliases-by-frequencies)
58+
:cljs (->> (core/find-in-project (util/with-suppressed-errors
59+
(some-fn core/cljs-file? core/cljc-file?)
60+
ignore-errors?))
61+
(map (partial get-libspec-from-file-with-caching :cljs))
62+
aliases-by-frequencies)}))
5663

5764
(defn- unwrap-refer
5865
[file {:keys [ns refer]}]
@@ -75,10 +82,16 @@
7582
Example:
7683
{:clj {\"/home/someuser/projects/some.clj\" [\"example.com/foobar\" foobar]}
7784
:cljs}"
78-
[]
79-
{:clj (->> (core/find-in-project (some-fn core/clj-file? core/cljc-file?))
80-
(map (juxt identity (partial get-libspec-from-file-with-caching :clj)))
81-
sym-by-file&fullname)
82-
:cljs (->> (core/find-in-project (some-fn core/cljs-file? core/cljc-file?))
83-
(map (juxt identity (partial get-libspec-from-file-with-caching :cljs)))
84-
sym-by-file&fullname)})
85+
([]
86+
(referred-syms-by-file&fullname false))
87+
([ignore-errors?]
88+
{:clj (->> (core/find-in-project (util/with-suppressed-errors
89+
(some-fn core/clj-file? core/cljc-file?)
90+
ignore-errors?))
91+
(map (juxt identity (partial get-libspec-from-file-with-caching :clj)))
92+
sym-by-file&fullname)
93+
:cljs (->> (core/find-in-project (util/with-suppressed-errors
94+
(some-fn core/cljs-file? core/cljc-file?)
95+
ignore-errors?))
96+
(map (juxt identity (partial get-libspec-from-file-with-caching :cljs)))
97+
sym-by-file&fullname)}))

src/refactor_nrepl/ns/tracker.clj

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[repl :refer [refresh-dirs]]
88
[track :as tracker]]
99
[refactor-nrepl.core :as core]
10+
[refactor-nrepl.util :as util]
1011
[refactor-nrepl.ns.ns-parser :as ns-parser])
1112
(:import [java.io File]))
1213

@@ -36,13 +37,15 @@
3637
;; corner case - use the mranderson-ized refresh-dirs (for supporting this project's test suite):
3738
refresh-dirs))
3839

40+
(def default-file-filter-predicate (every-pred core/source-file?
41+
safe-for-clojure-tools-namespace?))
42+
3943
(defn build-tracker
4044
"Build a tracker for the project.
4145
4246
If file-predicate is provided, use that instead of `core/source-file?`"
4347
([]
44-
(build-tracker #(and (core/source-file? %)
45-
(safe-for-clojure-tools-namespace? %))))
48+
(build-tracker default-file-filter-predicate))
4649
([file-predicate]
4750
(file/add-files (tracker/tracker) (core/find-in-project file-predicate))))
4851

@@ -74,11 +77,16 @@
7477
(boolean (some #(str/starts-with? % file-as-absolute-paths)
7578
refresh-dirs-as-absolute-paths)))))
7679

77-
(defn project-files-in-topo-order []
78-
(let [tracker (build-tracker (every-pred (partial in-refresh-dirs? (user-refresh-dirs))
79-
core/clj-file?))
80-
nses (dep/topo-sort (:clojure.tools.namespace.track/deps tracker))
81-
filemap (:clojure.tools.namespace.file/filemap tracker)
82-
ns2file (zipmap (vals filemap) (keys filemap))]
83-
(->> (map ns2file nses)
84-
(remove nil?))))
80+
(defn project-files-in-topo-order
81+
([]
82+
(project-files-in-topo-order false))
83+
([ignore-errors?]
84+
(let [tracker (build-tracker (util/with-suppressed-errors
85+
(every-pred (partial in-refresh-dirs? (user-refresh-dirs))
86+
core/clj-file?)
87+
ignore-errors?))
88+
nses (dep/topo-sort (:clojure.tools.namespace.track/deps tracker))
89+
filemap (:clojure.tools.namespace.file/filemap tracker)
90+
ns2file (zipmap (vals filemap) (keys filemap))]
91+
(->> (map ns2file nses)
92+
(remove nil?)))))

src/refactor_nrepl/rename_file_or_dir.clj

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,10 @@
194194

195195
(defn- rename-source-file
196196
"Move file from old to new, updating any dependents."
197-
[old-path new-path]
197+
[old-path new-path ignore-errors?]
198198
(let [old-ns (core/ns-from-string (slurp old-path))
199199
new-ns (path->ns new-path)
200-
tracker (tracker/build-tracker)
200+
tracker (tracker/build-tracker (util/with-suppressed-errors tracker/default-file-filter-predicate ignore-errors?))
201201
dependents (tracker/get-dependents tracker old-ns)
202202
new-dependents (atom {})]
203203
(doseq [^File f dependents]
@@ -214,15 +214,17 @@
214214
[path old-parent new-parent]
215215
(str/replace path old-parent new-parent))
216216

217-
(defn- rename-dir [old-path new-path]
217+
(defn- rename-dir [old-path new-path ignore-errors?]
218218
(let [old-path (util/normalize-to-unix-path old-path)
219219
new-path (util/normalize-to-unix-path new-path)
220220
old-path (if (.endsWith old-path "/") old-path (str old-path "/"))
221221
new-path (if (.endsWith new-path "/") new-path (str new-path "/"))]
222222
(flatten (for [^File f (file-seq (File. old-path))
223223
:when (not (fs/directory? f))
224224
:let [path (util/normalize-to-unix-path (.getAbsolutePath f))]]
225-
(-rename-file-or-dir path (merge-paths path old-path new-path))))))
225+
(-rename-file-or-dir path
226+
(merge-paths path old-path new-path)
227+
ignore-errors?)))))
226228

227229
(defn- file-or-symlink-exists? [^String path]
228230
(let [f (File. path)]
@@ -234,12 +236,12 @@
234236
(when (.. target toFile exists)
235237
path)))))))
236238

237-
(defn- -rename-file-or-dir [^String old-path new-path]
239+
(defn- -rename-file-or-dir [^String old-path new-path ignore-errors?]
238240
(let [affected-files (if (fs/directory? old-path)
239-
(rename-dir old-path new-path)
241+
(rename-dir old-path new-path ignore-errors?)
240242
(if ((some-fn core/clj-file? core/cljs-file?)
241243
(File. old-path))
242-
(rename-source-file old-path new-path)
244+
(rename-source-file old-path new-path ignore-errors?)
243245
(rename-file! old-path new-path)))]
244246
(->> affected-files
245247
flatten
@@ -271,7 +273,11 @@
271273
old-path and new-path are expected to be aboslute paths.
272274
273275
Returns a list of all files that were affected."
274-
[old-path new-path]
275-
(assert-friendly old-path new-path)
276-
(binding [*print-length* nil]
277-
(-rename-file-or-dir old-path new-path)))
276+
277+
([old-path new-path]
278+
(rename-file-or-dir old-path new-path false))
279+
280+
([old-path new-path ignore-errors?]
281+
(assert-friendly old-path new-path)
282+
(binding [*print-length* nil]
283+
(-rename-file-or-dir old-path new-path ignore-errors?))))

src/refactor_nrepl/util.clj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,19 @@
7272
(defn maybe-log-exception [^Throwable e]
7373
(when (System/getProperty "refactor-nrepl.internal.log-exceptions")
7474
(.printStackTrace e)))
75+
76+
(defn with-suppressed-errors
77+
"Wraps a predicate `f` in a try-catch, depending on `ignore-errors?`.
78+
79+
Typically used for making ingestion of data (`read`/`require`/`analyze`) more robust
80+
in face of unconfigured or otherwise problematic source paths."
81+
[f ignore-errors?]
82+
(if-not ignore-errors?
83+
f
84+
(fn [x]
85+
(try
86+
(f x)
87+
(catch Exception e
88+
(maybe-log-exception e)
89+
;; return false, because `with-suppressed-errors` is oriented for predicate usage
90+
false)))))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
;; An incorrect alias usage (because `a` is not declared in the `ns` form):
2+
::a/foo
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
;; An incorrect data readers usage (because there's no such data reader declared anywhere):
2+
#totally.made.up/foo []

test-resources/unreadable_file.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(ns unreadable-file

test/refactor_nrepl/extract_definition_test.clj

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
(ns refactor-nrepl.extract-definition-test
22
(:require [clojure.java.io :as io]
33
[clojure.test :refer :all]
4-
[refactor-nrepl.extract-definition :refer :all]))
4+
[refactor-nrepl.extract-definition :as sut]
5+
[refactor-nrepl.unreadable-files :refer [ignore-errors-str]]))
56

67
(defn- -extract-definition
78
[name line col]
8-
(get-in (extract-definition
9+
(get-in (sut/extract-definition
910
{:file (.getAbsolutePath (io/file "test-resources/extract_definition.clj"))
1011
:ns "extract-definition"
1112
:line line
1213
:column col
1314
:name name
15+
:ignore-errors ignore-errors-str
1416
:dir "test-resources"})
1517
[:definition :definition]))
1618

@@ -72,11 +74,12 @@
7274
"(+ 11 17)")))
7375

7476
(deftest returns-meta-data
75-
(let [res (extract-definition
77+
(let [res (sut/extract-definition
7678
{:file (.getAbsolutePath (io/file "test-resources/extract_definition.clj"))
7779
:ns "extract-definition"
7880
:line 44
7981
:column 14
82+
:ignore-errors ignore-errors-str
8083
:name "if-let-bound"
8184
:dir "."})]
8285
(is (= (count (:occurrences res)) 1))

0 commit comments

Comments
 (0)