diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 8c61c4586..ec73d8c19 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -982,8 +982,7 @@ (if-not (= 'js x) (with-meta 'js {:prefix (conj (->> (string/split (name x) #"\.") - (map symbol) vec) - 'prototype)}) + (map symbol) vec))}) x)) (defn ->type-set @@ -1030,34 +1029,50 @@ boolean Boolean symbol Symbol}) -(defn has-extern?* +(defn resolve-extern + "Given a foreign js property list, return a resolved js property list and the + extern var info" ([pre externs] - (let [pre (if-some [me (find - (get-in externs '[Window prototype]) - (first pre))] - (if-some [tag (-> me first meta :tag)] - (into [tag 'prototype] (next pre)) - pre) - pre)] - (has-extern?* pre externs externs))) - ([pre externs top] + (resolve-extern pre externs externs {:resolved [] :info nil})) + ([pre externs top ret] (cond - (empty? pre) true + (empty? pre) ret :else (let [x (first pre) me (find externs x)] (cond - (not me) false + (not me) nil :else (let [[x' externs'] me - xmeta (meta x')] - (if (and (= 'Function (:tag xmeta)) (:ctor xmeta)) - (or (has-extern?* (into '[prototype] (next pre)) externs' top) - (has-extern?* (next pre) externs' top) - ;; check base type if it exists - (when-let [super (:super xmeta)] - (has-extern?* (into [super] (next pre)) externs top))) - (recur (next pre) externs' top)))))))) + info' (meta x')] + (if (and (= 'Function (:tag info')) (:ctor info')) + (or + ;; then check for "static" property + (resolve-extern (next pre) externs' top + (-> ret + (update :resolved conj x) + (assoc :info info'))) + + ;; first look for a property on the prototype + (resolve-extern (into '[prototype] (next pre)) externs' top + (-> ret + (update :resolved conj x) + (assoc :info nil))) + + ;; finally check the super class if there is one + (when-let [super (:super info')] + (resolve-extern (into [super] (next pre)) externs top + (-> ret + (update :resolved conj x) + (assoc :info nil))))) + (recur (next pre) externs' top + (-> ret + (update :resolved conj x) + (assoc :info info')))))))))) + +(defn has-extern?* + [pre externs] + (boolean (resolve-extern pre externs))) (defn has-extern? ([pre] diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj index 8ca7ff9aa..4b4f4013f 100644 --- a/src/test/clojure/cljs/externs_infer_tests.clj +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -23,6 +23,15 @@ "goog.isArrayLike;" "Java.type;" "Object.out;" "Object.out.println;" "Object.error;" "Object.error.println;"]) +(deftest test-resolve-extern + (let [externs + (externs/externs-map + (closure/load-externs + {:externs ["src/test/externs/test.js"] + :use-only-custom-externs true}))] + (is (some? (ana/resolve-extern '[baz] externs))) + (is (nil? (ana/resolve-extern '[Foo gozMethod] externs))))) + (deftest test-has-extern?-basic (let [externs (externs/externs-map (closure/load-externs @@ -35,6 +44,25 @@ (is (true? (ana/has-extern? '[baz] externs))) (is (false? (ana/has-extern? '[Baz] externs))))) +(deftest test-resolve-extern + (let [externs (externs/externs-map)] + (is (= '[Number] + (-> (ana/resolve-extern '[Number] externs) :resolved))) + (is (= '[Number prototype valueOf] + (-> (ana/resolve-extern '[Number valueOf] externs) :resolved))))) + +(comment + + (def externs (externs/externs-map)) + + ;; succeeds + (ana/resolve-extern '[console] externs) + + ;; this one fails + (ana/resolve-extern '[console log] externs) + + ) + (deftest test-has-extern?-defaults (let [externs (externs/externs-map)] (is (true? (ana/has-extern? '[console] externs))) @@ -158,9 +186,9 @@ :warnings ws})] (is (= (unsplit-lines ["Foo.Boo.prototype.wozz;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Cannot resolve property wozz for inferred type js/Foo.Boo")))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property wozz for inferred type js/Foo.Boo"))))) (deftest test-type-hint-infer-unknown-property-in-chain (let [ws (atom []) @@ -172,9 +200,9 @@ :warnings ws})] (is (= (unsplit-lines ["Foo.Boo.prototype.wozz;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Cannot resolve property wozz for inferred type js/Foo.Boo")))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property wozz for inferred type js/Foo.Boo"))))) (deftest test-type-hint-infer-unknown-method (let [ws (atom []) @@ -185,9 +213,15 @@ :warnings ws})] (is (= (unsplit-lines ["Foo.prototype.gozMethod;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Cannot resolve property gozMethod for inferred type js/Foo")))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property gozMethod for inferred type js/Foo"))))) + +(comment + + (clojure.test/test-vars [#'test-type-hint-infer-unknown-method]) + + ) (deftest test-infer-unknown-method-from-externs (let [ws (atom []) @@ -197,9 +231,9 @@ :warnings ws})] (is (= (unsplit-lines ["Foo.prototype.gozMethod;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Cannot resolve property gozMethod for inferred type js/Foo")))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property gozMethod for inferred type js/Foo"))))) (deftest test-infer-js-require (let [ws (atom []) @@ -211,9 +245,9 @@ :warnings ws})] (is (= (unsplit-lines ["var require;" "Object.Component;"]) res)) (is (= 1 (count @ws))) - (is (string/starts-with? - (first @ws) - "Adding extern to Object for property Component")))) + (is (some-> @ws first + (string/starts-with? + "Adding extern to Object for property Component"))))) (deftest test-set-warn-on-infer (let [ws (atom []) @@ -227,7 +261,9 @@ :warn false :with-core? true})] (is (= 1 (count @ws))) - (is (string/starts-with? (first @ws) "Cannot infer target type")))) + (is (some-> @ws first + (string/starts-with? + "Cannot infer target type"))))) (deftest test-cljs-1970-infer-with-cljs-literals (let [ws (atom [])