Skip to content

Commit 851000f

Browse files
authored
CLJS-3346: :as-alias (#119)
Implement `:as-alias` support as simple structural pass on the ns form EDN. All `:as-alias` are collected, and the ns libspecs are returned with `:as-alias` elided. If a lib spec has *only* `:as-alias` it is *removed* entirely. This is possible since `:as-alias` does not load. The structural pass approach means further alterations to the analyzer or compiler are not needed. This aligns w/ the purpose of `:as-alias`, as it is primarily about feeding the reader. One interesting thing to note is that ClojureScript has two difference namespaces - one for defs and one for macros and they may in fact collide. Since `:as-alias` is only about the reader - collisions are not allowed across defs and macros. That is, if a `:require-macros` libspec declares an `:as-alias` it cannot be reused by a `:require` libspec. * change analyzer `ns` and `ns*` to support `:as-alias` * change ns merging to deep merge `:as-aliases` * change `form-seq` reader helper to include `:as-aliases` from the current namespace * change REPL reader to include `:as-aliases` from the current namespace
1 parent 1992641 commit 851000f

7 files changed

Lines changed: 204 additions & 7 deletions

File tree

src/main/clojure/cljs/analyzer.cljc

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
no-warn with-warning-handlers wrapping-errors]]
1515
[cljs.env.macros :refer [ensure]]))
1616
#?(:clj (:require [cljs.analyzer.impl :as impl]
17+
[cljs.analyzer.impl.namespaces :as nses]
1718
[cljs.analyzer.passes.and-or :as and-or]
1819
[cljs.env :as env :refer [ensure]]
1920
[cljs.externs :as externs]
@@ -27,6 +28,7 @@
2728
[clojure.tools.reader :as reader]
2829
[clojure.tools.reader.reader-types :as readers])
2930
:cljs (:require [cljs.analyzer.impl :as impl]
31+
[cljs.analyzer.impl.namespaces :as nses]
3032
[cljs.analyzer.passes.and-or :as and-or]
3133
[cljs.env :as env]
3234
[cljs.reader :as edn]
@@ -3101,7 +3103,7 @@
31013103
(if (pos? (count old))
31023104
(let [deep-merge-keys
31033105
[:use-macros :require-macros :rename-macros
3104-
:uses :requires :renames :imports]]
3106+
:uses :requires :renames :imports :as-aliases]]
31053107
#?(:clj
31063108
(when *check-alias-dupes*
31073109
(check-duplicate-aliases env old new)))
@@ -3141,13 +3143,15 @@
31413143
#?(:clj (rewrite-cljs-aliases
31423144
(if metadata (next args) args))
31433145
:cljs (if (some? metadata) (next args) args)))
3146+
{:keys [as-aliases] args :libspecs} (nses/elide-aliases-from-ns-specs args)
31443147
name (vary-meta name merge metadata)
31453148
{excludes :excludes core-renames :renames} (parse-ns-excludes env args)
31463149
core-renames (reduce (fn [m [original renamed]]
31473150
(assoc m renamed (symbol "cljs.core" (str original))))
31483151
{} core-renames)
31493152
deps (atom [])
3150-
aliases (atom {:fns {} :macros {}})
3153+
;; as-aliases can only be used *once* because they are about the reader
3154+
aliases (atom {:fns as-aliases :macros as-aliases})
31513155
spec-parsers {:require (partial parse-require-spec env false deps aliases)
31523156
:require-macros (partial parse-require-spec env true deps aliases)
31533157
:use (comp (partial parse-require-spec env false deps aliases)
@@ -3196,7 +3200,8 @@
31963200
spec-map)) [require-macros use-macros])])]
31973201
(set! *cljs-ns* name)
31983202
(let [ns-info
3199-
{:name name
3203+
{:as-aliases as-aliases
3204+
:name name
32003205
:doc (or docstring mdocstr)
32013206
:excludes excludes
32023207
:use-macros use-macros
@@ -3239,12 +3244,14 @@
32393244
#?(:clj (list (process-rewrite-form
32403245
specs))
32413246
:cljs (list specs)))
3247+
{:keys [as-aliases] args :libspecs} (nses/elide-aliases-from-ns-specs args)
32423248
{excludes :excludes core-renames :renames} (parse-ns-excludes env args)
32433249
core-renames (reduce (fn [m [original renamed]]
32443250
(assoc m renamed (symbol "cljs.core" (str original))))
32453251
{} core-renames)
32463252
deps (atom [])
3247-
aliases (atom {:fns {} :macros {}})
3253+
;; as-aliases can only be used *once* because they are about the reader
3254+
aliases (atom {:fns as-aliases :macros as-aliases})
32483255
spec-parsers {:require (partial parse-require-spec env false deps aliases)
32493256
:require-macros (partial parse-require-spec env true deps aliases)
32503257
:use (comp (partial parse-require-spec env false deps aliases)
@@ -3275,7 +3282,8 @@
32753282
{} (remove (fn [[r]] (= r :refer-clojure)) args))]
32763283
(set! *cljs-ns* name)
32773284
(let [require-info
3278-
{:name name
3285+
{:as-aliases as-aliases
3286+
:name name
32793287
:excludes excludes
32803288
:use-macros use-macros
32813289
:require-macros require-macros
@@ -4324,7 +4332,7 @@
43244332
reader/*data-readers* data-readers
43254333
reader/*alias-map*
43264334
(apply merge
4327-
((juxt :requires :require-macros)
4335+
((juxt :requires :require-macros :as-aliases)
43284336
(get-namespace *cljs-ns*)))
43294337
reader/resolve-symbol resolve-symbol]
43304338
(reader/read opts pbr))]
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
;; Copyright (c) Rich Hickey. All rights reserved.
2+
;; The use and distribution terms for this software are covered by the
3+
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+
;; which can be found in the file epl-v10.html at the root of this distribution.
5+
;; By using this software in any fashion, you are agreeing to be bound by
6+
;; the terms of this license.
7+
;; You must not remove this notice, or any other, from this software.
8+
9+
(ns cljs.analyzer.impl.namespaces)
10+
11+
(defn check-and-remove-as-alias
12+
"Given a libspec return a map of :as-alias alias, if was present. Return the
13+
libspec with :as-alias elided. If the libspec was *only* :as-alias do not
14+
return it."
15+
[libspec]
16+
;; ignore simple requires (symbols) and
17+
;; REPL stuff (keywords, i.e. :reload)
18+
(if (or (symbol? libspec)
19+
(keyword? libspec))
20+
{:libspec libspec}
21+
(let [[lib & spec :as libspec] libspec
22+
[pre-spec [_ alias & post-spec :as post]] (split-with (complement #{:as-alias}) spec)]
23+
(if (seq post)
24+
(let [libspec' (into [lib] (concat pre-spec post-spec))]
25+
(assert (symbol? alias)
26+
(str ":as-alias must be followed by a symbol, got: " alias))
27+
(cond-> {:as-alias {alias lib}}
28+
(> (count libspec') 1) (assoc :libspec libspec')))
29+
{:libspec libspec}))))
30+
31+
(defn check-as-alias-duplicates
32+
[as-aliases new-as-aliases]
33+
(doseq [[alias _] new-as-aliases]
34+
(assert (not (contains? as-aliases alias))
35+
(str "Duplicate :as-alias " alias ", already in use for lib "
36+
(get as-aliases alias)))))
37+
38+
(defn elide-aliases-from-libspecs
39+
"Given libspecs, elide all :as-alias. Return a map of :libspecs (filtered)
40+
and :as-aliases."
41+
([libspecs]
42+
(elide-aliases-from-libspecs libspecs {}))
43+
([libspecs as-aliases]
44+
(let [ret {:as-aliases as-aliases
45+
:libspecs []}]
46+
(reduce
47+
(fn [ret libspec]
48+
(let [{:keys [as-alias libspec]} (check-and-remove-as-alias libspec)]
49+
(check-as-alias-duplicates (:as-aliases ret) as-alias)
50+
(cond-> ret
51+
libspec (update :libspecs conj libspec)
52+
as-alias (update :as-aliases merge as-alias))))
53+
ret libspecs))))
54+
55+
(defn elide-aliases-from-ns-specs [ns-specs]
56+
"Given ns specs, elide all :as-alias. Return a map of :libspecs (filtered)
57+
and :as-aliases."
58+
(let [ret {:as-aliases {}
59+
:libspecs []}]
60+
(reduce
61+
(fn [{:keys [as-aliases] :as ret} [spec-key & libspecs]]
62+
(if-not (= :refer-clojure spec-key)
63+
(let [{:keys [as-aliases libspecs]} (elide-aliases-from-libspecs libspecs as-aliases)]
64+
(cond-> ret
65+
(not (empty? as-aliases)) (update :as-aliases merge as-aliases)
66+
(not (empty? libspecs)) (update :libspecs conj (list* spec-key libspecs))))
67+
(update ret :libspecs conj (list* spec-key libspecs))))
68+
ret ns-specs)))

src/main/clojure/cljs/repl.cljc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1146,7 +1146,7 @@
11461146
reader/*data-readers* tags/*cljs-data-readers*
11471147
reader/*alias-map*
11481148
(apply merge
1149-
((juxt :requires :require-macros)
1149+
((juxt :requires :require-macros :as-aliases)
11501150
(ana/get-namespace ana/*cljs-ns*)))]
11511151
(try
11521152
(read request-prompt request-exit)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(ns cljs-3346-as-alias.core
2+
(:require [clojure.set :as-alias set]
3+
[made.up.lib :as-alias lib]))
4+
5+
(println ::set/foo ::lib/bar)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
;; Copyright (c) Rich Hickey. All rights reserved.
2+
;; The use and distribution terms for this software are covered by the
3+
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+
;; which can be found in the file epl-v10.html at the root of this distribution.
5+
;; By using this software in any fashion, you are agreeing to be bound by
6+
;; the terms of this license.
7+
;; You must not remove this notice, or any other, from this software.
8+
9+
(ns cljs.analyzer.as-alias-test
10+
(:require [cljs.analyzer.impl.namespaces :as ana-nses]
11+
[cljs.env :as env]
12+
[clojure.test :as test :refer [deftest testing is]]))
13+
14+
;; =============================================================================
15+
16+
(deftest test-check-and-remove-as-alias
17+
(let [cenv (env/default-compiler-env)]
18+
(env/with-compiler-env cenv
19+
(testing "check-and-remove-as-alias basic tests"
20+
(is (= '{:as-alias {bar bar.core}}
21+
(ana-nses/check-and-remove-as-alias '[bar.core :as-alias bar])))
22+
(is (= '{:as-alias {bar bar.core}
23+
:libspec [bar.core :as boo]}
24+
(ana-nses/check-and-remove-as-alias '[bar.core :as-alias bar :as boo])))
25+
(is (thrown? Throwable
26+
(ana-nses/check-and-remove-as-alias '[bar.core :as-alias :bar]))))
27+
(testing "check-and-remove-as-alias should not elide simple specs"
28+
(is (= '{:libspec bar.core}
29+
(ana-nses/check-and-remove-as-alias 'bar.core)))
30+
(is (= '{:libspec [bar.core]}
31+
(ana-nses/check-and-remove-as-alias '[bar.core])))))))
32+
33+
(deftest test-elide-aliases-from-libspecs
34+
(let [cenv (env/default-compiler-env)]
35+
(env/with-compiler-env cenv
36+
(is (= '{:as-aliases {foo foo.core
37+
bar bar.core
38+
woz woz.core}
39+
:libspecs [[woz.core :as wozc]]}
40+
(ana-nses/elide-aliases-from-libspecs
41+
'([foo.core :as-alias foo]
42+
[bar.core :as-alias bar]
43+
[woz.core :as-alias woz :as wozc]))))
44+
(is (thrown? Throwable
45+
(ana-nses/elide-aliases-from-libspecs
46+
'([foo.core :as-alias foo]
47+
[bar.core :as-alias bar]
48+
[woz.core :as-alias woz :as wozc]
49+
[foo.impl :as-alias foo])))))))
50+
51+
(deftest test-elide-aliases-from-ns-specs
52+
(let [cenv (env/default-compiler-env)]
53+
(env/with-compiler-env cenv
54+
(is (= '{:as-aliases {blah blah.core, foo foo.core, bar bar.core},
55+
:libspecs [(:require-macros [[lala.core :as-lias lala :as tralala]])
56+
(:require [[woz.core :as woz]])]})
57+
(ana-nses/elide-aliases-from-ns-specs
58+
'((:require-macros [blah.core :as-alias blah]
59+
[lala.core :as-alias lala :as tralala])
60+
(:require
61+
[foo.core :as-alias foo]
62+
[bar.core :as-alias bar]
63+
[woz.core :as woz]))))
64+
(testing "Proper handling of ns-spec edgecases"
65+
(is (= '{:as-aliases {} :libspecs [(:require foo.core bar.core woz.core)]}
66+
(ana-nses/elide-aliases-from-ns-specs
67+
'((:require foo.core bar.core woz.core)))))
68+
(is (= '{:as-aliases {} :libspecs [(:require [foo.core] [bar.core] [woz.core])]}
69+
(ana-nses/elide-aliases-from-ns-specs
70+
'((:require [foo.core] [bar.core] [woz.core]))))))
71+
(testing ":refer-clojure is ignored"
72+
(is (= '{:as-aliases {}
73+
:libspecs [(:refer-clojure :exclude [first])
74+
(:require foo.core bar.core woz.core)]}
75+
(ana-nses/elide-aliases-from-ns-specs
76+
'((:refer-clojure :exclude [first])
77+
(:require foo.core bar.core woz.core))))))
78+
(testing ":reload/:reload-all is ignored"
79+
(is (= '{:as-aliases {},
80+
:libspecs [(:refer-clojure :exclude [first])
81+
(:require foo.core bar.core woz.core :reload-all)]}
82+
(ana-nses/elide-aliases-from-ns-specs
83+
'((:refer-clojure :exclude [first])
84+
(:require foo.core bar.core woz.core :reload-all)))))))))
85+
86+
(comment
87+
88+
(test/run-tests)
89+
90+
)

src/test/clojure/cljs/build_api_tests.clj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,3 +817,27 @@
817817
(.delete (io/file "package.json"))
818818
(test/delete-node-modules)
819819
(test/delete-out-files out))))
820+
821+
(deftest test-cljs-3346-as-alias
822+
(testing "Test that using :as-alias does not load the namespace, and that
823+
a namespace that does not exist on file can be used."
824+
(let [out (.getPath (io/file #_(test/tmp-dir) "cljs-3346-as-alias-out"))]
825+
(test/delete-out-files out)
826+
(test/delete-node-modules)
827+
(spit (io/file "package.json") "{}")
828+
(let [{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build"))
829+
:opts {:main 'cljs-3346-as-alias.core
830+
:output-dir out
831+
:optimizations :none
832+
:closure-warnings {:check-types :off}}}
833+
cenv (env/default-compiler-env)]
834+
(build/build (build/inputs (io/file inputs "cljs_3346_as_alias/core.cljs")) opts cenv))
835+
(let [source (slurp (io/file out "cljs_3346_as_alias/core.js"))]
836+
(is (true? (boolean (re-find #"goog.require\('cljs.core'\)" source))))
837+
(is (false? (boolean (re-find #"goog.require\('clojure.set'\)" source))))
838+
(is (false? (boolean (re-find #"goog.require\('made.up.lib'\)" source))))
839+
(is (true? (boolean (re-find #"clojure\.set\/foo" source))))
840+
(is (true? (boolean (re-find #"made\.up\.lib\/bar" source)))))
841+
(.delete (io/file "package.json"))
842+
(test/delete-node-modules)
843+
(test/delete-out-files out))))

src/test/clojure/cljs/test_runner.clj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
(ns cljs.test-runner
22
(:require [cljs.analyzer-api-tests]
3+
[cljs.analyzer.as-alias-test]
34
[cljs.analyzer-pass-tests]
45
[cljs.analyzer-tests]
56
[cljs.build-api-tests]
@@ -22,6 +23,7 @@
2223
(let [{:keys [fail error]}
2324
(run-tests
2425
'cljs.analyzer-api-tests
26+
'cljs.analyzer.as-alias-test
2527
'cljs.analyzer-pass-tests
2628
'cljs.analyzer-tests
2729
'cljs.build-api-tests

0 commit comments

Comments
 (0)