Skip to content

Commit d701b45

Browse files
authored
CLJS-3429: Handle More Complex Closure Type Annotations (#245)
* add cljs.externs/info helper * add cljs.externs/filter-externs helper * improve type parsing by handling the various cases and adding a simplifier * cleanup some old reflection warnings * add new test cases
1 parent 6d4bee6 commit d701b45

File tree

2 files changed

+85
-11
lines changed

2 files changed

+85
-11
lines changed

src/main/clojure/cljs/externs.clj

+58-10
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,43 @@
3838
(into [] (butlast props))
3939
(with-meta (last props) ty))))
4040

41+
(def token->kw
42+
{Token/BANG :bang
43+
Token/BLOCK :block
44+
Token/PIPE :pipe
45+
Token/STRINGLIT :string-lit
46+
Token/QMARK :qmark
47+
Token/STAR :star})
48+
49+
(defn parse-texpr [^Node root]
50+
(when-let [token (get token->kw (.getToken root))]
51+
(let [children (.children root)]
52+
(merge
53+
{:type token}
54+
(when-not (empty? children)
55+
{:children (vec (map parse-texpr (.children root)))})
56+
(when (= :string-lit token)
57+
{:value (.getString root)})))))
58+
59+
(defn undefined?
60+
[{:keys [type value] :as texpr}]
61+
(and (= type :string-lit)
62+
(= "undefined" value)))
63+
64+
(defn simplify-texpr
65+
[texpr]
66+
(case (:type texpr)
67+
:string-lit (some-> (:value texpr) symbol)
68+
(:star :qmark) 'any
69+
:bang (simplify-texpr (-> texpr :children first))
70+
:pipe (let [[x y] (:children texpr)]
71+
(if (undefined? y)
72+
(simplify-texpr x)
73+
'any))
74+
'any))
75+
4176
(defn get-tag [^JSTypeExpression texpr]
42-
(when-let [root (.getRoot texpr)]
43-
(if (.isString root)
44-
(symbol (.getString root))
45-
(if-let [child (.. root getFirstChild)]
46-
(if (.isString child)
47-
(symbol (.. child getString)))))))
77+
(some-> (.getRoot texpr) parse-texpr simplify-texpr))
4878

4979
(defn params->method-params [xs]
5080
(letfn [(not-opt? [x]
@@ -156,7 +186,7 @@
156186
[lhs])
157187
[]))))
158188

159-
(defmethod parse-extern-node Token/GETPROP [node]
189+
(defmethod parse-extern-node Token/GETPROP [^Node node]
160190
(when-not *ignore-var*
161191
(let [props (map symbol (string/split (.getQualifiedName node) #"\."))]
162192
[(if-let [ty (get-var-info node)]
@@ -165,7 +195,7 @@
165195

166196
;; JavaScript Object literal
167197
;; { ... }
168-
(defmethod parse-extern-node Token/OBJECTLIT [node]
198+
(defmethod parse-extern-node Token/OBJECTLIT [^Node node]
169199
(when (> (.getChildCount node) 0)
170200
(loop [nodes (.children node)
171201
externs []]
@@ -215,8 +245,8 @@
215245
(loop [nodes (cond-> nodes
216246
;; handle goog.modules which won't have top-levels
217247
;; need to look at internal children
218-
(= Token/MODULE_BODY (some-> nodes first .getToken))
219-
(-> first .children))
248+
(= Token/MODULE_BODY (some-> nodes ^Node (first) .getToken))
249+
(-> ^Node (first) .children))
220250
externs []]
221251
(if (empty? nodes)
222252
externs
@@ -313,6 +343,24 @@
313343
(parse-externs (resource->source-file rsrc))
314344
(:module desc))}))))
315345

346+
(defn info
347+
"Helper for grabbing var info from an externs map.
348+
Example:
349+
(info externs '[Number isNaN])
350+
See `externs-map`"
351+
[externs props]
352+
(-> externs
353+
(get-in (butlast props))
354+
(find (last props))
355+
first meta))
356+
357+
(defn filtered-externs [f]
358+
(->>
359+
(filter
360+
#(= f (.getName %))
361+
(default-externs))
362+
first parse-externs index-externs))
363+
316364
(comment
317365
(require '[clojure.java.io :as io]
318366
'[cljs.closure :as closure]

src/test/clojure/cljs/externs_parsing_tests.clj

+27-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
(ns cljs.externs-parsing-tests
1010
(:require [cljs.closure :as closure]
11+
[cljs.analyzer :as ana]
12+
[cljs.env :as env]
1113
[cljs.externs :as externs]
1214
[clojure.java.io :as io]
13-
[clojure.test :as test :refer [deftest is]])
15+
[clojure.test :as test :refer [deftest is testing]])
1416
(:import [com.google.javascript.jscomp CommandLineRunner]))
1517

1618
(deftest cljs-3121
@@ -45,8 +47,32 @@
4547
(find 'HTMLDocument) first meta)]
4648
(is (= 'Document (:super info)))))
4749

50+
(deftest test-parse-closure-type-annotations
51+
(let [externs (::ana/externs @(env/default-compiler-env))]
52+
(testing "JS global console has tag Console"
53+
(let [info (externs/info externs '[console])]
54+
(is (= 'Console (:tag info)))))
55+
(testing "JS global crypto has tag webCrypto.Crypto from:
56+
@type {!webCrypto.Crypto|undefined}"
57+
(let [info (externs/info externs '[crypto])]
58+
(is (= 'webCrypto.Crypto (:tag info)))))
59+
(testing "Generic return type on crypto methods returns ClojureScript relevant
60+
type info:"
61+
(testing "@return {!Promise<!ArrayBuffer>}"
62+
(let [info (externs/info externs '[webCrypto SubtleCrypto prototype encrypt])]
63+
(is (= 'Promise (:ret-tag info)))))
64+
(testing "@return {!Promise<!webCrypto.CryptoKey|!webCrypto.CryptoKeyPair>}"
65+
(let [info (externs/info externs '[webCrypto SubtleCrypto prototype deriveKey])]
66+
(is (= 'Promise (:ret-tag info)))))
67+
(testing "@return {!Int8Array|!Uint8Array|!Uint8ClampedArray|!Int16Array|!Uint16Array|!Int32Array|!Uint32Array|!BigInt64Array|!BigUint64Array}"
68+
(let [info (externs/info externs '[webCrypto Crypto prototype getRandomValues])]
69+
(is (= 'any (:ret-tag info))))))))
70+
4871
(comment
4972

73+
(let [externs (::ana/externs @(env/default-compiler-env))]
74+
(externs/info externs '[webCrypto Crypto prototype getRandomValues]))
75+
5076
(externs/parse-externs
5177
(externs/resource->source-file (io/resource "goog/object/object.js")))
5278

0 commit comments

Comments
 (0)