/
analysis.cljc
269 lines (225 loc) · 7.2 KB
/
analysis.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
(ns suitable.compliment.sources.cljs.analysis
(:require [clojure.string :as str])
(:refer-clojure :exclude [find-ns all-ns ns-aliases]))
(def NSES :cljs.analyzer/namespaces)
(defn as-sym [x]
(if x (symbol x)))
(defn namespace-sym
"Return the namespace of a fully qualified symbol if possible.
It leaves the symbol untouched if not."
[sym]
(if-let [ns (and sym (namespace sym))]
(as-sym ns)
sym))
(defn name-sym
"Return the name of a fully qualified symbol if possible.
It leaves the symbol untouched if not."
[sym]
(if-let [n (and sym (name sym))]
(as-sym n)
sym))
(defn all-ns
[env]
(->> (NSES env)
;; recent CLJS versions include data about macro namespaces in the
;; compiler env, but we should not include them in completions or pass
;; them to format-ns unless they're actually required
(into {} (filter (fn [[_ ns]]
(not (and (contains? ns :macros)
(= 1 (count ns)))))))))
(defn find-ns
[env ns]
(get (all-ns env) ns))
(defn add-ns-macros
"Append $macros to the input symbol"
[sym]
(some-> sym
(str "$macros")
symbol))
(defn remove-macros
"Remove $macros from the input symbol"
[sym]
(some-> sym
str
(str/replace #"\$macros" "")
symbol))
;; Code adapted from clojure-complete (http://github.com/ninjudd/clojure-complete)
(defn imports
"Returns a map of [import-name] to [ns-qualified-import-name] for all imports
in the given namespace."
[env ns]
(:imports (find-ns env ns)))
(defn ns-aliases
"Returns a map {ns-name-or-alias ns-name} for the given namespace."
[env ns]
(when-let [found (find-ns env ns)]
(let [imports (:imports found)]
(->> (:requires found)
(filter #(not (contains? imports (key %))))
(into {})))))
(defn macro-ns-aliases
"Returns a map of [macro-ns-name-or-alias] to [macro-ns-name] for the given namespace."
[env ns]
(:require-macros (find-ns env ns)))
(defn- expand-refer-map
[m]
(into {} (for [[k v] m] [k (symbol (str v "/" k))])))
(defn referred-vars
"Returns a map of [var-name] to [ns-qualified-var-name] for all referred vars
in the given namespace."
[env ns]
(->> (find-ns env ns)
:uses
expand-refer-map))
(defn referred-macros
"Returns a map of [macro-name] to [ns-qualified-macro-name] for all referred
macros in the given namespace."
[env ns]
(->> (find-ns env ns)
:use-macros
expand-refer-map))
(defn ns-alias
"If sym is an alias to, or the name of, a namespace referred to in ns, returns
the name of the namespace; else returns nil."
[env sym ns]
(get (ns-aliases env ns) (as-sym sym)))
(defn macro-ns-alias
"If sym is an alias to, or the name of, a macro namespace referred to in ns,
returns the name of the macro namespace; else returns nil."
[env sym ns]
(get (macro-ns-aliases env ns) (as-sym sym)))
(defn- public?
[[_ var]]
(not (:private var)))
(defn- named?
[[_ var]]
(not (:anonymous var)))
(defn- foreign-protocol?
[[_ var]]
(and (:impls var)
(not (:protocol-symbol var))))
(defn- macro?
[[_ var]]
(:macro (meta var)))
(defn ns-vars
"Returns a list of the vars declared in the ns."
[env ns]
(->> (find-ns env ns)
:defs
(filter (every-pred named? (complement foreign-protocol?)))
(into {})))
(defn public-vars
"Returns a list of the public vars declared in the ns."
[env ns]
(->> (find-ns env ns)
:defs
(filter (every-pred named? public? (complement foreign-protocol?)))
(into {})))
(defn public-macros
"Given a namespace return all the public var analysis maps. Analagous to
clojure.core/ns-publics but returns var analysis maps not vars.
Inspired by the ns-publics in cljs.analyzer.api."
[env ns]
{:pre [(symbol? ns)]}
#?(:clj (when (and ns (clojure.core/find-ns ns))
(->> (ns-publics ns)
(filter macro?)
(into {})))
:cljs (->> (merge
(get-in env [NSES ns :macros])
(get-in env [NSES ns :defs]))
(remove (fn [[k v]] (:private v)))
(into {}))))
(defn core-vars
"Returns a list of cljs.core vars visible to the ns."
[env ns]
(let [vars (public-vars env 'cljs.core)
excludes (:excludes (find-ns env ns))]
(apply dissoc vars excludes)))
(defn core-macros
"Returns a list of cljs.core macros visible to the ns."
[env ns]
(let [macros (public-macros env #?(:clj 'cljs.core :cljs 'cljs.core$macros))
excludes (:excludes (find-ns env ns))]
(apply dissoc macros excludes)))
(def ^:private language-keywords
#{:require :require-macros :import
:refer :refer-macros :include-macros
:refer-clojure :exclude
:keys :strs :syms
:as :or
:pre :post
:let :when :while
;; reader conditionals
:clj :cljs :default
;; common meta keywords
:private :tag :static
:doc :author :arglists
:added :const
;; spec keywords
:req :req-un :opt :opt-un
:args :ret :fn
;; misc
:keywordize-keys :else :gen-class})
(defn keyword-constants
"Returns a list of both keyword constants in the environment and
language specific ones."
[env]
;; using namespace for backward compatibility with Clojure 1.8
;; use qualified-keyword? at some point in the future
(concat language-keywords (filter namespace (keys (:cljs.analyzer/constant-table env)))))
;; grabbing directly from cljs.analyzer.api
(defn ns-interns-from-env
"Given a namespace return all the var analysis maps. Analagous to
clojure.core/ns-interns but returns var analysis maps not vars.
Directly from cljs.analyzer.api."
[env ns]
{:pre [(symbol? ns)]}
(merge
(get-in env [NSES ns :macros])
(get-in env [NSES ns :defs])))
(defn sanitize-ns
"Add :ns from :name if missing."
[m]
(cond-> m
(or (:name m) (:ns m)) (-> (assoc :ns (or (:ns m) (:name m)))
(update :ns namespace-sym)
(update :name name-sym))))
(defn ns-obj?
"Return true if n is a namespace object"
[ns]
(instance? #?(:clj clojure.lang.Namespace
:cljs cljs.core/Namespace)
ns))
(defn var-meta
"Return meta for the var, we wrap it in order to support both JVM and
self-host."
[var]
(cond-> {}
(map? var) (merge var)
(var? var) (-> (merge (meta var))
(update :ns #(cond-> % (ns-obj? %) ns-name)))
true sanitize-ns
#?@(:cljs [true (-> (update :ns remove-macros)
(update :name remove-macros))])))
(defn qualified-symbol-meta
"Given a namespace-qualified var name, gets the analyzer metadata for
that var."
[env sym]
{:pre [(symbol? sym)]}
(let [ns (find-ns env (namespace-sym sym))]
(some-> (:defs ns)
(get (name-sym sym))
var-meta)))
(defn ns-meta
[ns]
{:pre [(symbol? ns)]}
(meta (clojure.core/find-ns ns)))
(defn macro-meta
[env qualified-sym]
#?(:clj (some-> (find-var qualified-sym) var-meta)
:cljs (let [referred-ns (symbol (namespace qualified-sym))]
(some-> env
(ns-interns-from-env (add-ns-macros referred-ns))
(get refer)
var-meta))))