|
8 | 8 | [clojure.tools.analyzer.jvm.utils :as ajutils]
|
9 | 9 | [clojure.tools.namespace.parse :refer [read-ns-decl]]
|
10 | 10 | [clojure.walk :as walk]
|
11 |
| - [refactor-nrepl |
12 |
| - [config :as config]] |
| 11 | + [refactor-nrepl.config :as config] |
13 | 12 | [refactor-nrepl.ns.tracker :as tracker]
|
14 |
| - [clojure.string :as str]) |
| 13 | + [clojure.string :as str] |
| 14 | + [cljs.analyzer :as cljs-ana] |
| 15 | + [cljs.util :as cljs-util] |
| 16 | + [cljs.compiler :as cljs-comp] |
| 17 | + [cljs.env :as cljs-env] |
| 18 | + [cider.nrepl.middleware.util.cljs :as cljs]) |
15 | 19 | (:import java.io.PushbackReader
|
16 | 20 | java.util.regex.Pattern))
|
17 | 21 |
|
18 | 22 | ;;; The structure here is {ns {content-hash ast}}
|
19 | 23 | (def ^:private ast-cache (atom {}))
|
20 | 24 |
|
21 | 25 | (defn get-alias [as v]
|
22 |
| - (cond as (first v) |
| 26 | + (cond as (first v) |
23 | 27 | (= (first v) :as) (get-alias true (rest v))
|
24 |
| - :else (get-alias nil (rest v)))) |
| 28 | + :else (get-alias nil (rest v)))) |
25 | 29 |
|
26 | 30 | (defn parse-ns
|
27 | 31 | "Returns tuples with the ns as the first element and
|
|
80 | 84 | reader/*data-readers* *data-readers*]
|
81 | 85 | (assoc-in (aj/analyze-ns ns (aj/empty-env) opts) [0 :alias-info] aliases)))))
|
82 | 86 |
|
| 87 | +(comment |
| 88 | +;; integration test: find-used-locals |
| 89 | +;; myast is the parsed ast of com.example.five and com.example.five-cljs respectively |
| 90 | + |
| 91 | +;; cljs |
| 92 | +(->> (nth myast 3) :init :methods first :body :ret :bindings first :children) |
| 93 | +;; returns nil, but ... :init :form) returns (trim p), strangely :init has the same children as in the clj case |
| 94 | + |
| 95 | +;; clj |
| 96 | +(->> (nth myast 3) :init :expr :methods first :body :bindings first :children) |
| 97 | +;; returns [:init] .. :init :form) returns (trim p) |
| 98 | + |
| 99 | + ) |
| 100 | + |
| 101 | +(defn- repair-binding-children |
| 102 | + "Repairs cljs AST by adding `:children` entries to `:binding` AST nodes, see above comment tag." |
| 103 | + [] |
| 104 | + (fn [env ast opts] |
| 105 | + (if (= :let (:op ast)) |
| 106 | + (update |
| 107 | + ast |
| 108 | + :bindings |
| 109 | + (fn [bindings] |
| 110 | + (mapv #(assoc % :children [:init]) bindings))) |
| 111 | + ast))) |
| 112 | + |
| 113 | +(defn cljs-analyze-ns |
| 114 | + "Returns a sequence of abstract syntax trees for each form in |
| 115 | + the namespace." |
| 116 | + [ns] |
| 117 | + (cljs-env/ensure |
| 118 | + (let [f (cljs-util/ns->relpath ns) |
| 119 | + res (if (re-find #"^file://" f) (java.net.URL. f) (io/resource f))] |
| 120 | + (assert res (str "Can't find " f " in classpath")) |
| 121 | + (binding [cljs-ana/*cljs-ns* 'cljs.user |
| 122 | + cljs-ana/*cljs-file* (.getPath ^java.net.URL res) |
| 123 | + cljs-ana/*passes* [cljs-ana/infer-type cljs-ana/check-invoke-arg-types cljs-ana/ns-side-effects (repair-binding-children)]] |
| 124 | + (with-open [r (io/reader res)] |
| 125 | + (let [env (cljs-ana/empty-env) |
| 126 | + pbr (clojure.lang.LineNumberingPushbackReader. r) |
| 127 | + eof (Object.)] |
| 128 | + (loop [asts [] |
| 129 | + r (read pbr false eof false)] |
| 130 | + (let [env (assoc env :ns (cljs-ana/get-namespace cljs-ana/*cljs-ns*))] |
| 131 | + (if-not (identical? eof r) |
| 132 | + (recur (conj asts (cljs-ana/analyze env r)) (read pbr false eof false)) |
| 133 | + asts))))))))) |
| 134 | + |
| 135 | +(defn cljs-analyze-form [form] |
| 136 | + (cljs-env/ensure |
| 137 | + (binding [cljs-ana/*cljs-ns* 'cljs.user] |
| 138 | + (cljs-ana/analyze (cljs-ana/empty-env) form)))) |
| 139 | + |
| 140 | +(defn build-cljs-ast |
| 141 | + [file-content] |
| 142 | + (let [[ns aliases] (parse-ns file-content)] |
| 143 | + (assoc-in (cljs-analyze-ns ns) [0 :alias-info] aliases))) |
| 144 | + |
83 | 145 | (defn- cachable-ast [file-content]
|
84 | 146 | (let [[ns aliases] (parse-ns file-content)]
|
85 | 147 | (when ns
|
|
133 | 195 | ;; The node for ::an-ns-alias/foo, when it appeared as a toplevel form,
|
134 | 196 | ;; had nil as position info
|
135 | 197 | (and line end-line column end-column
|
136 |
| - (and (>= loc-line line) |
137 |
| - (<= loc-line end-line) |
138 |
| - (>= loc-column column) |
139 |
| - (<= loc-column end-column))))) |
| 198 | + (<= line loc-line end-line) |
| 199 | + (<= column loc-column end-column)))) |
140 | 200 |
|
141 |
| -(defn- normalize-anon-fn-params |
| 201 | +(defn normalize-anon-fn-params |
142 | 202 | "replaces anon fn params in a read form"
|
143 | 203 | [form]
|
144 | 204 | (walk/postwalk
|
145 |
| - (fn [token] (if (re-matches #"p\d+__\d+#" (str token)) 'p token)) form)) |
| 205 | + (fn [token] (cond (re-matches #"p\d+__\d+#" (str token)) 'p |
| 206 | + (instance? java.util.regex.Pattern token) (str token) |
| 207 | + :default token)) form)) |
146 | 208 |
|
147 | 209 | (defn- read-when-sexp [form]
|
148 | 210 | (let [f-string (str form)]
|
|
155 | 217 | (binding [*read-eval* false]
|
156 | 218 | (let [sexp-sans-comments-and-meta (normalize-anon-fn-params (read-string sexp))
|
157 | 219 | pattern (re-pattern (Pattern/quote (str sexp-sans-comments-and-meta)))]
|
| 220 | + ;; (println "raw-forms" (:raw-forms node)) |
| 221 | + ;; (println "form " (-> (:form node) |
| 222 | + ;; read-when-sexp |
| 223 | + ;; normalize-anon-fn-params)) |
158 | 224 | (if-let [forms (:raw-forms node)]
|
159 | 225 | (some #(re-find pattern %)
|
160 | 226 | (map (comp str normalize-anon-fn-params read-when-sexp) forms))
|
161 | 227 | (= sexp-sans-comments-and-meta (-> (:form node)
|
162 | 228 | read-when-sexp
|
163 | 229 | normalize-anon-fn-params))))))
|
164 | 230 |
|
| 231 | +(defn node-for-sexp-cljs? |
| 232 | + "Is NODE the ast node for SEXP for cljs? |
| 233 | +
|
| 234 | + As `:raw-forms` (stages of macro expansion, including the original form) is not available in cljs AST it does the comparison the other way around. Eg parses `sexp` with the cljs parser and compares that with the `:form` of the AST node." |
| 235 | + [sexp node] |
| 236 | + (binding [*read-eval* false] |
| 237 | + (let [sexp-sans-comments-and-meta-form (:form (cljs-analyze-form (normalize-anon-fn-params (read-string sexp)))) |
| 238 | + node-form (-> (:form node) |
| 239 | + read-when-sexp |
| 240 | + normalize-anon-fn-params)] |
| 241 | + ;; (println "sexp-sans-comments-and-meta" sexp-sans-comments-and-meta-form "types" (map type sexp-sans-comments-and-meta-form)) |
| 242 | + ;; (println "form " node-form "types" (map type node-form)) |
| 243 | + (= sexp-sans-comments-and-meta-form node-form)))) |
| 244 | + |
165 | 245 | (defn top-level-form-index
|
166 | 246 | [line column ns-ast]
|
167 | 247 | (->> ns-ast
|
|
170 | 250 | (some (partial node-at-loc? line column)))))
|
171 | 251 | (filter #(second %))
|
172 | 252 | ffirst))
|
| 253 | + |
| 254 | +(defn node-at-loc-cljs? |
| 255 | + "Works around the fact that cljs AST nodes don't have end-line and end-column info in them. This cheat only works for top level forms because after a `clojure.tools.analyzer.ast/nodes` call we can't expect the nodes in the right order." |
| 256 | + [^long loc-line ^long loc-column node next-node] |
| 257 | + (let [{:keys [^long line ^long column]} (:env node) |
| 258 | + env-next-node (:env next-node) |
| 259 | + ^long end-column (:column env-next-node) |
| 260 | + ^long end-line (:line env-next-node)] |
| 261 | + ;; The node for ::an-ns-alias/foo, when it appeared as a toplevel form, |
| 262 | + ;; had nil as position info |
| 263 | + (and line end-line column end-column |
| 264 | + (or (< line loc-line end-line) |
| 265 | + (and (or (= line loc-line) |
| 266 | + (= end-line loc-line)) |
| 267 | + (<= column loc-column end-column)))))) |
| 268 | + |
| 269 | +(defn top-level-form-index-cljs |
| 270 | + [line column ns-ast] |
| 271 | + (loop [[top-level-ast & top-level-asts-rest] ns-ast |
| 272 | + index 0] |
| 273 | + (if (or (node-at-loc-cljs? line column top-level-ast (first top-level-asts-rest)) |
| 274 | + (not (first top-level-asts-rest))) |
| 275 | + index |
| 276 | + (recur top-level-asts-rest (inc index))))) |
0 commit comments