babashka/test-resources/lib_tests/rewrite_clj/parser_test.cljc
2025-02-04 20:18:08 +01:00

734 lines
32 KiB
Clojure

(ns ^{:doc "Tests for EDN parser."
:author "Yannick Scherer"}
rewrite-clj.parser-test
(:require [clojure.string :as string]
[clojure.test :refer [deftest is testing]]
[clojure.tools.reader :as rdr]
[rewrite-clj.node :as node]
[rewrite-clj.parser :as p])
#?(:clj (:import [clojure.lang ExceptionInfo]
[java.io File])))
(deftest t-parsing-the-first-few-whitespaces
(doseq [[ws parsed]
[[" " " "]
[" \n " " "]]]
(let [n (p/parse-string ws)]
(is (= :whitespace (node/tag n)))
(is (= parsed (node/string n))))))
(deftest t-parsing-whitespace-strings
(doseq [[ws children]
[[" \n " [[:whitespace " "]
[:newline "\n"]
[:whitespace " "]]]
[" \t \r\n \t " [[:whitespace " \t "]
[:newline "\n"]
[:whitespace " \t "]]]]]
(let [n (p/parse-string-all ws)]
(is (= :forms (node/tag n)))
(is (= (string/replace ws "\r\n" "\n") (node/string n)))
(is (= children (map (juxt node/tag node/string) (node/children n)))))))
#?(:clj
(deftest t-parsing-unicode-whitespace-strings
(let [ws "\u2028"
children [[:whitespace "\u2028"]]
n (p/parse-string-all ws)]
(is (= :forms (node/tag n)))
(is (= (string/replace ws "\r\n" "\n") (node/string n)))
(is (= children (map (juxt node/tag node/string) (node/children n)))))))
(deftest t-parsing-simple-data
(doseq [[s r]
[["0" 0]
["0.1" 0.1]
["12e10" 1.2e11]
["2r1100" 12]
["1N" 1N]
[":key" :key]
["\\\\" \\]
["\\a" \a]
["\\space" \space]
["\\u2202" \u2202]
["\\'" \']
[":1.5" :1.5]
[":1.5.0" :1.5.0]
[":ns/key" :ns/key]
[":key:key" :key:key]
[":x'" :x']
["sym" 'sym]
["sym#" 'sym#]
["sym'" 'sym']
["sym'sym" 'sym'sym]
["sym:sym" 'sym:sym]
["\"string\"" "string"]
["b//" 'b//]]]
(let [n (p/parse-string s)]
(is (= :token (node/tag n)))
(is (= s (node/string n)))
(is (= r (node/sexpr n))))))
(deftest t-parsing-clojure-1-12-array-class-tokens
(doseq [dimension (range 1 10)]
(let [s (str "foobar/" dimension)
n (p/parse-string s)]
(is (= :token (node/tag n)))
(is (= s (node/string n)))
(is (= (symbol "foobar" (str dimension))
(node/sexpr n))))))
(deftest t-parsing-garden-selectors
;; https://github.com/noprompt/garden
(doseq [[s expected-r]
[[":&:hover" :&:hover]
;; clj clojure reader can't parse :&::before but we can create a keyword for it
[":&::before" (keyword "&::before")]]]
(let [n (p/parse-string s)
r (node/sexpr n)]
(is (= s (node/string n)))
(is (= :token (node/tag n)))
(is (keyword? r))
(is (= expected-r r)))))
(deftest t-ratios
(let [s "3/4"
r #?(:clj 3/4
;; no ratios in cljs; they are evaluated on sexpr
:cljs 0.75)
n (p/parse-string s)]
(is (= :token (node/tag n)))
(is (= s (node/string n)))
(is (= r (node/sexpr n)))))
(deftest t-big-integers
(let [s "1234567890123456789012345678901234567890"
r 1234567890123456789012345678901234567890N
n (p/parse-string s)]
(is (= :token (node/tag n)))
(is (= s (node/string n)))
(is (= r (node/sexpr n)))))
(deftest t-parsing-symbolic-inf-values
(doseq [[s r]
[["##Inf" #?(:cljs js/Number.POSITIVE_INFINITY
:default Double/POSITIVE_INFINITY)]
["##-Inf" #?(:cljs js/Number.NEGATIVE_INFINITY
:default Double/NEGATIVE_INFINITY)]]]
(let [n (p/parse-string s)]
(is (= :token (node/tag n)))
(is (= s (node/string n)))
(is (= r (node/sexpr n))))))
(deftest t-parsing-symbolic-NaN-value
(let [n (p/parse-string "##NaN")
e (node/sexpr n)]
(is (= :token (node/tag n)))
(is (= "##NaN" (node/string n)))
#?(:cljs (is (js/Number.isNaN e))
:default (is (Double/isNaN e)))))
(deftest t-parsing-reader-prefixed-data
(doseq [[ s t ws sexpr ltag lcld]
[["@sym" :deref [] '@sym :token 'sym]
["@ sym" :deref [:whitespace] '@sym :token 'sym]
["'sym" :quote [] ''sym :token 'sym]
["' sym" :quote [:whitespace] ''sym :token 'sym]
["`sym" :syntax-quote [] ''sym :token 'sym]
["` sym" :syntax-quote [:whitespace] ''sym :token 'sym]
["~sym" :unquote [] '~sym :token 'sym]
["~ sym" :unquote [:whitespace] '~sym :token 'sym]
["~@sym" :unquote-splicing [] '~@sym :token 'sym]
["~@ sym" :unquote-splicing [:whitespace] '~@sym :token 'sym]
["~ @sym" :unquote [:whitespace] '~ @sym :deref '@sym]
["#=sym" :eval [] '(eval 'sym) :token 'sym]
["#= sym" :eval [:whitespace] '(eval 'sym) :token 'sym]
["#'sym" :var [] '#'sym :token 'sym]
["#'\nsym" :var [:newline] '#'sym :token 'sym]]]
(testing (pr-str s)
(let [n (p/parse-string s)
children (node/children n)
c (map node/tag children)]
(is (= t (node/tag n)) "tag")
(is (= ltag (last c)) "ltag")
(is (= sexpr (node/sexpr n)) "sexpr")
(is (= s (node/string n)) "string")
;; ` and #= return different sexpr's than via clojure.core/read-string
(when-not (#{:syntax-quote :eval} t)
(is (= sexpr
#?(:cljs (rdr/read-string s)
;; BB_TEST_PATCH
:default (binding [#_#_rdr/*read-eval* false] (rdr/read-string s)))
#?@(:cljs []
:default [(binding [*read-eval* false] (read-string s))]))
"read-string"))
(is (= lcld (node/sexpr (last children))) "lcld")
(is (= ws (vec (butlast c))) "ws")))))
(deftest t-eval
(let [n (p/parse-string "#=(+ 1 2)")]
(is (= :eval (node/tag n)))
(is (= "#=(+ 1 2)" (node/string n)))
(is (= '(eval '(+ 1 2)) (node/sexpr n)))))
(deftest t-uneval
(let [s "#' #_ (+ 1 2) sym"
n (p/parse-string s)
[ws0 uneval ws1 sym] (node/children n)]
(is (= :var (node/tag n)))
(is (= s (node/string n)))
(is (= :whitespace (node/tag ws0)))
(is (= :whitespace (node/tag ws1)))
(is (= :token (node/tag sym)))
(is (= 'sym (node/sexpr sym)))
(is (= :uneval (node/tag uneval)))
(is (= "#_ (+ 1 2)" (node/string uneval)))
(is (node/printable-only? uneval))
(is (thrown-with-msg? ExceptionInfo #"unsupported operation" (node/sexpr uneval)))))
(deftest t-parsing-regular-expressions
(doseq [[s expected-sexpr]
[["#\"regex\"" '(re-pattern "regex")]
["#\"regex\\.\"" '(re-pattern "regex\\.")]
["#\"[reg|k].x\"" '(re-pattern "[reg|k].x")]
["#\"a\\nb\"" '(re-pattern "a\\nb")]
["#\"a\nb\"" '(re-pattern "a\nb")]
["#\"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\""
'(re-pattern "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]]]
(let [n (p/parse-string s)]
(is (= :regex (node/tag n)))
(is (= (count s) (node/length n)))
(is (= expected-sexpr (node/sexpr n))))))
(deftest t-parsing-strings
(doseq [[s tag sexpr]
[["\"123\"" :token "123"]
["\"123\\n456\"" :token "123\n456"]
["\"123\n456\"" :multi-line "123\n456"]]]
(let [n (p/parse-string s)]
(is (= tag (node/tag n)))
(is (= s (node/string n)))
(is (= sexpr (node/sexpr n))))))
(deftest t-parsing-seqs
(doseq [[s t w c]
[["(1 2 3)" :list 2 3]
["()" :list 0 0]
["( )" :list 1 0]
["() " :list 0 0]
["[1 2 3]" :vector 2 3]
["[]" :vector 0 0]
["[ ]" :vector 1 0]
["[] " :vector 0 0]
["#{1 2 3}" :set 2 3]
["#{}" :set 0 0]
["#{ }" :set 1 0]
["#{} " :set 0 0]
["{:a 0 :b 1}" :map 3 4]
["{}" :map 0 0]
["{ }" :map 1 0]
["{} " :map 0 0]]]
(let [n (p/parse-string s)
children (node/children n)
fq (frequencies (map node/tag children))]
(is (= t (node/tag n)))
(is (= (string/trim s) (node/string n)))
(is (= (node/sexpr n) #?(:cljs (rdr/read-string s)
;; BB_TEST_PATCH
:default (binding [#_#_rdr/*read-eval* false] (rdr/read-string s)))))
(is (= w (:whitespace fq 0)))
(is (= c (:token fq 0))))))
(deftest t-parsing-invalid-maps
;; I don't know if this ability is intentional, but libraries
;; have come to rely on the behavior of parsing invalid maps.
;; Note: sexpr won't be possible on invalid Clojure
(doseq [[s t]
[["{:a}" :map]
["{:r 1 :u}" :map]]]
(let [n (p/parse-string s)]
(is (= t (node/tag n)))
(is (= s (node/string n))))))
(deftest t-parsing-metadata
(doseq [[meta-str expected-tag expected-meta-child-tag]
[["^:private" :meta :token]
["^{:private true}" :meta :map]
["#^:private" :meta* :token]
["#^{:private true}" :meta* :map]]
:let [s (str meta-str " s")
n (p/parse-string s)
[meta-data ws target-sym] (node/children n)]]
(is (= expected-tag (node/tag n)))
(is (= s (node/string n)))
(is (= 's (node/sexpr n)))
(is (= {:private true} (meta (node/sexpr n))))
(is (= expected-meta-child-tag (node/tag meta-data)))
(is (= :whitespace (node/tag ws)))
(is (= :token (node/tag target-sym)))
(is (= 's (node/sexpr target-sym)))))
(deftest t-parsing-multiple-metadata-forms
(doseq [[meta-str expected-meta-tag expected-tag-on-metadata]
[["^:private ^:awe" :meta :token]
["^{:private true} ^{:awe true}" :meta :map]
["#^:private #^:awe" :meta* :token]
["#^{:private true} #^{:awe true}" :meta* :map]]
:let [s (str meta-str " s")
n (p/parse-string s)
[meta-data ws inner-node] (node/children n)
[inner-meta-data inner-ws target-sym] (node/children inner-node)]]
;; outer metadata
(is (= expected-meta-tag (node/tag n)))
(is (= {:private true :awe true} (meta (node/sexpr n))))
(is (= expected-tag-on-metadata (node/tag meta-data)))
(is (= :whitespace (node/tag ws)))
;; inner metadata
(is (= expected-meta-tag (node/tag inner-node)))
(is (= {:awe true} (meta (node/sexpr inner-node))))
(is (= expected-tag-on-metadata (node/tag inner-meta-data)))
(is (= :whitespace (node/tag inner-ws)))
;; target symbol
(is (= s (node/string n)))
(is (= 's (node/sexpr target-sym)))))
(deftest t-parsing-tag-symbol-metadata
(doseq [[s expected-node]
[["^MyType foo" (node/meta-node [(node/token-node 'MyType)
(node/spaces 1)
(node/token-node 'foo)])]
["^{:tag MyType} foo" (node/meta-node
[(node/map-node [(node/keyword-node :tag)
(node/spaces 1)
(node/token-node 'MyType)])
(node/spaces 1)
(node/token-node 'foo)])]
["#^MyType foo" (node/raw-meta-node [(node/token-node 'MyType)
(node/spaces 1)
(node/token-node 'foo)])]
["#^{:tag MyType} foo" (node/raw-meta-node
[(node/map-node [(node/keyword-node :tag)
(node/spaces 1)
(node/token-node 'MyType)])
(node/spaces 1)
(node/token-node 'foo)])]]
:let [n (p/parse-string s)]]
(is (= expected-node n) s)
(is (= s (node/string n)))
(is (= 'foo (node/sexpr n)) s)
(is (= {:tag 'MyType} (meta (node/sexpr n))) s)))
(deftest t-parsing-tag-string-metadata
(doseq [[s expected-node]
[["^\"MyType\" foo" (node/meta-node [(node/string-node "MyType")
(node/spaces 1)
(node/token-node 'foo)])]
["^{:tag \"MyType\"} foo" (node/meta-node
[(node/map-node [(node/keyword-node :tag)
(node/spaces 1)
(node/string-node "MyType")])
(node/spaces 1)
(node/token-node 'foo)])]
["#^\"MyType\" foo" (node/raw-meta-node [(node/string-node "MyType")
(node/spaces 1)
(node/token-node 'foo)])]
["#^{:tag \"MyType\"} foo" (node/raw-meta-node
[(node/map-node [(node/keyword-node :tag)
(node/spaces 1)
(node/string-node "MyType")])
(node/spaces 1)
(node/token-node 'foo)])]]
:let [n (p/parse-string s)]]
(is (= expected-node n) s)
(is (= s (node/string n)))
(is (= 'foo (node/sexpr n)) s)
(is (= {:tag "MyType"} (meta (node/sexpr n))) s)))
(deftest t-parsing-clj-1-12-vector-metadata
(doseq [[s expected-meta expected-node]
[["^[a b c] foo"
{:param-tags '[a b c]}
(node/meta-node [(node/vector-node [(node/token-node 'a)
(node/spaces 1)
(node/token-node 'b)
(node/spaces 1)
(node/token-node 'c)])
(node/spaces 1)
(node/token-node 'foo)])]
["^[] foo"
{:param-tags []}
(node/meta-node [(node/vector-node [])
(node/spaces 1)
(node/token-node 'foo)])]
["^[_ _] foo"
{:param-tags '[_ _]}
(node/meta-node [(node/vector-node [(node/token-node '_)
(node/spaces 1)
(node/token-node '_)])
(node/spaces 1)
(node/token-node 'foo)])]
["^{:param-tags [a b c]} foo"
{:param-tags '[a b c]}
(node/meta-node
[(node/map-node [(node/keyword-node :param-tags)
(node/spaces 1)
(node/vector-node [(node/token-node 'a)
(node/spaces 1)
(node/token-node 'b)
(node/spaces 1)
(node/token-node 'c)])])
(node/spaces 1)
(node/token-node 'foo)])]
["#^[a b c] foo"
{:param-tags '[a b c]}
(node/raw-meta-node [(node/vector-node [(node/token-node 'a)
(node/spaces 1)
(node/token-node 'b)
(node/spaces 1)
(node/token-node 'c)])
(node/spaces 1)
(node/token-node 'foo)])]
["#^{:param-tags [a b c]} foo"
{:param-tags '[a b c]}
(node/raw-meta-node
[(node/map-node [(node/keyword-node :param-tags)
(node/spaces 1)
(node/vector-node [(node/token-node 'a)
(node/spaces 1)
(node/token-node 'b)
(node/spaces 1)
(node/token-node 'c)])])
(node/spaces 1)
(node/token-node 'foo)])]]
:let [n (p/parse-string s)]]
(is (= expected-node n) s)
(is (= s (node/string n)))
(is (= 'foo (node/sexpr n)) s)
(is (= expected-meta (meta (node/sexpr n))) s)))
(deftest t-parsing-invalid-metadata
(let [s "^(list not valid) foo"
n (p/parse-string s)]
(is (= (node/meta-node [(node/list-node [(node/token-node 'list)
(node/spaces 1)
(node/token-node 'not)
(node/spaces 1)
(node/token-node 'valid)])
(node/spaces 1)
(node/token-node 'foo)])
n))
(is (= s (node/string n)))
(is (thrown-with-msg? ExceptionInfo #"Metadata must be a map, keyword, symbol or string"
(node/sexpr n)))))
(deftest t-parsing-reader-macros
(doseq [[s t children]
[["#'a" :var [:token]]
["#=(+ 1 2)" :eval [:list]]
["#macro 1" :reader-macro [:token :whitespace :token]]
["#macro (* 2 3)" :reader-macro [:token :whitespace :list]]
["#?(:clj bar)" :reader-macro [:token :list]]
["#? (:clj bar)" :reader-macro [:token :whitespace :list]]
["#?@ (:clj bar)" :reader-macro [:token :whitespace :list]]
["#?foo baz" :reader-macro [:token :whitespace :token]]
["#_abc" :uneval [:token]]
["#_(+ 1 2)" :uneval [:list]]]]
(let [n (p/parse-string s)]
(is (= t (node/tag n)))
(is (= s (node/string n)))
(is (= children (map node/tag (node/children n)))))))
(deftest t-parsing-anonymous-fn
(doseq [[s t sexpr-match children]
[["#(+ % 1)"
:fn #"\(fn\* \[p1_.*#\] \(\+ p1_.*# 1\)\)"
[:token :whitespace
:token :whitespace
:token]]
["#(+ %& %2 %1)"
:fn #"\(fn\* \[p1_.*# p2_.*# & rest_.*#\] \(\+ rest_.*# p2_.*# p1_.*#\)\)"
[:token :whitespace
:token :whitespace
:token :whitespace
:token]]]]
(let [n (p/parse-string s)]
(is (= t (node/tag n)))
(is (= s (node/string n)))
(is (re-matches sexpr-match (str (node/sexpr n))))
(is (= children (map node/tag (node/children n)))))))
(deftest t-parsing-comments
(doseq [s ["; this is a comment\n"
";; this is a comment\n"
"; this is a comment"
";; this is a comment"
";"
";;"
";\n"
";;\n"
"#!shebang comment\n"
"#! this is a comment"
"#!\n"]]
(let [n (p/parse-string s)]
(is (node/printable-only? n))
(is (= :comment (node/tag n)))
(is (= s (node/string n))))))
(deftest t-parsing-auto-resolve-keywords
(doseq [[s sexpr-default sexpr-custom]
[["::key" :?_current-ns_?/key :my.current.ns/key]
["::xyz/key" :??_xyz_??/key :my.aliased.ns/key]]]
(let [n (p/parse-string s)]
(is (= :token (node/tag n)))
(is (= s (node/string n)))
(is (= sexpr-default (node/sexpr n)))
(is (= sexpr-custom (node/sexpr n {:auto-resolve #(if (= :current %)
'my.current.ns
(get {'xyz 'my.aliased.ns} % 'alias-unresolved))}))))))
(deftest t-parsing-qualified-maps
(doseq [[s sexpr]
[["#:abc{:x 1, :y 1}"
{:abc/x 1, :abc/y 1}]
["#:abc {:x 1, :y 1}"
{:abc/x 1, :abc/y 1}]
["#:abc ,,, \n\n {:x 1 :y 2}"
{:abc/x 1, :abc/y 2}]
["#:foo{:kw 1, :n/kw 2, :_/bare 3, 0 4}"
{:foo/kw 1, :n/kw 2, :bare 3, 0 4}]
["#:abc{:a {:b 1}}"
{:abc/a {:b 1}}]
["#:abc{:a #:def{:b 1}}"
{:abc/a {:def/b 1}}]]]
(let [n (p/parse-string s)]
(is (= :namespaced-map (node/tag n)))
(is (= (count s) (node/length n)))
(is (= s (node/string n)))
(is (= sexpr (node/sexpr n))))))
(deftest t-parsing-auto-resolve-current-ns-maps
(doseq [[s sexpr-default sexpr-custom]
[["#::{:x 1, :y 1}"
{:?_current-ns_?/x 1, :?_current-ns_?/y 1}
{:booya.fooya/x 1, :booya.fooya/y 1}]
["#:: {:x 1, :y 1}"
{:?_current-ns_?/x 1, :?_current-ns_?/y 1}
{:booya.fooya/x 1, :booya.fooya/y 1}]
["#:: \n,,\n,, {:x 1, :y 1}"
{:?_current-ns_?/x 1, :?_current-ns_?/y 1}
{:booya.fooya/x 1, :booya.fooya/y 1}]
["#::{:kw 1, :n/kw 2, :_/bare 3, 0 4}"
{:?_current-ns_?/kw 1, :n/kw 2, :bare 3, 0 4}
{:booya.fooya/kw 1, :n/kw 2, :bare 3, 0 4}]
["#::{:a {:b 1}}"
{:?_current-ns_?/a {:b 1}}
{:booya.fooya/a {:b 1}}]
["#::{:a #::{:b 1}}"
{:?_current-ns_?/a {:?_current-ns_?/b 1}}
{:booya.fooya/a {:booya.fooya/b 1}}]]]
(let [n (p/parse-string s)]
(is (= :namespaced-map (node/tag n)))
(is (= (count s) (node/length n)))
(is (= s (node/string n)))
(is (= sexpr-default (node/sexpr n)))
(is (= sexpr-custom (node/sexpr n {:auto-resolve #(if (= :current %)
'booya.fooya
'alias-unresolved)}))))))
(deftest parsing-auto-resolve-ns-alias-maps
(doseq [[s sexpr-default sexpr-custom]
[["#::nsalias{:x 1, :y 1}"
'{:??_nsalias_??/x 1, :??_nsalias_??/y 1}
'{:bing.bang/x 1, :bing.bang/y 1}]
["#::nsalias {:x 1, :y 1}"
'{:??_nsalias_??/x 1, :??_nsalias_??/y 1}
'{:bing.bang/x 1, :bing.bang/y 1}]
["#::nsalias ,,,,,,,,,,\n,,,,,,\n,,,,, {:x 1, :y 1}"
'{:??_nsalias_??/x 1, :??_nsalias_??/y 1}
'{:bing.bang/x 1, :bing.bang/y 1}]
["#::nsalias{:kw 1, :n/kw 2, :_/bare 3, 0 4}"
'{:??_nsalias_??/kw 1, :n/kw 2, :bare 3, 0 4}
'{:bing.bang/kw 1, :n/kw 2, :bare 3, 0 4}]
["#::nsalias{:a {:b 1}}"
'{:??_nsalias_??/a {:b 1}}
'{:bing.bang/a {:b 1}}]
["#::nsalias{:a #::nsalias2{:b 1}}"
'{:??_nsalias_??/a {:??_nsalias2_??/b 1}}
'{:bing.bang/a {:woopa.doopa/b 1}}]]]
(let [n (p/parse-string s)]
(is (= :namespaced-map (node/tag n)))
(is (= (count s) (node/length n)))
(is (= s (node/string n)))
(is (= sexpr-default (node/sexpr n)))
(is (= sexpr-custom (node/sexpr n {:auto-resolve #(if (= :current %)
'my.current.ns
(get {'nsalias 'bing.bang
'nsalias2 'woopa.doopa} % 'alias-unresolved))}))))))
(deftest t-parsing-exceptions
(doseq [[s p]
[["#" #".*Unexpected EOF.*"]
["#(" #".*Unexpected EOF.*"]
["(def" #".*Unexpected EOF.*"]
["[def" #".*Unexpected EOF.*"]
["#{def" #".*Unexpected EOF.*"]
["{:a 0" #".*Unexpected EOF.*"]
["\"abc" #".*EOF.*"]
["#\"abc" #".*Unexpected EOF.*"]
["(def x 0]" #".*Unmatched delimiter.*"]
["foobar/0" #".*Invalid symbol: foobar/0"] ;; array class dimension can be 1 to 9
["foobar/11" #".*Invalid symbol: foobar/11"] ;; array class dimension can be 1 to 9
["##wtf" #".*Invalid token: ##wtf"]
["#=" #".*:eval node expects 1 value.*"]
["#^" #".*:meta node expects 2 values.*"]
["^:private" #".*:meta node expects 2 values.*"]
["#^:private" #".*:meta node expects 2 values.*"]
["#_" #".*:uneval node expects 1 value.*"]
["#'" #".*:var node expects 1 value.*"]
["#macro" #".*:reader-macro node expects 2 values.*"]
["#:" #".*namespaced map expects a namespace*"]
["#::" #".*namespaced map expects a map*"]
["#::nsarg" #".*namespaced map expects a map*"]
["#:{:a 1}" #".*namespaced map expects a namespace*"]
["#::[a]" #".*namespaced map expects a map*"]
["#:[a]" #".*namespaced map expects a namespace*"]
["#:: token" #".*namespaced map expects a map*"]
["#::alias [a]" #".*namespaced map expects a map*"]
["#:prefix [a]" #".*namespaced map expects a map.*"]]]
(is (thrown-with-msg? ExceptionInfo p (p/parse-string s)) s)))
(deftest t-sexpr-exceptions
(doseq [s ["#_42" ;; reader ignore/discard
";; can't sexpr me!" ;; comment
" " ;; whitespace
]]
(is (thrown-with-msg? ExceptionInfo #"unsupported operation.*" (node/sexpr (p/parse-string s))))))
(deftest t-parsing-multiple-forms
(let [s "1 2 3"
n (p/parse-string-all s)
children (node/children n)]
(is (= :forms (node/tag n)))
(is (= s (node/string n)))
(is (= '(do 1 2 3) (node/sexpr n)))
(is (= [:token :whitespace
:token :whitespace
:token]
(map node/tag children))))
(let [s ";; Hi!\n(def pi 3.14)"
n (p/parse-string-all s)
children (node/children n)]
(is (= :forms (node/tag n)))
(is (= s (node/string n)))
(is (= '(def pi 3.14) (node/sexpr n)))
(is (= [:comment :list] (map node/tag children)))
(node/string (first children))))
#?(:clj
(deftest t-parsing-files
(let [f (doto (java.io.File/createTempFile "rewrite.test" "")
(.deleteOnExit))
s "âbcdé"
c ";; Hi"
o (str c "\n\n" (pr-str s))]
(spit f o)
(is (= o (slurp f)))
(let [n (p/parse-file-all f)
children (node/children n)]
(is (= :forms (node/tag n)))
(is (= o (node/string n)))
(is (= s (node/sexpr n)))
(is (= [:comment :newline :token] (map node/tag children)))
(is (= [";; Hi\n" "\n" (pr-str s)] (map node/string children)))))))
(defn- nodes-with-meta
"Create map associating row/column number pairs with the node at that position."
[n]
(let [start-pos ((juxt :row :col) (meta n))
end-pos ((juxt :end-row :end-col) (meta n))
entry {start-pos {:node n, :end-pos end-pos}}]
(if (node/inner? n)
(->> (node/children n)
(map nodes-with-meta)
(into entry))
entry)))
(deftest t-rowcolumn-metadata-from-clojure-tools-reader
;; if you update this test, please also review/update:
;; rewrite-clj.zip-test.t-rowcolumn-positions-from-position-tracking-zipper
(let [s (str
;12345678901234
"(defn f\n"
" [x]\n"
" (println x))")
positions (->> (p/parse-string-all s)
(nodes-with-meta))]
(doseq [[pos end t s sexpr]
[[[1 1] [3 15] :list s '(defn f [x] (println x))]
[[1 2] [1 6] :token "defn" 'defn]
[[1 7] [1 8] :token "f" 'f]
[[2 3] [2 6] :vector "[x]" '[x]]
[[2 4] [2 5] :token "x" 'x]
[[3 3] [3 14] :list "(println x)" '(println x)]
[[3 4] [3 11] :token "println" 'println]
[[3 12] [3 13] :token "x" 'x]]]
(let [{:keys [node end-pos]} (positions pos)]
(is (= t (node/tag node)))
(is (= s (node/string node)))
(is (= sexpr (node/sexpr node)))
(is (= end end-pos)))))
;; root node
(let [s (str
;1234567890
"(def a 1)\n"
"(def b\n"
" 2)")
n (p/parse-string-all s)
start-pos ((juxt :row :col) (meta n))
end-pos ((juxt :end-row :end-col) (meta n))]
(is (= [1 1] start-pos))
(is (= [3 5] end-pos))))
(deftest t-os-specific-line-endings
(doseq [[in expected]
[["heya\r\nplaya\r\n"
"heya\nplaya\n"]
[";; comment\r\n(+ 1 2 3)\r\n"
";; comment\n(+ 1 2 3)\n"]
["1\r2\r\n3\r\f4"
"1\n2\n3\n4"]
["\n\n\n\n"
"\n\n\n\n"]
["\r\r\r\r\r"
"\n\n\n\n\n"]
["\r\n\r\n\r\n\r\n\r\n\r\n"
"\n\n\n\n\n\n"]
[;1 2 3 4 5 6 7
"\r\n\r\r\f\r\n\r\r\n\r"
"\n\n\n\n\n\n\n"]
]]
(let [str-actual (-> in p/parse-string-all node/string)]
(is (= expected str-actual) "from string")
#?(:clj
(is (= expected (let [t-file (File/createTempFile "rewrite-clj-parse-test" ".clj")]
(.deleteOnExit t-file)
(spit t-file in)
(-> t-file p/parse-file-all node/string))) "from file")))))
(deftest t-position-in-ex-data
(let [ex (try (p/parse-string "(defn foo [)")
(catch #?(:clj Exception :cljs :default) e e))]
(is (= 1 (-> ex ex-data :row)))
(is (= 12 (-> ex ex-data :col)))))