Skip to content

Commit df95932

Browse files
committed
x
1 parent 470b993 commit df95932

File tree

3 files changed

+279
-142
lines changed

3 files changed

+279
-142
lines changed

CHANGELOG.md

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

33
## master
44

5+
* [#30](https://github.com/clojure-emacs/clj-suitable/issues/30): don't run side-effects for pure-clojurescript (non-interop) `->` chains.
6+
57
## 0.4.1 (2021-10-02)
68

79
* [#22](https://github.com/clojure-emacs/clj-suitable/issues/22): Gracefully handle string requires.

src/main/suitable/js_completions.clj

Lines changed: 73 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
(ns suitable.js-completions
22
(:refer-clojure :exclude [replace])
3-
(:require [clojure.pprint :refer [cl-format]]
4-
[clojure.string :refer [replace split starts-with?]]
5-
[clojure.zip :as zip]
6-
[suitable.ast :refer [tree-zipper]]))
3+
(:require
4+
[clojure.pprint :refer [cl-format]]
5+
[clojure.string :refer [replace split starts-with?]]
6+
[clojure.zip :as zip]
7+
[suitable.ast :refer [tree-zipper]]))
78

89
(def debug? false)
910

@@ -45,16 +46,22 @@
4546
[form]
4647
(->> form
4748
str
48-
(re-find #"->")
49-
nil?
50-
not))
49+
(re-find #"^->")
50+
some?))
5151

5252
(defn doto-form? [form]
53-
(= form 'doto))
53+
(#{'doto 'cljs.core/doto} form))
54+
55+
(defn js-interop? [x]
56+
(boolean (or (and (symbol? x)
57+
(-> x namespace (= "js")))
58+
(and (seq? x)
59+
(-> x first symbol?)
60+
(-> x first namespace (= "js"))))))
5461

5562
(defn expr-for-parent-obj
56-
"Given the context and symbol of a completion request, will try to find an
57-
expression that evaluates to the object being accessed."
63+
"Given the `context` and `symbol` of a completion request,
64+
will try to find an expression that evaluates to the object being accessed."
5865
[symbol context]
5966
(when-let [form (if (string? context)
6067
(try
@@ -67,55 +74,72 @@
6774
(let [prefix (find-prefix form)
6875
left-sibling (zip/left prefix)
6976
first? (nil? left-sibling)
70-
first-sibling (and (not first?) (some-> prefix zip/leftmost zip/node))
77+
first-sibling (when-not first?
78+
(some-> prefix zip/leftmost zip/node))
7179
first-sibling-in-parent (some-> prefix zip/up zip/leftmost zip/node)
72-
threaded? (if first? (thread-form? first-sibling-in-parent) (thread-form? first-sibling) )
73-
doto? (if first? (doto-form? first-sibling-in-parent) (doto-form? first-sibling))
80+
relevant-first-member (if first?
81+
first-sibling-in-parent
82+
first-sibling)
83+
threaded? (thread-form? relevant-first-member)
84+
doto? (doto-form? relevant-first-member)
85+
base (cond-> prefix
86+
(or (and threaded? first?)
87+
(and doto? first?))
88+
zip/up)
89+
;; the operand is the element over which the main -> (or doto, .., ., .-, etc) is being applied
90+
;; e.g. for (-> x f g h), it's x
91+
;; and for (-> x f (__prefix__)), it's also x
92+
operand (some-> base zip/leftmost zip/right zip/node)
7493
dot-fn? (starts-with? symbol ".")]
7594

76-
(letfn [(with-type [type maybe-expr]
95+
(letfn [(with-type [type likely-interop? maybe-expr]
7796
(when maybe-expr
7897
{:type type
98+
:js-interop? (or likely-interop? ;; .., doto, . and .- are mainly used for interop, so we can infer the intent is for interop.
99+
;; NOTE: detection could be extended to also detect when a given symbol `foo`
100+
;; maps to a JS library that was `require`d with "string requires".
101+
;; The cljs analyzer could be used for that.
102+
(js-interop? operand))
79103
:obj-expr maybe-expr}))]
80104
(cond
81105
(nil? prefix) nil
82106

83107
;; is it a threading macro?
84108
threaded?
85-
(with-type :-> (if first?
86-
;; parent is the thread
87-
(some-> prefix zip/up zip/lefts str)
88-
;; thread on same level
89-
(-> prefix zip/lefts str)))
109+
(with-type :-> false (if first?
110+
;; parent is the thread
111+
(some-> prefix zip/up zip/lefts str)
112+
;; thread on same level
113+
(-> prefix zip/lefts str)))
90114

91115
doto?
92-
(with-type :doto (if first?
93-
;; parent is the thread
94-
(some-> prefix zip/up zip/leftmost zip/right zip/node str)
95-
;; thread on same level
96-
(some-> prefix zip/leftmost zip/right zip/node str)))
116+
(with-type :doto true (if first?
117+
;; parent is the thread
118+
(some-> prefix zip/up zip/leftmost zip/right zip/node str)
119+
;; thread on same level
120+
(some-> prefix zip/leftmost zip/right zip/node str)))
97121

98122
;; a .. form: if __prefix__ is a prop deeper than one level we need the ..
99123
;; expr up to that point. If just the object that is accessed is left of
100124
;; prefix, we can take that verbatim.
101125
;; (.. js/console log) => js/console
102126
;; (.. js/console -memory -jsHeapSizeLimit) => (.. js/console -memory)
103127
(and first-sibling (#{"." ".."} (str first-sibling)) left-sibling)
104-
(with-type :.. (let [lefts (-> prefix zip/lefts)]
105-
(if (<= (count lefts) 2)
106-
(str (last lefts))
107-
(str lefts))))
128+
(with-type :.. true (let [lefts (-> prefix zip/lefts)]
129+
(if (<= (count lefts) 2)
130+
(str (last lefts))
131+
(str lefts))))
108132

109133
;; (.. js/window -console (log "foo")) => (.. js/window -console)
110134
(and first? (some-> prefix zip/up zip/leftmost zip/node str (= "..")))
111-
(with-type :.. (let [lefts (-> prefix zip/up zip/lefts)]
112-
(if (<= (count lefts) 2)
113-
(str (last lefts))
114-
(str lefts))))
135+
(with-type :.. true (let [lefts (-> prefix zip/up zip/lefts)]
136+
(if (<= (count lefts) 2)
137+
(str (last lefts))
138+
(str lefts))))
115139

116140
;; simple (.foo bar)
117141
(and first? dot-fn?)
118-
(with-type :. (some-> prefix zip/right zip/node str)))))))
142+
(with-type :. true (some-> prefix zip/right zip/node str)))))))
119143

120144
(def global-expr-re #"^js/((?:[^\.]+\.)*)([^\.]*)$")
121145
(def dot-dash-prefix-re #"^\.-?")
@@ -128,7 +152,6 @@
128152
those properties into candidates for completion."
129153
[symbol context]
130154
(if (starts-with? symbol "js/")
131-
132155
;; symbol is a global like js/console or global/property like js/console.log
133156
(let [[_ dotted-obj-expr prefix] (re-matches global-expr-re symbol)
134157
obj-expr-parts (keep not-empty (split dotted-obj-expr dot-prefix-re))
@@ -138,6 +161,7 @@
138161
obj-expr (cl-format nil "(this-as this ~[this~:;(.. this ~{-~A~^ ~})~])"
139162
(count obj-expr-parts) obj-expr-parts)]
140163
{:prefix prefix
164+
:js-interop? true
141165
:prepend-to-candidate (str "js/" dotted-obj-expr)
142166
:vars-have-dashes? false
143167
:obj-expr obj-expr
@@ -181,16 +205,20 @@
181205
are :extra-metadata :sort-order and :plain-candidates."
182206
[cljs-eval-fn symbol {:keys [ns context]}]
183207
(when (and symbol (not= "nil" symbol))
184-
(let [{:keys [prefix prepend-to-candidate vars-have-dashes? obj-expr type]}
208+
(let [{:keys [prefix prepend-to-candidate vars-have-dashes? obj-expr type js-interop?]}
185209
(analyze-symbol-and-context symbol context)
186210
global? (#{:global} type)]
187-
(when-let [{error :error properties :value} (and obj-expr (js-properties-of-object cljs-eval-fn ns obj-expr prefix))]
188-
(if (seq error)
189-
(when debug?
190-
(binding [*out* *err*]
191-
(println "[suitable] error in suitable cljs-completions:" error)))
192-
(for [{:keys [name type]} properties
193-
:let [maybe-dash (if (and vars-have-dashes? (= "var" type)) "-" "")
194-
candidate (str prepend-to-candidate maybe-dash name)]
195-
:when (starts-with? candidate symbol)]
196-
{:type type :candidate candidate :ns (if global? "js" obj-expr)}))))))
211+
;; ONLY evaluate forms that are detected as js interop,
212+
;; it's Suitable's promise per its README.
213+
;; There was issue #30 that broke this expectation at some point.
214+
(when js-interop?
215+
(when-let [{error :error properties :value} (and obj-expr (js-properties-of-object cljs-eval-fn ns obj-expr prefix))]
216+
(if (seq error)
217+
(when debug?
218+
(binding [*out* *err*]
219+
(println "[suitable] error in suitable cljs-completions:" error)))
220+
(for [{:keys [name type]} properties
221+
:let [maybe-dash (if (and vars-have-dashes? (= "var" type)) "-" "")
222+
candidate (str prepend-to-candidate maybe-dash name)]
223+
:when (starts-with? candidate symbol)]
224+
{:type type :candidate candidate :ns (if global? "js" obj-expr)})))))))

0 commit comments

Comments
 (0)