Skip to content

CLJS-3233: :refer-global + :only, :require-global #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
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])

)