Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce wrap-ignore-errors #317

Merged
merged 3 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

## Unreleased

#### Changes
* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also document this more prominently somewhere, as there are a couple of other editors using refactor-nrepl.

* Examples: WIP files, Moustache template files, scripts.
* Closes https://github.com/clojure-emacs/refactor-nrepl/issues/291
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a prefix of the parent CL entry.

* Upgrade Orchard
* Worth emphasizing: now the following options are available https://github.com/clojure-emacs/orchard/tree/v0.7.0#configuration-options
* They can make the refactor-nrepl experience simpler / more robust.
* Reliabilty improvement: try using `require` prior to `find-ns`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in reliability

* This increases the chances that a namespace will be found, which in turns makes refactor-nrepl more complete/accurate.
* Replace Cheshire with `clojure.data.json`
* Build ASTs more robustly (by using locks and ruling out certain namespaces like refactor-nrepl itself)

### Bugs fixed
* [#289](https://github.com/clojure-emacs/refactor-nrepl/issues/289): Fix an edge-case with involving keywords that caused find-symbol to crash.
* [#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.
Expand Down
4 changes: 4 additions & 0 deletions eastwood.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(disable-warning
{:linter :unused-ret-vals-in-try
:if-inside-macroexpansion-of #{'clojure.test/deftest
'clojure.core/assert}})
5 changes: 3 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@
with-debug-bindings [[:inner 0]]
merge-meta [[:inner 0]]
try-if-let [[:block 1]]}}}]
:eastwood {:plugins [[jonase/eastwood "0.7.0"]]
:eastwood {:plugins [[jonase/eastwood "0.7.1"]]
:eastwood {;; vendored - shouldn't be tweaked for satisfying linters:
:exclude-namespaces [refactor-nrepl.ns.slam.hound.regrow]
;; :implicit-dependencies would fail spuriously when the CI matrix runs for Clojure < 1.10,
;; because :implicit-dependencies can only work for a certain corner case starting from 1.10.
:exclude-linters [:implicit-dependencies]}}
:exclude-linters [:implicit-dependencies]
:config-files ["eastwood.clj"]}}
:clj-kondo [:test
{:dependencies [[clj-kondo "2021.06.18"]]}]}

Expand Down
2 changes: 1 addition & 1 deletion src/refactor_nrepl/analyzer.clj
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
"OK"))))))

(defn warm-ast-cache []
(doseq [f (tracker/project-files-in-topo-order)]
(doseq [f (tracker/project-files-in-topo-order true)]
(try
(ns-ast (slurp f))
(catch Throwable th
Expand Down
6 changes: 4 additions & 2 deletions src/refactor_nrepl/find/find_macros.clj
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
(defn- find-macro-definitions-in-project
"Finds all macros that are defined in the project."
[ignore-errors?]
(->> (core/find-in-project (some-fn core/cljc-file? core/clj-file?))
(->> (core/find-in-project (util/wrap-ignore-errors (some-fn core/cljc-file? core/clj-file?)
ignore-errors?))
(mapcat #(try
(get-macro-definitions-in-file-with-caching %)
(catch Exception e
Expand Down Expand Up @@ -215,7 +216,8 @@
(when (fully-qualified-name? fully-qualified-name)
(let [all-defs (find-macro-definitions-in-project ignore-errors?)
macro-def (first (filter #(= (:name %) fully-qualified-name) all-defs))
tracker (tracker/build-tracker)
tracker (tracker/build-tracker (util/wrap-ignore-errors tracker/default-predicate
ignore-errors?))
origin-ns (symbol (core/prefix fully-qualified-name))
dependents (tracker/get-dependents tracker origin-ns)]
(some->> macro-def
Expand Down
27 changes: 14 additions & 13 deletions src/refactor_nrepl/find/find_symbol.clj
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,21 @@
fully-qualified-name (if (= namespace "clojure.core")
var-name
(str/join "/" [namespace var-name]))
referred-syms (libspecs/referred-syms-by-file&fullname)]
referred-syms (libspecs/referred-syms-by-file&fullname ignore-errors)]
(->> (core/dirs-on-classpath)
(mapcat (partial core/find-in-dir (every-pred (some-fn core/clj-file? core/cljc-file?)
(fn [f]
(try
(let [n (some-> f
core/read-ns-form
parse/name-from-ns-decl)]
(if-not n
false
(not (self-referential? n))))
(catch Exception e
(util/maybe-log-exception e)
false))))))
(mapcat (partial core/find-in-dir (util/wrap-ignore-errors (every-pred (some-fn core/clj-file? core/cljc-file?)
(fn [f]
(try
(let [n (some-> f
core/read-ns-form
parse/name-from-ns-decl)]
(if-not n
false
(not (self-referential? n))))
(catch Exception e
(util/maybe-log-exception e)
false))))
ignore-errors)))
(mapcat (partial find-symbol-in-file fully-qualified-name ignore-errors referred-syms)))))

(defn- get&read-enclosing-sexps
Expand Down
4 changes: 2 additions & 2 deletions src/refactor_nrepl/middleware.clj
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@
(delay
(require-and-resolve 'refactor-nrepl.rename-file-or-dir/rename-file-or-dir)))

(defn- rename-file-or-dir-reply [{:keys [transport old-path new-path] :as msg}]
(reply transport msg :touched (@rename-file-or-dir old-path new-path)
(defn- rename-file-or-dir-reply [{:keys [transport old-path new-path ignore-errors] :as msg}]
(reply transport msg :touched (@rename-file-or-dir old-path new-path (= ignore-errors "true"))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I should compare against true or "true" here?

...Checking out the codebase, I see a handful of "true" handlings. But there's also a boolean coercion in config.clj which makes me hesitate

Copy link
Member

@expez expez Jul 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, the stuff that's in the msg hasn't yet been coerced to boolean.

I suppose we could've done a better job of consistently doing coercions in this ns (like you're doing now) instead of passing the msg on as is.

:status :done))

(defn- namespace-aliases-reply [{:keys [transport] :as msg}]
Expand Down
41 changes: 26 additions & 15 deletions src/refactor_nrepl/ns/libspecs.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(ns refactor-nrepl.ns.libspecs
(:require [refactor-nrepl.core :as core]
[refactor-nrepl.ns.ns-parser :as ns-parser])
[refactor-nrepl.ns.ns-parser :as ns-parser]
[refactor-nrepl.util :as util])
(:import [java.io File]))

;; The structure here is {path {lang [timestamp value]}}
Expand Down Expand Up @@ -46,13 +47,18 @@

{:clj {util com.acme.util str clojure.string
:cljs {gstr goog.str}}}"
[]
{:clj (->> (core/find-in-project (some-fn core/clj-file? core/cljc-file?))
(map (partial get-libspec-from-file-with-caching :clj))
aliases-by-frequencies)
:cljs (->> (core/find-in-project (some-fn core/cljs-file? core/cljc-file?))
(map (partial get-libspec-from-file-with-caching :cljs))
aliases-by-frequencies)})
([]
(namespace-aliases false))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra line here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will rm it

(fwiw it was there on purpose... I don't like "shim" arities to clutter the "impl" arity, so that I can read the impl better)

([ignore-errors?]
{:clj (->> (core/find-in-project (util/wrap-ignore-errors (some-fn core/clj-file? core/cljc-file?)
ignore-errors?))
(map (partial get-libspec-from-file-with-caching :clj))
aliases-by-frequencies)
:cljs (->> (core/find-in-project (util/wrap-ignore-errors (some-fn core/cljs-file? core/cljc-file?)
ignore-errors?))
(map (partial get-libspec-from-file-with-caching :cljs))
aliases-by-frequencies)}))

(defn- unwrap-refer
[file {:keys [ns refer]}]
Expand All @@ -75,10 +81,15 @@
Example:
{:clj {\"/home/someuser/projects/some.clj\" [\"example.com/foobar\" foobar]}
:cljs}"
[]
{:clj (->> (core/find-in-project (some-fn core/clj-file? core/cljc-file?))
(map (juxt identity (partial get-libspec-from-file-with-caching :clj)))
sym-by-file&fullname)
:cljs (->> (core/find-in-project (some-fn core/cljs-file? core/cljc-file?))
(map (juxt identity (partial get-libspec-from-file-with-caching :cljs)))
sym-by-file&fullname)})
([]
(referred-syms-by-file&fullname false))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra line here

([ignore-errors?]
{:clj (->> (core/find-in-project (util/wrap-ignore-errors (some-fn core/clj-file? core/cljc-file?)
ignore-errors?))
(map (juxt identity (partial get-libspec-from-file-with-caching :clj)))
sym-by-file&fullname)
:cljs (->> (core/find-in-project (util/wrap-ignore-errors (some-fn core/cljs-file? core/cljc-file?)
ignore-errors?))
(map (juxt identity (partial get-libspec-from-file-with-caching :cljs)))
sym-by-file&fullname)}))
29 changes: 19 additions & 10 deletions src/refactor_nrepl/ns/tracker.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[repl :refer [refresh-dirs]]
[track :as tracker]]
[refactor-nrepl.core :as core]
[refactor-nrepl.util :as util]
[refactor-nrepl.ns.ns-parser :as ns-parser])
(:import [java.io File]))

Expand Down Expand Up @@ -36,13 +37,16 @@
;; corner case - use the mranderson-ized refresh-dirs (for supporting this project's test suite):
refresh-dirs))

(def default-predicate (every-pred core/source-file?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default-file-predicate or maybe even something like filter-file-predicate? The latter gives some indication that we're using this predicate to filter the files to be processed.

safe-for-clojure-tools-namespace?))

(defn build-tracker
"Build a tracker for the project.

If file-predicate is provided, use that instead of `core/source-file?`"
([]
(build-tracker #(and (core/source-file? %)
(safe-for-clojure-tools-namespace? %))))
(build-tracker default-predicate))

([file-predicate]
(file/add-files (tracker/tracker) (core/find-in-project file-predicate))))

Expand Down Expand Up @@ -74,11 +78,16 @@
(boolean (some #(str/starts-with? % file-as-absolute-paths)
refresh-dirs-as-absolute-paths)))))

(defn project-files-in-topo-order []
(let [tracker (build-tracker (every-pred (partial in-refresh-dirs? (user-refresh-dirs))
core/clj-file?))
nses (dep/topo-sort (:clojure.tools.namespace.track/deps tracker))
filemap (:clojure.tools.namespace.file/filemap tracker)
ns2file (zipmap (vals filemap) (keys filemap))]
(->> (map ns2file nses)
(remove nil?))))
(defn project-files-in-topo-order
([]
(project-files-in-topo-order false))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra blank line here

([ignore-errors?]
(let [tracker (build-tracker (util/wrap-ignore-errors (every-pred (partial in-refresh-dirs? (user-refresh-dirs))
core/clj-file?)
ignore-errors?))
nses (dep/topo-sort (:clojure.tools.namespace.track/deps tracker))
filemap (:clojure.tools.namespace.file/filemap tracker)
ns2file (zipmap (vals filemap) (keys filemap))]
(->> (map ns2file nses)
(remove nil?)))))
29 changes: 18 additions & 11 deletions src/refactor_nrepl/rename_file_or_dir.clj
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,11 @@

(defn- rename-source-file
"Move file from old to new, updating any dependents."
[old-path new-path]
[old-path new-path ignore-errors?]
(let [old-ns (core/ns-from-string (slurp old-path))
new-ns (path->ns new-path)
tracker (tracker/build-tracker)
tracker (tracker/build-tracker (util/wrap-ignore-errors tracker/default-predicate
ignore-errors?))
dependents (tracker/get-dependents tracker old-ns)
new-dependents (atom {})]
(doseq [^File f dependents]
Expand All @@ -214,15 +215,17 @@
[path old-parent new-parent]
(str/replace path old-parent new-parent))

(defn- rename-dir [old-path new-path]
(defn- rename-dir [old-path new-path ignore-errors?]
(let [old-path (util/normalize-to-unix-path old-path)
new-path (util/normalize-to-unix-path new-path)
old-path (if (.endsWith old-path "/") old-path (str old-path "/"))
new-path (if (.endsWith new-path "/") new-path (str new-path "/"))]
(flatten (for [^File f (file-seq (File. old-path))
:when (not (fs/directory? f))
:let [path (util/normalize-to-unix-path (.getAbsolutePath f))]]
(-rename-file-or-dir path (merge-paths path old-path new-path))))))
(-rename-file-or-dir path
(merge-paths path old-path new-path)
ignore-errors?)))))

(defn- file-or-symlink-exists? [^String path]
(let [f (File. path)]
Expand All @@ -234,12 +237,12 @@
(when (.. target toFile exists)
path)))))))

(defn- -rename-file-or-dir [^String old-path new-path]
(defn- -rename-file-or-dir [^String old-path new-path ignore-errors?]
(let [affected-files (if (fs/directory? old-path)
(rename-dir old-path new-path)
(rename-dir old-path new-path ignore-errors?)
(if ((some-fn core/clj-file? core/cljs-file?)
(File. old-path))
(rename-source-file old-path new-path)
(rename-source-file old-path new-path ignore-errors?)
(rename-file! old-path new-path)))]
(->> affected-files
flatten
Expand Down Expand Up @@ -271,7 +274,11 @@
old-path and new-path are expected to be aboslute paths.

Returns a list of all files that were affected."
[old-path new-path]
(assert-friendly old-path new-path)
(binding [*print-length* nil]
(-rename-file-or-dir old-path new-path)))

([old-path new-path]
(rename-file-or-dir old-path new-path false))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra line here

([old-path new-path ignore-errors?]
(assert-friendly old-path new-path)
(binding [*print-length* nil]
(-rename-file-or-dir old-path new-path ignore-errors?))))
11 changes: 11 additions & 0 deletions src/refactor_nrepl/util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,14 @@
(defn maybe-log-exception [^Throwable e]
(when (System/getProperty "refactor-nrepl.internal.log-exceptions")
(.printStackTrace e)))

(defn wrap-ignore-errors [f ignore-errors?]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A docstring is in order for everything part of the public API.

(if-not ignore-errors?
f
(fn [x]
(try
(f x)
(catch Exception e
(maybe-log-exception e)
;; return false, because `wrap-ignore-errors` is oriented for predicate usage:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the trailing : in this comment makes it seem like something is missing

Copy link
Member Author

@vemv vemv Jul 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the trailing : in this comment makes it seem like something is missing

I add the colon to make it clear that the comment refers to the next LOC. Else there can be an ambiguity (does it refer to the previous one, to the defn in general, etc?)

I guess it failed at that 😇

false)))))
2 changes: 2 additions & 0 deletions test-resources/readable_file_incorrect_aliases.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
;; An incorrect alias usage (because `a` is not declared in the `ns` form):
::a/foo
2 changes: 2 additions & 0 deletions test-resources/readable_file_incorrect_data_readers.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
;; An incorrect data readers usage (because there's no such data reader declared anywhere):
#totally.made.up/foo []
1 change: 1 addition & 0 deletions test-resources/unreadable_file.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(ns unreadable-file
9 changes: 6 additions & 3 deletions test/refactor_nrepl/extract_definition_test.clj
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
(ns refactor-nrepl.extract-definition-test
(:require [clojure.java.io :as io]
[clojure.test :refer :all]
[refactor-nrepl.extract-definition :refer :all]))
[refactor-nrepl.extract-definition :as sut]
[refactor-nrepl.unreadable-files :refer [ignore-errors-str]]))

(defn- -extract-definition
[name line col]
(get-in (extract-definition
(get-in (sut/extract-definition
{:file (.getAbsolutePath (io/file "test-resources/extract_definition.clj"))
:ns "extract-definition"
:line line
:column col
:name name
:ignore-errors ignore-errors-str
:dir "test-resources"})
[:definition :definition]))

Expand Down Expand Up @@ -72,11 +74,12 @@
"(+ 11 17)")))

(deftest returns-meta-data
(let [res (extract-definition
(let [res (sut/extract-definition
{:file (.getAbsolutePath (io/file "test-resources/extract_definition.clj"))
:ns "extract-definition"
:line 44
:column 14
:ignore-errors ignore-errors-str
:name "if-let-bound"
:dir "."})]
(is (= (count (:occurrences res)) 1))
Expand Down
19 changes: 10 additions & 9 deletions test/refactor_nrepl/find/find_macros_test.clj
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
(ns refactor-nrepl.find.find-macros-test
(:require [clojure.test :refer :all]
[refactor-nrepl.find.find-macros :refer [find-macro]]))
[refactor-nrepl.find.find-macros :refer [find-macro]]
[refactor-nrepl.unreadable-files :refer [ignore-errors?]]))

(defn- found? [regexp occurrences]
(first (filter #(re-find regexp (:match %)) occurrences)))

(deftest find-macro-test
(let [occurrences (find-macro "com.example.macro-def/my-macro" false)
(let [occurrences (find-macro "com.example.macro-def/my-macro" ignore-errors?)
{:keys [line-beg col-beg ^String file match]}
(first (filter #(.contains ^String (:match %) "defmacro") occurrences))]
(testing "finds the macro definition"
Expand All @@ -29,27 +30,27 @@
(is (.endsWith file "macro_def.clj")))))

(deftest find-regular-symbol-test
(is (nil? (find-macro "sym" false))))
(is (nil? (find-macro "sym" ignore-errors?))))

(deftest find-fully-qualified-random-name
(is (nil? (find-macro "asdf" false))))
(is (nil? (find-macro "asdf" ignore-errors?))))

(deftest find-fully-qualified-fn
(is (nil? (find-macro "refactor-nrepl.find.find-macros/find-macro" false))))
(is (nil? (find-macro "refactor-nrepl.find.find-macros/find-macro" ignore-errors?))))

(deftest finds-macro-defined-in-cljc-file
(is (found? #"defmacro cljc-macro"
(find-macro "com.example.macro-def-cljc/cljc-macro" false))))
(find-macro "com.example.macro-def-cljc/cljc-macro" ignore-errors?))))

(deftest finds-macro-defined-in-cljc-file-and-used-in-clj-file
(is (found? #"(com.example.macro-def-cljc/cljc-macro :fully-qualified)"
(find-macro "com.example.macro-def-cljc/cljc-macro" false))))
(find-macro "com.example.macro-def-cljc/cljc-macro" ignore-errors?))))

(deftest macro-definitions-are-cached
(find-macro "com.example.macro-def/my-macro" false)
(find-macro "com.example.macro-def/my-macro" ignore-errors?)
(with-redefs [refactor-nrepl.find.find-macros/put-cached-macro-definitions
(fn [& _] (throw (ex-info "Cache miss!" {})))]
(is (found? #"defmacro my-macro" (find-macro "com.example.macro-def/my-macro" false))))
(is (found? #"defmacro my-macro" (find-macro "com.example.macro-def/my-macro" ignore-errors?))))
(reset! @#'refactor-nrepl.find.find-macros/macro-defs-cache {})
(with-redefs [refactor-nrepl.find.find-macros/put-cached-macro-definitions
(fn [& _] (throw (Exception. "Expected!")))]
Expand Down