diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj index d86aa91ef..910643b49 100644 --- a/src/main/clojure/cljs/externs.clj +++ b/src/main/clojure/cljs/externs.clj @@ -38,13 +38,43 @@ (into [] (butlast props)) (with-meta (last props) ty)))) +(def token->kw + {Token/BANG :bang + Token/BLOCK :block + Token/PIPE :pipe + Token/STRINGLIT :string-lit + Token/QMARK :qmark + Token/STAR :star}) + +(defn parse-texpr [^Node root] + (when-let [token (get token->kw (.getToken root))] + (let [children (.children root)] + (merge + {:type token} + (when-not (empty? children) + {:children (vec (map parse-texpr (.children root)))}) + (when (= :string-lit token) + {:value (.getString root)}))))) + +(defn undefined? + [{:keys [type value] :as texpr}] + (and (= type :string-lit) + (= "undefined" value))) + +(defn simplify-texpr + [texpr] + (case (:type texpr) + :string-lit (some-> (:value texpr) symbol) + (:star :qmark) 'any + :bang (simplify-texpr (-> texpr :children first)) + :pipe (let [[x y] (:children texpr)] + (if (undefined? y) + (simplify-texpr x) + 'any)) + 'any)) + (defn get-tag [^JSTypeExpression texpr] - (when-let [root (.getRoot texpr)] - (if (.isString root) - (symbol (.getString root)) - (if-let [child (.. root getFirstChild)] - (if (.isString child) - (symbol (.. child getString))))))) + (some-> (.getRoot texpr) parse-texpr simplify-texpr)) (defn params->method-params [xs] (letfn [(not-opt? [x] @@ -156,7 +186,7 @@ [lhs]) [])))) -(defmethod parse-extern-node Token/GETPROP [node] +(defmethod parse-extern-node Token/GETPROP [^Node node] (when-not *ignore-var* (let [props (map symbol (string/split (.getQualifiedName node) #"\."))] [(if-let [ty (get-var-info node)] @@ -165,7 +195,7 @@ ;; JavaScript Object literal ;; { ... } -(defmethod parse-extern-node Token/OBJECTLIT [node] +(defmethod parse-extern-node Token/OBJECTLIT [^Node node] (when (> (.getChildCount node) 0) (loop [nodes (.children node) externs []] @@ -215,8 +245,8 @@ (loop [nodes (cond-> nodes ;; handle goog.modules which won't have top-levels ;; need to look at internal children - (= Token/MODULE_BODY (some-> nodes first .getToken)) - (-> first .children)) + (= Token/MODULE_BODY (some-> nodes ^Node (first) .getToken)) + (-> ^Node (first) .children)) externs []] (if (empty? nodes) externs @@ -313,6 +343,24 @@ (parse-externs (resource->source-file rsrc)) (:module desc))})))) +(defn info + "Helper for grabbing var info from an externs map. + Example: + (info externs '[Number isNaN]) + See `externs-map`" + [externs props] + (-> externs + (get-in (butlast props)) + (find (last props)) + first meta)) + +(defn filtered-externs [f] + (->> + (filter + #(= f (.getName %)) + (default-externs)) + first parse-externs index-externs)) + (comment (require '[clojure.java.io :as io] '[cljs.closure :as closure] diff --git a/src/test/clojure/cljs/externs_parsing_tests.clj b/src/test/clojure/cljs/externs_parsing_tests.clj index effad773d..ed0cfdb70 100644 --- a/src/test/clojure/cljs/externs_parsing_tests.clj +++ b/src/test/clojure/cljs/externs_parsing_tests.clj @@ -8,9 +8,11 @@ (ns cljs.externs-parsing-tests (:require [cljs.closure :as closure] + [cljs.analyzer :as ana] + [cljs.env :as env] [cljs.externs :as externs] [clojure.java.io :as io] - [clojure.test :as test :refer [deftest is]]) + [clojure.test :as test :refer [deftest is testing]]) (:import [com.google.javascript.jscomp CommandLineRunner])) (deftest cljs-3121 @@ -45,8 +47,32 @@ (find 'HTMLDocument) first meta)] (is (= 'Document (:super info))))) +(deftest test-parse-closure-type-annotations + (let [externs (::ana/externs @(env/default-compiler-env))] + (testing "JS global console has tag Console" + (let [info (externs/info externs '[console])] + (is (= 'Console (:tag info))))) + (testing "JS global crypto has tag webCrypto.Crypto from: + @type {!webCrypto.Crypto|undefined}" + (let [info (externs/info externs '[crypto])] + (is (= 'webCrypto.Crypto (:tag info))))) + (testing "Generic return type on crypto methods returns ClojureScript relevant + type info:" + (testing "@return {!Promise}" + (let [info (externs/info externs '[webCrypto SubtleCrypto prototype encrypt])] + (is (= 'Promise (:ret-tag info))))) + (testing "@return {!Promise}" + (let [info (externs/info externs '[webCrypto SubtleCrypto prototype deriveKey])] + (is (= 'Promise (:ret-tag info))))) + (testing "@return {!Int8Array|!Uint8Array|!Uint8ClampedArray|!Int16Array|!Uint16Array|!Int32Array|!Uint32Array|!BigInt64Array|!BigUint64Array}" + (let [info (externs/info externs '[webCrypto Crypto prototype getRandomValues])] + (is (= 'any (:ret-tag info)))))))) + (comment + (let [externs (::ana/externs @(env/default-compiler-env))] + (externs/info externs '[webCrypto Crypto prototype getRandomValues])) + (externs/parse-externs (externs/resource->source-file (io/resource "goog/object/object.js")))