|
1 | 1 | (ns suitable.js-completions |
2 | 2 | (: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]])) |
7 | 8 |
|
8 | 9 | (def debug? false) |
9 | 10 |
|
|
45 | 46 | [form] |
46 | 47 | (->> form |
47 | 48 | str |
48 | | - (re-find #"->") |
49 | | - nil? |
50 | | - not)) |
| 49 | + (re-find #"^->") |
| 50 | + some?)) |
51 | 51 |
|
52 | 52 | (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")))))) |
54 | 61 |
|
55 | 62 | (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." |
58 | 65 | [symbol context] |
59 | 66 | (when-let [form (if (string? context) |
60 | 67 | (try |
|
67 | 74 | (let [prefix (find-prefix form) |
68 | 75 | left-sibling (zip/left prefix) |
69 | 76 | 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)) |
71 | 79 | 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) |
74 | 93 | dot-fn? (starts-with? symbol ".")] |
75 | 94 |
|
76 | | - (letfn [(with-type [type maybe-expr] |
| 95 | + (letfn [(with-type [type likely-interop? maybe-expr] |
77 | 96 | (when maybe-expr |
78 | 97 | {: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)) |
79 | 103 | :obj-expr maybe-expr}))] |
80 | 104 | (cond |
81 | 105 | (nil? prefix) nil |
82 | 106 |
|
83 | 107 | ;; is it a threading macro? |
84 | 108 | 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))) |
90 | 114 |
|
91 | 115 | 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))) |
97 | 121 |
|
98 | 122 | ;; a .. form: if __prefix__ is a prop deeper than one level we need the .. |
99 | 123 | ;; expr up to that point. If just the object that is accessed is left of |
100 | 124 | ;; prefix, we can take that verbatim. |
101 | 125 | ;; (.. js/console log) => js/console |
102 | 126 | ;; (.. js/console -memory -jsHeapSizeLimit) => (.. js/console -memory) |
103 | 127 | (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)))) |
108 | 132 |
|
109 | 133 | ;; (.. js/window -console (log "foo")) => (.. js/window -console) |
110 | 134 | (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)))) |
115 | 139 |
|
116 | 140 | ;; simple (.foo bar) |
117 | 141 | (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))))))) |
119 | 143 |
|
120 | 144 | (def global-expr-re #"^js/((?:[^\.]+\.)*)([^\.]*)$") |
121 | 145 | (def dot-dash-prefix-re #"^\.-?") |
|
128 | 152 | those properties into candidates for completion." |
129 | 153 | [symbol context] |
130 | 154 | (if (starts-with? symbol "js/") |
131 | | - |
132 | 155 | ;; symbol is a global like js/console or global/property like js/console.log |
133 | 156 | (let [[_ dotted-obj-expr prefix] (re-matches global-expr-re symbol) |
134 | 157 | obj-expr-parts (keep not-empty (split dotted-obj-expr dot-prefix-re)) |
|
138 | 161 | obj-expr (cl-format nil "(this-as this ~[this~:;(.. this ~{-~A~^ ~})~])" |
139 | 162 | (count obj-expr-parts) obj-expr-parts)] |
140 | 163 | {:prefix prefix |
| 164 | + :js-interop? true |
141 | 165 | :prepend-to-candidate (str "js/" dotted-obj-expr) |
142 | 166 | :vars-have-dashes? false |
143 | 167 | :obj-expr obj-expr |
|
181 | 205 | are :extra-metadata :sort-order and :plain-candidates." |
182 | 206 | [cljs-eval-fn symbol {:keys [ns context]}] |
183 | 207 | (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?]} |
185 | 209 | (analyze-symbol-and-context symbol context) |
186 | 210 | 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