Skip to content
71 changes: 58 additions & 13 deletions src/main/clojure/cljs/analyzer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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*
Expand Down Expand Up @@ -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})
Expand All @@ -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 {})
Expand All @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -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
Expand All @@ -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*
Expand Down
9 changes: 8 additions & 1 deletion src/main/clojure/cljs/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
10 changes: 7 additions & 3 deletions src/main/clojure/cljs/repl.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
32 changes: 32 additions & 0 deletions src/test/clojure/cljs/analyzer_tests.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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])

)