diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 709531e59..4b6032ffe 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -2820,13 +2820,19 @@ (error env (error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))))))))))) +(defn global-ns? [x] + (or (= 'js x) + (= "js" (namespace x)))) + (defn missing-use? [lib sym cenv] - (let [js-lib (get-in cenv [:js-dependency-index (name lib)])] - (and (= (get-in cenv [::namespaces lib :defs sym] ::not-found) ::not-found) - (not (= (get js-lib :group) :goog)) - (not (get js-lib :closure-lib)) - (not (node-module-dep? lib)) - (not (dep-has-global-exports? lib))))) + ;; ignore globals referred via :refer-global + (when-not (global-ns? lib) + (let [js-lib (get-in cenv [:js-dependency-index (name lib)])] + (and (= (get-in cenv [::namespaces lib :defs sym] ::not-found) ::not-found) + (not (= (get js-lib :group) :goog)) + (not (get js-lib :closure-lib)) + (not (node-module-dep? lib)) + (not (dep-has-global-exports? lib)))))) (defn missing-rename? [sym cenv] (let [lib (symbol (namespace sym)) @@ -3039,6 +3045,37 @@ ret (recur fs ret true))))) +(defn parse-global-refer-spec + [env args] + (let [xs (filter #(-> % first (= :refer-global)) args) + cnt (count xs)] + (cond + (> cnt 1) + (throw (error env "Only one :refer-global form is allowed per namespace definition")) + + (== cnt 1) + (let [[_ & {:keys [only rename] :as parsed-spec}] (first xs) + err-str "Only (:refer-global :only [names]) and optionally `:rename {from to}` specs supported"] + (when-not (or (empty? only) + (and (vector? only) + (every? symbol only))) + (throw (error env err-str))) + (when-not (or (empty? rename) + (and (map? rename) + (every? symbol (mapcat identity rename)))) + (throw (error env (str err-str (pr-str parsed-spec))))) + (when-not (every? #{:only :rename} (keys parsed-spec)) + (throw (error env (str err-str (pr-str parsed-spec))))) + {:use (zipmap only (repeat 'js)) + :rename (into {} + (map (fn [[orig new-name]] + [new-name (symbol "js" (str orig))])) + rename)})))) + +#_(defn parse-global-require-spec + [env deps aliases spec] + ) + (defn parse-require-spec [env macros? deps aliases spec] (if (or (symbol? spec) (string? spec)) (recur env macros? deps aliases [spec]) @@ -3292,6 +3329,10 @@ (select-keys new deep-merge-keys)))) new)) +(def ns-spec-cases + #{:use :use-macros :require :require-macros + :import :refer-global :require-global}) + (defmethod parse 'ns [_ env [_ name & args :as form] _ opts] (when-not *allow-ns* @@ -3326,6 +3367,7 @@ core-renames (reduce (fn [m [original renamed]] (assoc m renamed (symbol "cljs.core" (str original)))) {} core-renames) + {global-uses :use global-renames :rename} (parse-global-refer-spec env args) deps (atom []) ;; as-aliases can only be used *once* because they are about the reader aliases (atom {:fns as-aliases :macros as-aliases}) @@ -3335,7 +3377,9 @@ (partial use->require env)) :use-macros (comp (partial parse-require-spec env true deps aliases) (partial use->require env)) - :import (partial parse-import-spec env deps)} + :import (partial parse-import-spec env deps) + ;:require-global #(parse-global-require-spec env deps aliases %) + } valid-forms (atom #{:use :use-macros :require :require-macros :import}) reload (atom {:use nil :require nil :use-macros nil :require-macros nil}) reloads (atom {}) @@ -3362,7 +3406,7 @@ (apply merge-with merge m (map (spec-parsers k) (remove #{:reload :reload-all} libs)))) - {} (remove (fn [[r]] (= r :refer-clojure)) args)) + {} (remove (fn [[r]] (#{:refer-clojure :refer-global} r)) args)) ;; patch `require-macros` and `use-macros` in Bootstrap for namespaces ;; that require their own macros #?@(:cljs [[require-macros use-macros] @@ -3384,9 +3428,9 @@ :use-macros use-macros :require-macros require-macros :rename-macros rename-macros - :uses uses + :uses (merge uses global-uses) :requires requires - :renames (merge renames core-renames) + :renames (merge renames core-renames global-renames) :imports imports}] (swap! env/*compiler* update-in [::namespaces name] merge ns-info) (merge {:op :ns @@ -3426,6 +3470,7 @@ core-renames (reduce (fn [m [original renamed]] (assoc m renamed (symbol "cljs.core" (str original)))) {} core-renames) + {global-uses :use global-renames :rename} (parse-global-refer-spec env args) deps (atom []) ;; as-aliases can only be used *once* because they are about the reader aliases (atom {:fns as-aliases :macros as-aliases}) @@ -3456,7 +3501,7 @@ (apply merge-with merge m (map (spec-parsers k) (remove #{:reload :reload-all} libs)))) - {} (remove (fn [[r]] (= r :refer-clojure)) args))] + {} (remove (fn [[r]] (#{:refer-clojure :refer-global} r)) args))] (set! *cljs-ns* name) (let [require-info {:as-aliases as-aliases @@ -3465,9 +3510,9 @@ :use-macros use-macros :require-macros require-macros :rename-macros rename-macros - :uses uses + :uses (merge uses global-uses) :requires requires - :renames (merge renames core-renames) + :renames (merge renames core-renames global-renames) :imports imports}] (swap! env/*compiler* update-in [::namespaces name] merge-ns-info require-info env) (merge {:op :ns* diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 8393a1a67..4ac879d47 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -12,7 +12,7 @@ defprotocol defrecord defstruct deftype delay destructure doseq dosync dotimes doto extend-protocol extend-type fn for future gen-class gen-interface if-let if-not import io! lazy-cat lazy-seq let letfn locking loop - memfn ns or proxy proxy-super pvalues refer-clojure reify sync time + memfn ns or proxy proxy-super pvalues reify sync time when when-first when-let when-not while with-bindings with-in-str with-loading-context with-local-vars with-open with-out-str with-precision with-redefs satisfies? identical? true? false? number? nil? instance? symbol? keyword? string? str get @@ -3116,6 +3116,13 @@ [& args] `(~'ns* ~(cons :refer-clojure args))) +(core/defmacro refer-global + "Refer global js vars. Supports renaming via :rename. + + (refer-global :only '[Date Symbol] :rename '{Symbol Sym})" + [& args] + `(~'ns* ~(cons :refer-global args))) + ;; INTERNAL - do not use, only for Node.js (core/defmacro load-file* [f] `(goog/nodeGlobalRequire ~f)) diff --git a/src/main/clojure/cljs/repl.cljc b/src/main/clojure/cljs/repl.cljc index b685a62e5..f6b584ba9 100644 --- a/src/main/clojure/cljs/repl.cljc +++ b/src/main/clojure/cljs/repl.cljc @@ -256,7 +256,11 @@ ([repl-env requires] (load-dependencies repl-env requires nil)) ([repl-env requires opts] - (doall (mapcat #(load-namespace repl-env % opts) (distinct requires))))) + (->> requires + distinct + (remove ana/global-ns?) + (mapcat #(load-namespace repl-env % opts)) + doall))) (defn ^File js-src->cljs-src "Map a JavaScript output file back to the original ClojureScript source @@ -652,7 +656,7 @@ (defn- wrap-fn [form] (cond (and (seq? form) - (#{'ns 'require 'require-macros + (#{'ns 'require 'require-macros 'refer-global 'use 'use-macros 'import 'refer-clojure} (first form))) identity @@ -673,7 +677,7 @@ (defn- init-wrap-fn [form] (cond (and (seq? form) - (#{'ns 'require 'require-macros + (#{'ns 'require 'require-macros 'refer-global 'use 'use-macros 'import 'refer-clojure} (first form))) identity diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index f1b639938..5d87340b5 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -387,6 +387,13 @@ :renames {map clj-map}})) (is (set? (:excludes parsed))))) +(deftest test-parse-global-refer + (let [parsed (ana/parse-global-refer-spec {} + '((:refer-global :only [Date] :rename {Symbol JSSymbol})))] + (is (= parsed + '{:use {Date js} + :rename {JSSymbol js/Symbol}})))) + (deftest test-cljs-1785-js-shadowed-by-local (let [ws (atom [])] (ana/with-warning-handlers [(collecting-warning-handler ws)] @@ -547,6 +554,14 @@ (analyze test-env '(map #(require '[clojure.set :as set]) [1 2])))))) +(deftest test-analyze-refer-global + (testing "refer-global macro expr return expected AST" + (binding [ana/*cljs-ns* ana/*cljs-ns* + ana/*cljs-warnings* nil] + (let [test-env (ana/empty-env)] + (is (= (-> (analyze test-env '(refer-global :only '[Date])) :uses vals set) + '#{js})))))) + (deftest test-gen-user-ns ;; note: can't use `with-redefs` because direct-linking is enabled (let [s "src/cljs/foo.cljs" @@ -1533,3 +1548,20 @@ (ana/gen-constant-id '+))) (is (not= (ana/gen-constant-id 'foo.bar) (ana/gen-constant-id 'foo$bar)))) + +;; ----------------------------------------------------------------------------- +;; :refer-global / :require-global ns parsing tests + +#_(deftest test-refer-global + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (let [parsed-ns (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:refer-global [Date] :rename {Date MyDate}))))] + ))) + +(comment + + (clojure.test/test-vars [#'test-refer-global]) + + ) \ No newline at end of file