From b7481f7d25b45fd06d7ebf37d76e98b63b9e577b Mon Sep 17 00:00:00 2001
From: davidnolen <david.nolen@gmail.com>
Date: Sat, 25 Jan 2025 11:51:36 -0500
Subject: [PATCH 1/5] * externs parsing comments * doc externs-map * add info
 helper, doc * update eval comments

---
 src/main/clojure/cljs/externs.clj | 37 ++++++++++++++++++++++++++-----
 1 file changed, 32 insertions(+), 5 deletions(-)

diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj
index d86aa91ef..07ab13c86 100644
--- a/src/main/clojure/cljs/externs.clj
+++ b/src/main/clojure/cljs/externs.clj
@@ -124,6 +124,8 @@
   (when (> (.getChildCount node) 0)
     (parse-extern-node (.getFirstChild node))))
 
+;; Handle:
+;;   Math.hypot = function(...) {...};
 (defmethod parse-extern-node Token/ASSIGN [^Node node]
   (when (> (.getChildCount node) 0)
     (let [ty  (get-var-info node)
@@ -253,7 +255,20 @@
            externs (index-externs (parse-externs externs-file))))
        defaults sources))))
 
-(def externs-map (memoize externs-map*))
+(def ^{:doc "Returns a map of externs in the form:
+
+    {foo {bar {baz {}
+     woz {...}}
+
+  JavaScript var information is not held in the map itself, but on the
+  symbols in the map. See the helper `info` for grabbing the metadata.
+  The metadata map matches the layout of var info of the ClojureScript
+  analyzer: :file & :line, method info, :ret-tag, :tag, :doc, etc.
+  are all available.
+
+  See also `parse-externs`."}
+  externs-map
+  (memoize externs-map*))
 
 (defn ns-match? [ns-segs var-segs]
   (or
@@ -313,6 +328,16 @@
                 (parse-externs (resource->source-file rsrc))
                 (:module desc))}))))
 
+(defn info
+  "Helper for grabbing var info from an externs map.
+
+  See `externs-map`"
+  [externs props]
+  (-> externs
+    (get-in (butlast props))
+    (find (last props))
+    first meta))
+
 (comment
   (require '[clojure.java.io :as io]
            '[cljs.closure :as closure]
@@ -354,8 +379,7 @@
         [(closure/js-source-file "goog/date/date.js"
            (io/input-stream (io/resource "goog/date/date.js")))]
         {})
-    (get-in '[goog date month])
-    )
+    (get-in '[goog date month]))
 
   (pprint (analyze-goog-file "goog/date/date.js" 'goog.date.month))
 
@@ -385,7 +409,7 @@
   (->
     (filter
       (fn [s]
-        (= "externs.zip//webkit_dom.js" (.getName s)))
+        (= "externs.zip//whatwg_console.js" (.getName s)))
       (default-externs))
     first parse-externs index-externs
     (find 'console) first meta)
@@ -393,7 +417,7 @@
   (->
     (filter
       (fn [s]
-        (= "externs.zip//webkit_dom.js" (.getName s)))
+        (= "externs.zip//whatwg_console.js" (.getName s)))
       (default-externs))
     first parse-externs index-externs
     (get-in '[Console prototype])
@@ -402,8 +426,11 @@
   (require '[clojure.java.io :as io]
            '[cljs.closure :as cc])
 
+  ;; react.ext.js needs to be available
   (-> (cc/js-source-file nil (io/file "react.ext.js"))
     parse-externs index-externs
     (get 'React)
     (find 'Component) first meta)
+
+  (-> (info (externs-map) '[Number isNaN]) :ret-tag) ;; => boolean
   )

From fee443cc711eb5b720006144786d38b8d85255df Mon Sep 17 00:00:00 2001
From: davidnolen <david.nolen@gmail.com>
Date: Sat, 25 Jan 2025 12:24:44 -0500
Subject: [PATCH 2/5] * copy over min/max logic from Clojure

---
 src/main/cljs/cljs/core.cljs | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs
index dcdf23c31..ef2818486 100644
--- a/src/main/cljs/cljs/core.cljs
+++ b/src/main/cljs/cljs/core.cljs
@@ -2771,14 +2771,22 @@ reduces them without incurring seq initialization"
 (defn ^number max
   "Returns the greatest of the nums."
   ([x] x)
-  ([x y] (cljs.core/max x y))
+  ([x y]
+   (cond
+     (.isNaN js/Number x) x
+     (.isNaN js/Number y) y
+     :else (cljs.core/max x y)))
   ([x y & more]
    (reduce max (cljs.core/max x y) more)))
 
 (defn ^number min
   "Returns the least of the nums."
   ([x] x)
-  ([x y] (cljs.core/min x y))
+  ([x y]
+   (cond
+     (.isNaN js/Number x) x
+     (.isNaN js/Number y) y
+     :else (cljs.core/min x y)))
   ([x y & more]
    (reduce min (cljs.core/min x y) more)))
 

From 9609c26afafd79d2531d39b91ee5e39e846db382 Mon Sep 17 00:00:00 2001
From: davidnolen <david.nolen@gmail.com>
Date: Sat, 25 Jan 2025 12:25:12 -0500
Subject: [PATCH 3/5] * wip add TODO

---
 src/test/clojure/cljs/externs_parsing_tests.clj | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/test/clojure/cljs/externs_parsing_tests.clj b/src/test/clojure/cljs/externs_parsing_tests.clj
index effad773d..a698bf866 100644
--- a/src/test/clojure/cljs/externs_parsing_tests.clj
+++ b/src/test/clojure/cljs/externs_parsing_tests.clj
@@ -7,7 +7,10 @@
 ;; You must not remove this notice, or any other, from this software.
 
 (ns cljs.externs-parsing-tests
-  (:require [cljs.closure :as closure]
+  (:require [cljs.analyzer :as ana]
+            [cljs.closure :as closure]
+            [cljs.compiler :as comp]
+            [cljs.env :as env]
             [cljs.externs :as externs]
             [clojure.java.io :as io]
             [clojure.test :as test :refer [deftest is]])
@@ -45,8 +48,16 @@
                (find 'HTMLDocument) first meta)]
     (is (= 'Document (:super info)))))
 
+;; TODO:
+;; analyze (.isNaN js/NaN 1)
+;; node :tag should be js/Boolean
+
 (comment
 
+  ;; js/Boolean
+  (env/with-compiler-env (env/default-compiler-env)
+    (ana/js-tag '[Number isNaN] :ret-tag))
+
   (externs/parse-externs
     (externs/resource->source-file (io/resource "goog/object/object.js")))
 

From 19677dbcef4702c65b1c1f584800dd56400c1114 Mon Sep 17 00:00:00 2001
From: davidnolen <david.nolen@gmail.com>
Date: Mon, 27 Jan 2025 21:23:37 -0500
Subject: [PATCH 4/5] * add js-global? helper to analyzer * in host call check
 for this case   - if js global change the type resolution via externs * add
 some comment eval exprs for context

---
 src/main/clojure/cljs/analyzer.cljc           | 19 ++++++++++----
 src/main/clojure/cljs/compiler.cljc           |  2 +-
 src/main/clojure/cljs/externs.clj             |  1 +
 .../clojure/cljs/externs_parsing_tests.clj    | 25 ++++++++++++++++---
 4 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc
index 8c61c4586..ba8ff4240 100644
--- a/src/main/clojure/cljs/analyzer.cljc
+++ b/src/main/clojure/cljs/analyzer.cljc
@@ -3542,6 +3542,12 @@
                  (list* '. dot-form) " with classification "
                  (classify-dot-form dot-form))))))
 
+(defn js-global?
+  "Return true if the expr is a JS global"
+  [expr]
+  (and (= 'js (:ns expr))
+       (some? (get-in (get-externs) [(-> (:name expr) name symbol)]))))
+
 (defn analyze-dot [env target field member+ form]
   (let [v [target field member+]
         {:keys [dot-action target method field args]} (build-dot-form v)
@@ -3550,11 +3556,14 @@
         form-meta  (meta form)
         target-tag (:tag targetexpr)
         prop       (or field method)
-        tag        (or (:tag form-meta)
-                       (and (js-tag? target-tag)
-                            (vary-meta (normalize-js-tag target-tag)
-                              update-in [:prefix] (fnil conj '[Object]) prop))
-                       nil)]
+        tag        (if (js-global? targetexpr)
+                     ;; we have a known global, don't treat as instance of some type
+                     (with-meta 'js {:prefix [(-> targetexpr :name name symbol) prop]})
+                     (or (:tag form-meta)
+                         (and (js-tag? target-tag)
+                              (vary-meta (normalize-js-tag target-tag)
+                                update-in [:prefix] (fnil conj '[Object]) prop))
+                         nil))]
     (when (and (not= 'constructor prop)
                (not (string/starts-with? (str prop) "cljs$"))
                (not (-> prop meta :protocol-prop)))
diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc
index b96c09b36..632cc75de 100644
--- a/src/main/clojure/cljs/compiler.cljc
+++ b/src/main/clojure/cljs/compiler.cljc
@@ -641,7 +641,7 @@
 
 (defn safe-test? [env e]
   (let [tag (ana/infer-tag env e)]
-    (or (#{'boolean 'seq} tag) (truthy-constant? e))))
+    (or ('#{boolean seq js/Boolean} tag) (truthy-constant? e))))
 
 (defmethod emit* :if
   [{:keys [test then else env unchecked]}]
diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj
index 07ab13c86..71619e25c 100644
--- a/src/main/clojure/cljs/externs.clj
+++ b/src/main/clojure/cljs/externs.clj
@@ -432,5 +432,6 @@
     (get 'React)
     (find 'Component) first meta)
 
+  (info (externs-map) '[Number])
   (-> (info (externs-map) '[Number isNaN]) :ret-tag) ;; => boolean
   )
diff --git a/src/test/clojure/cljs/externs_parsing_tests.clj b/src/test/clojure/cljs/externs_parsing_tests.clj
index a698bf866..1d5a51698 100644
--- a/src/test/clojure/cljs/externs_parsing_tests.clj
+++ b/src/test/clojure/cljs/externs_parsing_tests.clj
@@ -48,16 +48,35 @@
                (find 'HTMLDocument) first meta)]
     (is (= 'Document (:super info)))))
 
-;; TODO:
-;; analyze (.isNaN js/NaN 1)
-;; node :tag should be js/Boolean
+(deftest test-number-infer-test
+  (let [cenv (env/default-compiler-env)
+        aenv (ana/empty-env)]
+    (is (= (env/with-compiler-env cenv
+             (:tag (ana/analyze aenv '(.isNaN js/Number 1))))
+           'js/Boolean))))
+
+;; TODO: js/subtle.crypto
 
 (comment
 
+  (externs/info
+    (::ana/externs @(env/default-compiler-env))
+    '[Number])
+
+  (externs/info
+    (::ana/externs @(env/default-compiler-env))
+    '[Number isNaN])
+
   ;; js/Boolean
   (env/with-compiler-env (env/default-compiler-env)
     (ana/js-tag '[Number isNaN] :ret-tag))
 
+  ;; js
+  (let [cenv (env/default-compiler-env)
+        aenv (ana/empty-env)]
+    (->> (env/with-compiler-env cenv
+           (:tag (ana/analyze aenv '(.isNaN js/Number 1))))))
+
   (externs/parse-externs
     (externs/resource->source-file (io/resource "goog/object/object.js")))
 

From 67b53695f90e314e9720cb16ff9849f60a0f6d7d Mon Sep 17 00:00:00 2001
From: davidnolen <david.nolen@gmail.com>
Date: Mon, 27 Jan 2025 21:29:29 -0500
Subject: [PATCH 5/5] * add info example

---
 src/main/clojure/cljs/externs.clj | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj
index 71619e25c..a6d460eab 100644
--- a/src/main/clojure/cljs/externs.clj
+++ b/src/main/clojure/cljs/externs.clj
@@ -330,6 +330,8 @@
 
 (defn info
   "Helper for grabbing var info from an externs map.
+  Example:
+    (info externs '[Number isNaN])
 
   See `externs-map`"
   [externs props]