Skip to content

Commit 7b68d6e

Browse files
committed
sexpr on tag metadata now expands to long form
We also now throw when trying to sexpr invalid metadata. Closes #280
1 parent 6c4c6ea commit 7b68d6e

File tree

4 files changed

+124
-4
lines changed

4 files changed

+124
-4
lines changed

CHANGELOG.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ A release with known breaking changes is marked with:
1919
// (adjust these in publish.clj as you see fit)
2020
=== Unreleased
2121

22-
* bump `org.clojure/tools.reader` to version `1.4.2`
22+
* bump `org.clojure/tools.reader` to version `1.4.2` (@lread)
23+
* `sexpr` now 1) expands tag metadata to its long form 2) throws on invalid metadata
24+
https://github.com/clj-commons/rewrite-clj/issues/280[#280]
25+
(@lread)
2326
* `rewrite-clj.paredit/barf-forward` on zipper created with `:track-position? true` now correctly barfs when current node has children
2427
https://github.com/clj-commons/rewrite-clj/issues/245[#245]
28+
(@lread, thanks for the issue @p4ulcristian!)
2529

2630
=== v1.1.47 - 2023-03-25 [[v1.1.47]]
2731

doc/01-user-guide.adoc

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,48 @@ But when converting to a Clojure form, duplicate values in a set are not valid C
792792
;; => #{:b :a}
793793
----
794794

795+
[[invalid-metadata]]
796+
=== Invalid Metadata
797+
Clojure can read metadata that is any of:
798+
799+
[%autowidth]
800+
|===
801+
| Type | Example | Equivalent Long Form
802+
803+
| map
804+
a|`^{:a 1 :b 2} foo`
805+
a|`^{:a 1 :b 2} foo`
806+
807+
| keyword
808+
a| `^:private bar`
809+
a| `^{:private true} bar`
810+
811+
| symbol
812+
a| `^SomeType baz`
813+
a| `^{:tag SomeType} baz`
814+
815+
| string
816+
a|`^"SomeType" qux`
817+
a| `^{:tag "SomeType"} qux`
818+
819+
|===
820+
821+
Rewrite-clj will happily read and write metdata that is technically invalid.
822+
When you `sexpr` a metadata node you are also effectively converting it to its long form.
823+
If you try to `sexpr` a node with invalid metadata you will get an exception:
824+
825+
//#:test-doc-blocks {:reader-cond :clj}
826+
[source,clojure]
827+
----
828+
(try
829+
(-> "^(bad metadata) foobar"
830+
z/of-string
831+
z/sexpr)
832+
(catch Throwable e
833+
(.getMessage e)))
834+
;; => "Metadata must be a map, keyword, symbol or string"
835+
----
836+
795837
[#sexpr-nuances]
796838
== Sexpr Nuances
797839

@@ -863,8 +905,8 @@ Both the zip and node APIs include `sexpr-able?` to check if sexpr is supported
863905
====
864906
`sexpr-able?` only looks at the current node element type. This means that `sexpr` will still throw when:
865907
866-
1. called on a node with an element type that is `sepxr-able?` but, for whatever reason, has a child node that fails to `sexpr`, see link:#unbalanced-maps[unbalanced maps].
867-
2. called directly on an link:#unbalanced-maps[unbalanced maps].
908+
1. called on a node with an element type that is `sepxr-able?` but, for whatever reason, has a child node that fails to `sexpr`, see link:#unbalanced-maps[unbalanced maps] and link:#invalid-metadata[invalid metadata].
909+
2. called directly on an link:#unbalanced-maps[unbalanced maps] or node with link:#invalid-metadata[invalid metadata].
868910
====
869911

870912
[source, clojure]

src/rewrite_clj/node/meta.cljc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
(let [[mta data] (node/sexprs children opts)]
1717
(assert (interop/meta-available? data)
1818
(str "cannot attach metadata to: " (pr-str data)))
19-
(vary-meta data merge (if (map? mta) mta {mta true}))))
19+
(vary-meta data merge
20+
(cond (map? mta) mta
21+
(keyword? mta) {mta true}
22+
(symbol? mta) {:tag mta}
23+
(string? mta) {:tag mta}
24+
:else (throw (ex-info "Metadata must be a map, keyword, symbol or string" {}))))))
2025
(length [_node]
2126
(+ (count prefix) (node/sum-lengths children)))
2227
(string [_node]

test/rewrite_clj/parser_test.cljc

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,75 @@
270270
(is (= s (node/string n)))
271271
(is (= 's (node/sexpr target-sym)))))
272272

273+
(deftest t-parsing-tag-symbol-metadata
274+
(doseq [[s expected-node]
275+
[["^MyType foo" (node/meta-node [(node/token-node 'MyType)
276+
(node/spaces 1)
277+
(node/token-node 'foo)])]
278+
["^{:tag MyType} foo" (node/meta-node
279+
[(node/map-node [(node/keyword-node :tag)
280+
(node/spaces 1)
281+
(node/token-node 'MyType)])
282+
(node/spaces 1)
283+
(node/token-node 'foo)])]
284+
["#^MyType foo" (node/raw-meta-node [(node/token-node 'MyType)
285+
(node/spaces 1)
286+
(node/token-node 'foo)])]
287+
["#^{:tag MyType} foo" (node/raw-meta-node
288+
[(node/map-node [(node/keyword-node :tag)
289+
(node/spaces 1)
290+
(node/token-node 'MyType)])
291+
(node/spaces 1)
292+
(node/token-node 'foo)])]]
293+
294+
:let [n (p/parse-string s)]]
295+
(is (= expected-node n) s)
296+
(is (= s (node/string n)))
297+
(is (= 'foo (node/sexpr n)) s)
298+
(is (= {:tag 'MyType} (meta (node/sexpr n))) s)))
299+
300+
(deftest t-parsing-tag-string-metadata
301+
(doseq [[s expected-node]
302+
[["^\"MyType\" foo" (node/meta-node [(node/string-node "MyType")
303+
(node/spaces 1)
304+
(node/token-node 'foo)])]
305+
["^{:tag \"MyType\"} foo" (node/meta-node
306+
[(node/map-node [(node/keyword-node :tag)
307+
(node/spaces 1)
308+
(node/string-node "MyType")])
309+
(node/spaces 1)
310+
(node/token-node 'foo)])]
311+
["#^\"MyType\" foo" (node/raw-meta-node [(node/string-node "MyType")
312+
(node/spaces 1)
313+
(node/token-node 'foo)])]
314+
["#^{:tag \"MyType\"} foo" (node/raw-meta-node
315+
[(node/map-node [(node/keyword-node :tag)
316+
(node/spaces 1)
317+
(node/string-node "MyType")])
318+
(node/spaces 1)
319+
(node/token-node 'foo)])]]
320+
321+
:let [n (p/parse-string s)]]
322+
(is (= expected-node n) s)
323+
(is (= s (node/string n)))
324+
(is (= 'foo (node/sexpr n)) s)
325+
(is (= {:tag "MyType"} (meta (node/sexpr n))) s)))
326+
327+
(deftest t-parsing-invalid-metadata
328+
(let [s "^(list not valid) foo"
329+
n (p/parse-string s)]
330+
(is (= (node/meta-node [(node/list-node [(node/token-node 'list)
331+
(node/spaces 1)
332+
(node/token-node 'not)
333+
(node/spaces 1)
334+
(node/token-node 'valid)])
335+
(node/spaces 1)
336+
(node/token-node 'foo)])
337+
n))
338+
(is (= s (node/string n)))
339+
(is (thrown-with-msg? ExceptionInfo #"Metadata must be a map, keyword, symbol or string"
340+
(node/sexpr n)))))
341+
273342
(deftest t-parsing-reader-macros
274343
(are [?s ?t ?children]
275344
(let [n (p/parse-string ?s)]

0 commit comments

Comments
 (0)