From a0c3c41a068d06737a54422237f19f054ef5093a Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 9 Jul 2025 08:43:14 -0400 Subject: [PATCH 01/20] wip --- src/main/clojure/cljs/analyzer.cljc | 27 +++++++++++++++++++++++- src/test/clojure/cljs/analyzer_tests.clj | 17 +++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 709531e59..bb31a36db 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3039,6 +3039,24 @@ ret (recur fs ret true))))) +;; this case is like :refer-clojure it cannot be used as a spec parser +(defn parse-global-refer-spec + [env spec] + (let [{referred :refer-global + renamed :rename} (apply hash-map (concat [:refer-global] [spec])) + referred-without-renamed (remove (set (keys renamed)) referred)] + ;; TODO: refer validation, this can be lifted easily into common helper from parse-require-spec + ;; TODO: rename validation, harder to lift + {:use (zipmap referred-without-renamed (repeat 'js)) + :rename (into {} + (map (fn [[orig new-name]] + [new-name (symbol "js" (str orig))])) + renamed)})) + +#_(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 +3310,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* @@ -3335,7 +3357,10 @@ (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) + ;:refer-global (partial parse-global-refer-spec env) + ;: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 {}) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index f1b639938..71d593c57 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -1533,3 +1533,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 From dfe9c652f740ea5e7c21fe4f479b00025b9f1d1d Mon Sep 17 00:00:00 2001 From: davidnolen Date: Mon, 14 Jul 2025 22:04:05 -0400 Subject: [PATCH 02/20] - parse-global-refer-spec is more like parse-ns-excludes - basic impl test --- src/main/clojure/cljs/analyzer.cljc | 31 +++++++++++++++--------- src/test/clojure/cljs/analyzer_tests.clj | 7 ++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index bb31a36db..f01fee5b0 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3039,19 +3039,26 @@ ret (recur fs ret true))))) -;; this case is like :refer-clojure it cannot be used as a spec parser (defn parse-global-refer-spec - [env spec] - (let [{referred :refer-global - renamed :rename} (apply hash-map (concat [:refer-global] [spec])) - referred-without-renamed (remove (set (keys renamed)) referred)] - ;; TODO: refer validation, this can be lifted easily into common helper from parse-require-spec - ;; TODO: rename validation, harder to lift - {:use (zipmap referred-without-renamed (repeat 'js)) - :rename (into {} - (map (fn [[orig new-name]] - [new-name (symbol "js" (str orig))])) - renamed)})) + [env args] + (let [xs (filter #(-> % first (= :refer-global)) args)] + (if-not (= 1 (count xs)) + (throw (error env "Only one :refer-global form is allowed per namespace definition")) + (let [[_ refers & {:keys [rename] :as renames-only}] (first xs) + err-str "Only [:refer-global (names)] and optionally `:rename {from to}` specs supported"] + (when-not (and (vector? refers) (every? symbol refers)) + (throw (error env err-str))) + (when-not (or (empty? renames-only) + (and (= 1 (count renames-only)) + (contains? renames-only :rename) + (map? rename) + (every? symbol (mapcat identity rename)))) + (throw (error env (str err-str (pr-str renames-only))))) + {:use (zipmap refers (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] diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index 71d593c57..de8573b16 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 [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)] From c2487b80d052a2663c9d4d27cf4fe24f41c090dc Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 25 Jul 2025 07:01:51 -0400 Subject: [PATCH 03/20] - fix parse-global-refer-spec validation - incorporate, untested --- src/main/clojure/cljs/analyzer.cljc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index f01fee5b0..d52e6f705 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3041,9 +3041,13 @@ (defn parse-global-refer-spec [env args] - (let [xs (filter #(-> % first (= :refer-global)) args)] - (if-not (= 1 (count xs)) - (throw (error env "Only one :refer-global form is allowed per namespace definition")) + (let [xs (filter #(-> % first (= :refer-global)) args) + cnt (count xs)] + (cond + (> cnt 1) + (throw (error env (str "Only one :refer-global form is allowed per namespace definition " (count xs)))) + + (== cnt 1) (let [[_ refers & {:keys [rename] :as renames-only}] (first xs) err-str "Only [:refer-global (names)] and optionally `:rename {from to}` specs supported"] (when-not (and (vector? refers) (every? symbol refers)) @@ -3355,6 +3359,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}) @@ -3416,9 +3421,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 From c47146ae4459bc2a80386c6c862156e828e0041a Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 25 Jul 2025 08:27:12 -0400 Subject: [PATCH 04/20] - refactor validation - :refer-global :only semantically more aligned w/ Clojure --- src/main/clojure/cljs/analyzer.cljc | 22 ++++++++++++---------- src/main/clojure/cljs/core.cljc | 7 +++++++ src/test/clojure/cljs/analyzer_tests.clj | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index d52e6f705..91173b69a 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3045,20 +3045,22 @@ cnt (count xs)] (cond (> cnt 1) - (throw (error env (str "Only one :refer-global form is allowed per namespace definition " (count xs)))) + (throw (error env "Only one :refer-global form is allowed per namespace definition")) (== cnt 1) - (let [[_ refers & {:keys [rename] :as renames-only}] (first xs) - err-str "Only [:refer-global (names)] and optionally `:rename {from to}` specs supported"] - (when-not (and (vector? refers) (every? symbol refers)) + (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? renames-only) - (and (= 1 (count renames-only)) - (contains? renames-only :rename) - (map? rename) + (when-not (or (empty? rename) + (and (map? rename) (every? symbol (mapcat identity rename)))) - (throw (error env (str err-str (pr-str renames-only))))) - {:use (zipmap refers (repeat 'js)) + (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))])) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 8393a1a67..3b0b01afb 100644 --- a/src/main/clojure/cljs/core.cljc +++ b/src/main/clojure/cljs/core.cljc @@ -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/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index de8573b16..dd0e24806 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -389,7 +389,7 @@ (deftest test-parse-global-refer (let [parsed (ana/parse-global-refer-spec {} - '((:refer-global [Date] :rename {Symbol JSSymbol})))] + '((:refer-global :only [Date] :rename {Symbol JSSymbol})))] (is (= parsed '{:use {Date js} :rename {JSSymbol js/Symbol}})))) From c63f305f1a706e1e12f4e4ebaea1ddc14efe5357 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 25 Jul 2025 08:40:39 -0400 Subject: [PATCH 05/20] - works for Date in a Node REPL --- src/main/clojure/cljs/analyzer.cljc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 91173b69a..214865942 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3465,6 +3465,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}) @@ -3495,7 +3496,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 @@ -3504,9 +3505,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* From 34690cc0c1b4de7561202ae87b25eb1c44b39ac6 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 25 Jul 2025 08:53:09 -0400 Subject: [PATCH 06/20] - :refer-global is not a normal spec-parser - copy over logic from ns* --- src/main/clojure/cljs/analyzer.cljc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 214865942..649934e81 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3372,7 +3372,6 @@ :use-macros (comp (partial parse-require-spec env true deps aliases) (partial use->require env)) :import (partial parse-import-spec env deps) - ;:refer-global (partial parse-global-refer-spec env) ;:require-global #(parse-global-require-spec env deps aliases %) } valid-forms (atom #{:use :use-macros :require :require-macros :import}) @@ -3401,7 +3400,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] From 688a21656a9ef3d39a9be066df1ddf1205cbf350 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 25 Jul 2025 09:30:53 -0400 Subject: [PATCH 07/20] - refer-clojure exclude duped --- src/main/clojure/cljs/core.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc index 3b0b01afb..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 From fdb9c7a634e73c41b52cb79d159239a48fa5a9b3 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 25 Jul 2025 17:39:32 -0400 Subject: [PATCH 08/20] - add case to missing-use for global referred vars - add unit test --- src/main/clojure/cljs/analyzer.cljc | 14 ++++++++------ src/test/clojure/cljs/analyzer_tests.clj | 6 ++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 649934e81..5edef6d90 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -2821,12 +2821,14 @@ (error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))))))))))) (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 (= 'js 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)) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index dd0e24806..d68692eb9 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -554,6 +554,12 @@ (analyze test-env '(map #(require '[clojure.set :as set]) [1 2])))))) +(deftest test-analyze-refer-global + (testing "refer-global macro expr return expected AST" + (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" From 53abd2c7e40142715945d0b3092ff12cdab6477b Mon Sep 17 00:00:00 2001 From: davidnolen Date: Fri, 25 Jul 2025 17:47:13 -0400 Subject: [PATCH 09/20] - missing binding for test --- src/test/clojure/cljs/analyzer_tests.clj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index d68692eb9..5d87340b5 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -556,9 +556,11 @@ (deftest test-analyze-refer-global (testing "refer-global macro expr return expected AST" - (let [test-env (ana/empty-env)] - (is (= (-> (analyze test-env '(refer-global :only '[Date])) :uses vals set) - '#{js}))))) + (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 From dd859199462eb5e1a8e8d8d2d439a0db82e248d4 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 13:58:21 -0400 Subject: [PATCH 10/20] - fix load-dependencies so we drop global namespaces --- src/main/clojure/cljs/repl.cljc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/cljs/repl.cljc b/src/main/clojure/cljs/repl.cljc index b685a62e5..0db0ba7d6 100644 --- a/src/main/clojure/cljs/repl.cljc +++ b/src/main/clojure/cljs/repl.cljc @@ -251,12 +251,20 @@ (load-sources repl-env sources opts) sources))) +(defn global-ns? [x] + (or (= 'js x) + (= "js" (namespace x)))) + (defn- load-dependencies "Compile and load the given `requires` and return the compiled sources." ([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 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 +660,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 +681,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 From f4b6f562e87dd34514f7531ec0876971d7d37efa Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 29 Jul 2025 15:26:28 -0400 Subject: [PATCH 11/20] - move global-ns? predicate to analyzer --- src/main/clojure/cljs/analyzer.cljc | 6 +++++- src/main/clojure/cljs/repl.cljc | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 5edef6d90..4b6032ffe 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -2820,9 +2820,13 @@ (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] ;; ignore globals referred via :refer-global - (when-not (= 'js lib) + (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)) diff --git a/src/main/clojure/cljs/repl.cljc b/src/main/clojure/cljs/repl.cljc index 0db0ba7d6..f6b584ba9 100644 --- a/src/main/clojure/cljs/repl.cljc +++ b/src/main/clojure/cljs/repl.cljc @@ -251,10 +251,6 @@ (load-sources repl-env sources opts) sources))) -(defn global-ns? [x] - (or (= 'js x) - (= "js" (namespace x)))) - (defn- load-dependencies "Compile and load the given `requires` and return the compiled sources." ([repl-env requires] @@ -262,7 +258,7 @@ ([repl-env requires opts] (->> requires distinct - (remove global-ns?) + (remove ana/global-ns?) (mapcat #(load-namespace repl-env % opts)) doall))) From 3e50ebebe8db0757176052698a4e3f408dee9d76 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Sat, 4 Oct 2025 23:19:52 -0400 Subject: [PATCH 12/20] :rename symbols must first occur in :only, fix check & tests --- src/main/clojure/cljs/analyzer.cljc | 7 +++++-- src/test/clojure/cljs/analyzer_tests.clj | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 6ad5390ac..7137612fc 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3063,14 +3063,17 @@ (== 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"] + 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? 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))))) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index 5d87340b5..4e1b54c89 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -389,9 +389,9 @@ (deftest test-parse-global-refer (let [parsed (ana/parse-global-refer-spec {} - '((:refer-global :only [Date] :rename {Symbol JSSymbol})))] + '((:refer-global :only [Date Symbol] :rename {Symbol JSSymbol})))] (is (= parsed - '{:use {Date js} + '{:use {Date js Symbol js} :rename {JSSymbol js/Symbol}})))) (deftest test-cljs-1785-js-shadowed-by-local @@ -1564,4 +1564,4 @@ (clojure.test/test-vars [#'test-refer-global]) - ) \ No newline at end of file + ) From 1ce164fe3710e5bcdeec60b460771e15561846fa Mon Sep 17 00:00:00 2001 From: davidnolen Date: Mon, 6 Oct 2025 20:24:51 -0400 Subject: [PATCH 13/20] copy over basic shape of parse-global-require-spec --- src/main/clojure/cljs/analyzer.cljc | 48 +++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 7137612fc..641b6ecd2 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3083,9 +3083,53 @@ [new-name (symbol "js" (str orig))])) rename)})))) -#_(defn parse-global-require-spec +(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)) From 58ac3811a5be4b3b52c20b7160c483616056b05f Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 8 Oct 2025 21:13:37 -0400 Subject: [PATCH 14/20] global requires are not "real" namespace deps, don't add it to the list --- src/main/clojure/cljs/analyzer.cljc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 641b6ecd2..37e1319bb 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3084,9 +3084,9 @@ rename)})))) (defn parse-global-require-spec - [env deps aliases spec] + [env aliases spec] (if (or (symbol? spec) (string? spec)) - (recur env deps aliases [spec]) + (recur env aliases [spec]) (do (basic-validate-ns-spec env false spec) (let [[lib & opts] spec @@ -3117,7 +3117,6 @@ (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})}) From a96edfee71c346d934a427f08f5afc799d0b5f43 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 8 Oct 2025 21:39:25 -0400 Subject: [PATCH 15/20] need js/ qualify global libs, add parsing test --- src/main/clojure/cljs/analyzer.cljc | 15 ++++++++------- src/test/clojure/cljs/analyzer_tests.clj | 8 ++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 37e1319bb..72c4e9668 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3082,6 +3082,8 @@ (map (fn [[orig new-name]] [new-name (symbol "js" (str orig))])) rename)})))) +(defn js-lib [lib] + (symbol "js" (str lib))) (defn parse-global-require-spec [env aliases spec] @@ -3119,15 +3121,15 @@ ":refer must be followed by a sequence of symbols in :require / :require-macros")))) (merge (when (some? alias) - {rk (merge {alias lib} {lib lib})}) + {rk (merge {alias (js-lib lib)} {lib (js-lib lib)})}) (when (some? referred-without-renamed) - {uk (apply hash-map (interleave referred-without-renamed (repeat lib)))}) + {uk (apply hash-map (interleave referred-without-renamed (repeat (js-lib 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)))) + (assoc m renamed (symbol "js " (str (str lib) "." (str original))))) {} renamed)})))))) (defn parse-require-spec [env macros? deps aliases spec] @@ -3432,8 +3434,7 @@ :use-macros (comp (partial parse-require-spec env true deps aliases) (partial use->require env)) :import (partial parse-import-spec env deps) - ;:require-global #(parse-global-require-spec env deps aliases %) - } + :require-global #(parse-global-require-spec env 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 {}) @@ -3442,8 +3443,8 @@ rename-macros :rename-macros imports :import :as params} (reduce (fn [m [k & libs :as libspec]] - (when-not (#{:use :use-macros :require :require-macros :import} k) - (throw (error env (str "Only :refer-clojure, :require, :require-macros, :use, :use-macros, and :import libspecs supported. Got " libspec " instead.")))) + (when-not (#{:use :use-macros :require :require-macros :require-global :import} k) + (throw (error env (str "Only :refer-clojure, :require, :require-macros, :use, :use-macros, :require-global and :import libspecs supported. Got " libspec " instead.")))) (when-not (@valid-forms k) (throw (error env (str "Only one " k " form is allowed per namespace definition")))) (swap! valid-forms disj k) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index 4e1b54c89..c5ea636ed 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -394,6 +394,14 @@ '{:use {Date js Symbol js} :rename {JSSymbol js/Symbol}})))) +(deftest test-parse-require-global + (let [parsed (ana/parse-global-require-spec {} (atom {:fns {}}) + '[React :refer [createElement] :as react])] + (is (= parsed + '{:require {react js/React + React js/React} + :use {createElement js/React}})))) + (deftest test-cljs-1785-js-shadowed-by-local (let [ws (atom [])] (ana/with-warning-handlers [(collecting-warning-handler ws)] From 0564ab42024ddb4e8301abe8db138b2ddc37e7fc Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 8 Oct 2025 21:43:56 -0400 Subject: [PATCH 16/20] typo, update test - add :rename case --- src/main/clojure/cljs/analyzer.cljc | 2 +- src/test/clojure/cljs/analyzer_tests.clj | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 72c4e9668..1662cd736 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3129,7 +3129,7 @@ (when-not (some #{original} referred) (throw (error env (str "Renamed symbol " original " not referred")))) - (assoc m renamed (symbol "js " (str (str lib) "." (str original))))) + (assoc m renamed (symbol "js" (str (str lib) "." (str original))))) {} renamed)})))))) (defn parse-require-spec [env macros? deps aliases spec] diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index c5ea636ed..2f802c76c 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -398,9 +398,15 @@ (let [parsed (ana/parse-global-require-spec {} (atom {:fns {}}) '[React :refer [createElement] :as react])] (is (= parsed - '{:require {react js/React - React js/React} - :use {createElement js/React}})))) + '{:require {react js/React + React js/React} + :use {createElement js/React}}))) + (let [parsed (ana/parse-global-require-spec {} (atom {:fns {}}) + '[React :refer [createElement] :rename {createElement create} :as react])] + (is (= parsed + '{:require {react js/React + React js/React} + :rename {create js/React.createElement}})))) (deftest test-cljs-1785-js-shadowed-by-local (let [ws (atom [])] From a5178ca93fe505bddd4b25541ccc998ed2e2cf86 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 8 Oct 2025 21:47:54 -0400 Subject: [PATCH 17/20] uncomment test-refer-global test --- src/test/clojure/cljs/analyzer_tests.clj | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index 2f802c76c..621e5ac71 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -1566,16 +1566,11 @@ ;; ----------------------------------------------------------------------------- ;; :refer-global / :require-global ns parsing tests -#_(deftest test-refer-global +(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]) - - ) + (:refer-global :only [Date] :rename {Date MyDate}))))] + (= (:renames parsed-ns) + '{MyDate js/Date})))) From cc972ab767600f9bcc38a34f34a5e852138381a2 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 8 Oct 2025 22:02:54 -0400 Subject: [PATCH 18/20] update string check for ns spec test --- src/test/clojure/cljs/analyzer_tests.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index 621e5ac71..474bef806 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -171,7 +171,7 @@ (analyze ns-env '(ns foo.bar (:unless []))) (catch Exception e (.getMessage (.getCause e)))) - "Only :refer-clojure, :require, :require-macros, :use, :use-macros, and :import libspecs supported. Got (:unless []) instead.")) + "Only :refer-clojure, :require, :require-macros, :use, :use-macros, :require-global and :import libspecs supported. Got (:unless []) instead.")) (is (.startsWith (try (analyze ns-env '(ns foo.bar (:require baz.woz) (:require noz.goz))) From e27c1dabf95d67b6a6fbb1955c6f213384d64f2d Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 8 Oct 2025 22:04:17 -0400 Subject: [PATCH 19/20] js-lib -> global-lib, less clashing w/ existing use of js-lib --- src/main/clojure/cljs/analyzer.cljc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 1662cd736..78093a0b7 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3082,7 +3082,8 @@ (map (fn [[orig new-name]] [new-name (symbol "js" (str orig))])) rename)})))) -(defn js-lib [lib] + +(defn global-lib [lib] (symbol "js" (str lib))) (defn parse-global-require-spec @@ -3121,9 +3122,9 @@ ":refer must be followed by a sequence of symbols in :require / :require-macros")))) (merge (when (some? alias) - {rk (merge {alias (js-lib lib)} {lib (js-lib lib)})}) + {rk (merge {alias (global-lib lib)} {lib (global-lib lib)})}) (when (some? referred-without-renamed) - {uk (apply hash-map (interleave referred-without-renamed (repeat (js-lib lib))))}) + {uk (apply hash-map (interleave referred-without-renamed (repeat (global-lib lib))))}) (when (some? renamed) {renk (reduce (fn [m [original renamed]] (when-not (some #{original} referred) From 389adaccb96a42075e6e0bb9f882f811a8d6f236 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Wed, 8 Oct 2025 22:11:59 -0400 Subject: [PATCH 20/20] require-global test --- src/main/clojure/cljs/analyzer.cljc | 2 +- src/test/clojure/cljs/analyzer_tests.clj | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc index 78093a0b7..47ddea625 100644 --- a/src/main/clojure/cljs/analyzer.cljc +++ b/src/main/clojure/cljs/analyzer.cljc @@ -3436,7 +3436,7 @@ (partial use->require env)) :import (partial parse-import-spec env deps) :require-global #(parse-global-require-spec env aliases %)} - valid-forms (atom #{:use :use-macros :require :require-macros :import}) + valid-forms (atom #{:use :use-macros :require :require-macros :require-global :import}) reload (atom {:use nil :require nil :use-macros nil :require-macros nil}) reloads (atom {}) {uses :use requires :require renames :rename diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj index 474bef806..65bb31961 100644 --- a/src/test/clojure/cljs/analyzer_tests.clj +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -1574,3 +1574,15 @@ (:refer-global :only [Date] :rename {Date MyDate}))))] (= (:renames parsed-ns) '{MyDate js/Date})))) + +(deftest test-require-global + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (let [parsed-ns (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:require-global [React :as react :refer [createElement]]))))] + (is (= (:requires parsed-ns) + '{React js/React + react js/React})) + (is (= (:uses parsed-ns) + '{createElement js/React})))))