Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 105 additions & 13 deletions src/main/clojure/cljs/analyzer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -2828,13 +2828,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 @@ -3047,6 +3053,84 @@
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)
only-set (set only)
err-str "Only (:refer-global :only [names]) and optionally `:rename {from to}` specs supported.
:rename symbols must be present in :only"]
(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))
(every? only-set (keys 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]
(if (or (symbol? spec) (string? spec))
(recur env deps aliases [spec])
(do
(basic-validate-ns-spec env false spec)
(let [[lib & opts] spec
{alias :as referred :refer renamed :rename
:or {alias (if (string? lib)
(symbol (munge lib))
lib)}}
(apply hash-map opts)
referred-without-renamed (seq (remove (set (keys renamed)) referred))
[rk uk renk] [:require :use :rename]]
(when-not (or (symbol? alias) (nil? alias))
(throw
(error env
(parse-ns-error-msg spec
":as must be followed by a symbol in :require / :require-macros"))))
(when (some? alias)
(let [lib' ((:fns @aliases) alias)]
(when (and (some? lib') (not= lib lib'))
(throw (error env (parse-ns-error-msg spec ":as alias must be unique"))))
(when (= alias 'js)
(when-not (= lib (get-in @aliases [:fns 'js])) ; warn only once
(warning :js-used-as-alias env {:spec spec})))
(swap! aliases update-in [:fns] conj [alias lib])))
(when-not (or (and (sequential? referred)
(every? symbol? referred))
(nil? referred))
(throw
(error env
(parse-ns-error-msg spec
":refer must be followed by a sequence of symbols in :require / :require-macros"))))
(swap! deps conj lib)
(merge
(when (some? alias)
{rk (merge {alias lib} {lib lib})})
(when (some? referred-without-renamed)
{uk (apply hash-map (interleave referred-without-renamed (repeat lib)))})
(when (some? renamed)
{renk (reduce (fn [m [original renamed]]
(when-not (some #{original} referred)
(throw (error env
(str "Renamed symbol " original " not referred"))))
(assoc m renamed (symbol (str lib) (str original))))
{} renamed)}))))))

(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 @@ -3300,6 +3384,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 @@ -3334,6 +3422,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 @@ -3343,7 +3432,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 @@ -3370,7 +3461,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 @@ -3392,9 +3483,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 @@ -3434,6 +3525,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 @@ -3464,7 +3556,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 @@ -3473,9 +3565,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 @@ -3121,6 +3121,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 Symbol] :rename {Symbol JSSymbol})))]
(is (= parsed
'{:use {Date js Symbol 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])

)