diff --git a/CHANGELOG.md b/CHANGELOG.md index e800650..cdf3844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## Unreleased +* [#396](https://github.com/clojure-emacs/refactor-nrepl/pull/396) Handle the analyzing and parsing of Clojure code from .cljc files. * Upgrade various dependencies: [#393](https://github.com/clojure-emacs/refactor-nrepl/pull/393), [#394](https://github.com/clojure-emacs/refactor-nrepl/pull/394) & [#395](https://github.com/clojure-emacs/refactor-nrepl/pull/395). * Does not impact users, since we use [mranderson](https://github.com/benedekfazekas/mranderson). -* The `rename-file-or-dir` is now also able to rename .cljs files that depended on .clj files via :require-macros [#394](https://github.com/clojure-emacs/refactor-nrepl/pull/394). +* The `rename-file-or-dir` op is now also able to rename .cljs files that depended on .clj files via :require-macros [#394](https://github.com/clojure-emacs/refactor-nrepl/pull/394). * `ns-tracker` no longer ignores cljs files with string requires [#394](https://github.com/clojure-emacs/refactor-nrepl/pull/394). * note that `ns-tracker` is normally used for clj files only. diff --git a/src/refactor_nrepl/analyzer.clj b/src/refactor_nrepl/analyzer.clj index f7b892d..d4de4fd 100644 --- a/src/refactor_nrepl/analyzer.clj +++ b/src/refactor_nrepl/analyzer.clj @@ -148,7 +148,7 @@ (defn warm-ast-cache [] (doseq [f (tracker/project-files-in-topo-order true)] (try - (ns-ast (slurp f)) + (ns-ast (core/file-forms f #{:clj})) (catch Throwable th (when (System/getProperty "refactor-nrepl.internal.log-exceptions") (-> th .printStackTrace)) diff --git a/src/refactor_nrepl/core.clj b/src/refactor_nrepl/core.clj index 60e75c9..c131493 100644 --- a/src/refactor_nrepl/core.clj +++ b/src/refactor_nrepl/core.clj @@ -3,6 +3,7 @@ [clojure.java.io :as io] [clojure.string :as str] [clojure.tools.namespace.parse :as parse] + [clojure.tools.reader :as reader] [clojure.tools.reader.reader-types :as readers] [orchard.java.classpath :as cp] [orchard.misc :as misc] @@ -10,6 +11,7 @@ [refactor-nrepl.s-expressions :as sexp] [refactor-nrepl.util :as util :refer [normalize-to-unix-path]]) (:import + (clojure.lang LineNumberingPushbackReader) (java.io File FileNotFoundException FileReader PushbackReader StringReader))) ;; Require our `fs` customizations before `fs` is loaded: @@ -182,8 +184,8 @@ (defn cljc-file? [path-or-file] (let [path (.getPath (io/file path-or-file))] - (and (cljc-extension? path) - (read-ns-form path)))) + (boolean (and (cljc-extension? path) + (read-ns-form path))))) (defn cljs-extension? [^String path] (.endsWith path ".cljs")) @@ -191,8 +193,8 @@ (defn cljs-file? [path-or-file] (let [path (.getPath (io/file path-or-file))] - (and (cljs-extension? path) - (read-ns-form path)))) + (boolean (and (cljs-extension? path) + (read-ns-form path))))) (defn clj-extension? [^String path] (.endsWith path ".clj")) @@ -200,9 +202,14 @@ (defn clj-file? [path-or-file] (let [path (.getPath (io/file path-or-file))] - (and (not (util/data-file? path-or-file)) - (clj-extension? path) - (read-ns-form path)))) + (boolean (and (not (util/data-file? path-or-file)) + (clj-extension? path) + (read-ns-form path))))) + +(defn clj-or-cljc-file? + [path-or-file] + (or (clj-file? path-or-file) + (cljc-file? path-or-file))) (defn source-file? "True for clj, cljs or cljc files. @@ -409,6 +416,28 @@ ([no-error path] (when-not (cljs-file? path) (some-> path read-ns-form-with-meta parse/name-from-ns-decl (safe-find-ns no-error))))) +(defn file-forms + "For a given `file`, get all the forms from it. + + If `file` is .cljc, `features` (a set) will be used. + + Please prefer this helper over `#'slurp`, + so that reader conditionals are properly handled." + [file features] + (let [reader (LineNumberingPushbackReader. (StringReader. (slurp file))) + reader-opts {:read-cond :allow + :eof ::eof + :features (case (file->dialect file) + :clj #{:clj} + :cljc features + :cljs #{:cljs})}] + (loop [forms [] + form (reader/read reader-opts reader)] + (if (not= form ::eof) + (recur (conj forms form) + (reader/read reader-opts reader)) + (str/join " " forms))))) + (defn file-content-sans-ns "Read the content of file after the ns. @@ -420,10 +449,10 @@ ([file-content dialect] ;; NOTE: It's tempting to trim this result but ;; find-macros relies on this not being trimmed - (let [rdr-opts {:read-cond :allow :features #{dialect}} - rdr (PushbackReader. (StringReader. file-content))] - (read rdr-opts rdr) - (slurp rdr)))) + (let [reader-opts {:read-cond :allow :features #{dialect}} + reader (PushbackReader. (StringReader. file-content))] + (read reader-opts reader) + (slurp reader)))) (defn ns-form-from-string ([file-content] @@ -433,9 +462,9 @@ (catch Exception _e (throw (IllegalArgumentException. "Malformed ns form!"))))) ([dialect file-content] - (let [rdr-opts {:read-cond :allow :features #{dialect}}] + (let [reader-opts {:read-cond :allow :features #{dialect}}] (try - (with-meta (parse/read-ns-decl (PushbackReader. (StringReader. file-content)) rdr-opts) + (with-meta (parse/read-ns-decl (PushbackReader. (StringReader. file-content)) reader-opts) (extract-ns-meta file-content)) (catch Exception _e (throw (IllegalArgumentException. "Malformed ns form!"))))))) diff --git a/src/refactor_nrepl/ns/tracker.clj b/src/refactor_nrepl/ns/tracker.clj index c03b985..dc076cd 100644 --- a/src/refactor_nrepl/ns/tracker.clj +++ b/src/refactor_nrepl/ns/tracker.clj @@ -82,7 +82,7 @@ (let [refresh-dirs (user-refresh-dirs) tracker (build-tracker (util/with-suppressed-errors (every-pred (partial in-refresh-dirs? refresh-dirs (absolutize-dirs refresh-dirs)) - core/clj-file?) + core/clj-or-cljc-file?) ignore-errors?)) nses (dep/topo-sort (:clojure.tools.namespace.track/deps tracker)) filemap (:clojure.tools.namespace.file/filemap tracker) diff --git a/test/refactor_nrepl/core_test.clj b/test/refactor_nrepl/core_test.clj index 74187b9..f829820 100644 --- a/test/refactor_nrepl/core_test.clj +++ b/test/refactor_nrepl/core_test.clj @@ -87,3 +87,65 @@ :foo true} (:top-level-meta ns-meta))) (is (= {:c 3, :d 4} (:attr-map ns-meta)))))) + +(deftest clj-or-cljc-file-test + (let [clj "testproject/src/com/move/dependent_ns1.clj" + cljs "testproject/src/com/move/dependent_ns1_cljs.cljs" + cljc "testproject/src/com/move/cljc_test_file.cljc"] + (is (= true + (sut/clj-or-cljc-file? cljc))) + (is (= true + (sut/clj-or-cljc-file? clj))) + (is (= false + (sut/clj-or-cljc-file? cljs))) + + (is (= false + (sut/clj-file? cljc))) + (is (= true + (sut/clj-file? clj))) + (is (= false + (sut/clj-file? cljs))) + + (is (= true + (sut/cljc-file? cljc))) + (is (= false + (sut/cljc-file? clj))) + (is (= false + (sut/cljc-file? cljs))) + + (is (= false + (sut/cljs-file? cljc))) + (is (= false + (sut/cljs-file? clj))) + (is (= true + (sut/cljs-file? cljs))))) + +(deftest file-forms-test + (is (= "(ns com.move.subdir.dependent-ns-3-cljs (:require com.move.ns-to-be-moved-cljs) (:require-macros [com.move.ns-to-be-moved :refer [macro-to-be-moved]]))" + (sut/file-forms "testproject/src/com/move/subdir/dependent_ns_3_cljs.cljs" + #{:clj})) + "cljs file parsing") + + (is (= "(ns com.move.subdir.dependent-ns-3 (:require [com.move.ns-to-be-moved :refer [fn-to-be-moved]]))" + (sut/file-forms "testproject/src/com/move/subdir/dependent_ns_3.clj" + #{:clj})) + "clj file parsing") + + (let [clj-content + "(ns com.move.cljc-test-file (:require [clj-namespace-from.cljc-file :as foo])) (declare something-or-other)" + + cljs-content + "(ns com.move.cljc-test-file (:require [cljs-namespace-from.cljc-file :as bar :include-macros true])) (declare something-or-other)"] + + (is (not= clj-content cljs-content) + "Sanity check") + + (is (= clj-content + (sut/file-forms "testproject/src/com/move/cljc_test_file.cljc" + #{:clj})) + "cljc file parsing, `:clj` choice") + + (is (= cljs-content + (sut/file-forms "testproject/src/com/move/cljc_test_file.cljc" + #{:cljs})) + "cljc file parsing, `:cljs` choice"))) diff --git a/test/refactor_nrepl/ns/tracker_test.clj b/test/refactor_nrepl/ns/tracker_test.clj index ee897ac..9a870ce 100644 --- a/test/refactor_nrepl/ns/tracker_test.clj +++ b/test/refactor_nrepl/ns/tracker_test.clj @@ -1,7 +1,9 @@ (ns refactor-nrepl.ns.tracker-test (:require - [clojure.test :refer [are deftest is]] - [refactor-nrepl.ns.tracker :as sut])) + [clojure.test :refer [are deftest is testing]] + [refactor-nrepl.core :as core] + [refactor-nrepl.ns.tracker :as sut] + [refactor-nrepl.util :as util])) (deftest in-refresh-dirs? (are [refresh-dirs file-ns expected] (= expected @@ -23,3 +25,15 @@ (is (seq (sut/project-files-in-topo-order false)) "Does not throw exceptions even when specifying to not ignore errors, i.e. it doesn't have bugs")) + +(deftest build-tracker-test + (let [tracker (sut/build-tracker (util/with-suppressed-errors core/clj-or-cljc-file? true)) + target-cljc-namespace 'com.move.cljc-test-file + found-namespace (-> tracker + vals + first + :dependencies + (get target-cljc-namespace) + first)] + (testing "that tracker is picking up .cljc file" + (is (= 'clj-namespace-from.cljc-file found-namespace))))) diff --git a/test/refactor_nrepl/rename_file_or_dir_test.clj b/test/refactor_nrepl/rename_file_or_dir_test.clj index e9b5583..f417a27 100644 --- a/test/refactor_nrepl/rename_file_or_dir_test.clj +++ b/test/refactor_nrepl/rename_file_or_dir_test.clj @@ -148,7 +148,8 @@ (deftest calls-rename-file!-on-the-right-files-when-moving-dirs (let [files (atom []) - original-files ["testproject/src/com/move/dependent_ns1.clj" + original-files ["testproject/src/com/move/cljc_test_file.cljc" + "testproject/src/com/move/dependent_ns1.clj" "testproject/src/com/move/dependent_ns1_cljs.cljs" "testproject/src/com/move/dependent_ns2.clj" "testproject/src/com/move/dependent_ns2_cljs.cljs" @@ -315,7 +316,7 @@ and the string requires remain there as-is" refactor-nrepl.rename-file-or-dir/update-ns! (fn [_path _old-ns]) refactor-nrepl.rename-file-or-dir/update-dependents! (fn [_deps])] (sut/rename-file-or-dir from-dir-path to-dir-path ignore-errors?) - (is (= (count @files) 9)) + (is (= (count @files) 10)) (doseq [[^String old ^String new] @files] (is (.contains old "/move/")) (is (.contains new "/moved/")))))) diff --git a/testproject/src/com/move/cljc_test_file.cljc b/testproject/src/com/move/cljc_test_file.cljc new file mode 100644 index 0000000..eff7589 --- /dev/null +++ b/testproject/src/com/move/cljc_test_file.cljc @@ -0,0 +1,5 @@ +(ns com.move.cljc-test-file + (:require #?(:clj [clj-namespace-from.cljc-file :as foo] + :cljs [cljs-namespace-from.cljc-file :as bar :include-macros true]))) + +(declare something-or-other)