[#769] rewrite-clj support

This commit is contained in:
Michiel Borkent 2021-04-04 16:22:45 +02:00 committed by GitHub
parent 2aa8c88822
commit 1870e2729e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1938 additions and 15 deletions

View file

@ -7,6 +7,7 @@
"feature-hiccup"
"feature-test-check"
"feature-spec-alpha"
"feature-rewrite-clj"
"pods/src"
"babashka.nrepl/src"
"depstar/src" "process/src"
@ -33,7 +34,8 @@
http-kit/http-kit {:mvn/version "2.5.3"}
babashka/clojure-lanterna {:mvn/version "0.9.8-SNAPSHOT"}
org.clojure/core.match {:mvn/version "1.0.0"}
hiccup/hiccup {:mvn/version "2.0.0-alpha2"}}
hiccup/hiccup {:mvn/version "2.0.0-alpha2"}
rewrite-clj/rewrite-clj {:mvn/version "1.0.605-alpha"}}
:aliases {:babashka/dev
{:main-opts ["-m" "babashka.main"]}
:profile

View file

@ -0,0 +1,67 @@
(ns babashka.impl.rewrite-clj
{:no-doc true}
(:require [rewrite-clj.node]
[rewrite-clj.paredit]
[rewrite-clj.parser]
[rewrite-clj.zip]
[rewrite-clj.zip.subedit]
[sci.core :as sci]))
(def nns (sci/create-ns 'rewrite-clj.node nil))
(def pens (sci/create-ns 'rewrite-clj.paredit nil))
(def pns (sci/create-ns 'rewrite-clj.parser nil))
(def zns (sci/create-ns 'rewrite-clj.zip nil))
(def zsns (sci/create-ns 'rewrite-clj.zip.subedit nil))
#_(defmacro copy-var
"Copies contents from var `sym` to a new sci var. The value `ns` is an
object created with `sci.core/create-ns`."
([sym ns]
`(let [ns# ~ns
var# (var ~sym)
val# (deref var#)
m# (-> var# meta)
ns-name# (vars/getName ns#)
name# (:name m#)
name-sym# (symbol (str ns-name#) (str name#))
new-m# {:doc (:doc m#)
:name name#
:arglists (:arglists m#)
:ns ns#}]
(cond (:dynamic m#)
(new-dynamic-var name# val# new-m#)
(:macro m#)
(new-macro-var name# val# new-m#)
:else (new-var name# val# new-m#)))))
(defn make-ns [ns sci-ns]
(reduce (fn [ns-map [var-name var]]
(let [m (meta var)
no-doc (:no-doc m)
doc (:doc m)
arglists (:arglists m)]
(if no-doc ns-map
(assoc ns-map var-name
(sci/new-var (symbol var-name) @var
(cond-> {:ns sci-ns
:name (:name m)}
(:macro m) (assoc :macro true)
doc (assoc :doc doc)
arglists (assoc :arglists arglists)))))))
{}
(ns-publics ns)))
(def node-namespace
(make-ns 'rewrite-clj.node nns))
(def parser-namespace
(make-ns 'rewrite-clj.parser pns))
(def paredit-namespace
(make-ns 'rewrite-clj.paredit pens))
(def zip-namespace
(make-ns 'rewrite-clj.zip zns))
(def subedit-namespace
(make-ns 'rewrite-clj.zip.subedit zsns))

View file

@ -52,6 +52,8 @@
:dependencies [[hiccup/hiccup "2.0.0-alpha2"]]}
:feature/test-check {:source-paths ["feature-test-check"]}
:feature/spec-alpha {:source-paths ["feature-spec-alpha"]}
:feature/rewrite-clj {:source-paths ["feature-rewrite-clj"]
:dependencies [[rewrite-clj/rewrite-clj "1.0.605-alpha"]]}
:test [:feature/xml
:feature/lanterna
:feature/yaml
@ -67,11 +69,13 @@
:feature/hiccup
:feature/test-check
:feature/spec-alpha
:feature/rewrite-clj
{:dependencies [[com.clojure-goes-fast/clj-async-profiler "0.4.1"]
[com.opentable.components/otj-pg-embedded "0.13.3"]]}]
:uberjar {:global-vars {*assert* false}
:jvm-opts ["-Dclojure.compiler.direct-linking=true"
"-Dclojure.spec.skip-macros=true"]
"-Dclojure.spec.skip-macros=true"
"-Dborkdude.dynaload.aot=true"]
:main babashka.main
:aot :all}
:reflection {:main babashka.impl.classes/generate-reflection-file}}

2
sci

@ -1 +1 @@
Subproject commit 106919aad41062055828df832acb863080e4b5bd
Subproject commit 386ff4c3d4f8d0aba11947beccb8320d75c5fcf8

View file

@ -41,8 +41,7 @@ BABASHKA_BINARY=${BABASHKA_BINARY:-"bb"}
args=( "-jar" "$BABASHKA_JAR"
"-H:Name=$BABASHKA_BINARY"
"-H:+ReportExceptionStackTraces"
"-J-Dclojure.spec.skip-macros=true"
"-J-Dclojure.compiler.direct-linking=true"
"-J-Dborkdude.dynaload.aot=true"
"-H:IncludeResources=BABASHKA_VERSION"
"-H:IncludeResources=SCI_VERSION"
"-H:ReflectionConfigurationFiles=reflection.json"
@ -104,6 +103,7 @@ then
export BABASHKA_FEATURE_HICCUP="${BABASHKA_FEATURE_HICCUP:-false}"
export BABASHKA_FEATURE_TEST_CHECK="${BABASHKA_FEATURE_TEST_CHECK:-false}"
export BABASHKA_FEATURE_SPEC_ALPHA="${BABASHKA_FEATURE_SPEC_ALPHA:-false}"
export BABASHKA_FEATURE_REWRITE_CLJ="${BABASHKA_FEATURE_REWRITE_CLJ:-false}"
fi
"$GRAALVM_HOME/bin/native-image" "${args[@]}"

View file

@ -146,6 +146,13 @@ else
BABASHKA_LEIN_PROFILES+=",-feature/spec-alpha"
fi
if [ "$BABASHKA_FEATURE_REWRITE_CLJ" != "false" ]
then
BABASHKA_LEIN_PROFILES+=",+feature/rewrite-clj"
else
BABASHKA_LEIN_PROFILES+=",-feature/rewrite-clj"
fi
if [ -z "$BABASHKA_JAR" ]; then
lein with-profiles "$BABASHKA_LEIN_PROFILES,+reflection,-uberjar" do run
lein with-profiles "$BABASHKA_LEIN_PROFILES" do clean, uberjar

View file

@ -112,6 +112,13 @@ set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/spec-alpha
set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,-feature/spec-alpha
)
if not "%BABASHKA_FEATURE_REWRITE_CLJ%"=="false" (
set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/rewrite-clj
) else (
set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,-feature/rewrite-clj
)
call lein with-profiles %BABASHKA_LEIN_PROFILES% bb "(+ 1 2 3)"
call lein with-profiles %BABASHKA_LEIN_PROFILES%,+reflection,-uberjar do run

View file

@ -1,11 +1,11 @@
(ns babashka.impl.clojure.core
{:no-doc true}
(:refer-clojure :exclude [future read+string])
(:refer-clojure :exclude [future read+string clojure-version])
(:require [babashka.impl.common :as common]
[borkdude.graal.locking :as locking]
[clojure.string :as str]
[sci.core :as sci]
[sci.impl.namespaces :refer [copy-core-var]]))
[sci.impl.namespaces :refer [copy-core-var #_clojure-core-ns]]))
(defn locking* [form bindings v f & args]
(apply @#'locking/locking form bindings v f args))
@ -23,6 +23,33 @@
(def command-line-args (sci/new-dynamic-var '*command-line-args* nil))
(def warn-on-reflection (sci/new-dynamic-var '*warn-on-reflection* false))
;; (def major (:major *clojure-version*))
;; (def minor (:minor *clojure-version*))
;; (def incremental (:incremental *clojure-version*))
;; (def qualifier (str "sci" (when-let [q (:qualifier *clojure-version*)]
;; (str "+" q))))
;; (def clojure-ver {:major major
;; :minor minor
;; :incremental incremental
;; :qualifier qualifier})
;; (defn clojure-version
;; "Returns clojure version as a printable string."
;; {:added "1.0"}
;; []
;; (str major
;; "."
;; minor
;; (when-let [i incremental]
;; (str "." i))
;; (when-let [q qualifier]
;; (when (pos? (count q)) (str "-" q)))
;; (when incremental
;; "-SNAPSHOT")))
;; (def clojure-version-var (sci/new-dynamic-var '*clojure-version* clojure-ver))
(defn read+string
"Added for compatibility. Must be used with
clojure.lang.LineNumberingPushbackReader. Does not support all of
@ -63,4 +90,8 @@
'read+string (fn [& args]
(apply read+string @common/ctx args))
'*command-line-args* command-line-args
'*warn-on-reflection* warn-on-reflection})
'*warn-on-reflection* warn-on-reflection
;;'*clojure-version* clojure-version-var
;;'clojure-version (sci/copy-var clojure-version clojure-core-ns)
}
)

View file

@ -387,15 +387,26 @@
;;; UTILITIES FOR ASSERTIONS
(defn get-possibly-unbound-var
"Like var-get but returns nil if the var is unbound."
{:added "1.1"}
[v]
(try (deref v)
(catch IllegalStateException _
nil)))
(defn function?
"Returns true if argument is a function or a symbol that resolves to
a function (not a macro)."
{:added "1.1"}
[x]
(if (symbol? x) ;; TODO
(if (symbol? x)
(when-let [v (second (resolve/lookup @ctx x false))]
(when-let [value (if (vars/var? v) @v v)]
(when-let [value (if (vars/var? v)
(get-possibly-unbound-var v)
v)]
(and (fn? value)
(not (:macro (meta v)))
(not (:sci/macro (meta v))))))
(fn? x)))
@ -444,7 +455,7 @@
;; symbol in the test expression.
(defmulti assert-expr
(fn [msg form]
(fn [_msg form]
(cond
(nil? form) :always-fail
(seq? form) (first form)
@ -548,7 +559,8 @@
{:added "1.1"}
([form]
`(clojure.test/is ~form nil))
([form msg] `(clojure.test/try-expr ~msg ~form)))
([form msg]
`(clojure.test/try-expr ~msg ~form)))
(defmacro are
"Checks multiple assertions with a template expression.

View file

@ -14,6 +14,7 @@
(def core-match? (not= "false" (System/getenv "BABASHKA_FEATURE_CORE_MATCH")))
(def hiccup? (not= "false" (System/getenv "BABASHKA_FEATURE_HICCUP")))
(def test-check? (not= "false" (System/getenv "BABASHKA_FEATURE_TEST_CHECK")))
(def rewrite-clj? (not= "false" (System/getenv "BABASHKA_FEATURE_REWRITE_CLJ")))
;; excluded by default
(def jdbc? (= "true" (System/getenv "BABASHKA_FEATURE_JDBC")))
@ -66,3 +67,6 @@
(when spec-alpha?
(require '[babashka.impl.spec]))
(when rewrite-clj?
(require '[babashka.impl.rewrite-clj]))

View file

@ -191,7 +191,8 @@ When no eval opts or subcommand is provided, the implicit subcommand is repl.")
:feature/core-match %s
:feature/hiccup %s
:feature/test-check %s
:feature/spec-alpha %s}")
:feature/spec-alpha %s
:feature/rewrite-clj %s}")
version
features/core-async?
features/csv?
@ -208,7 +209,8 @@ When no eval opts or subcommand is provided, the implicit subcommand is repl.")
features/core-match?
features/hiccup?
features/test-check?
features/spec-alpha?)))
features/spec-alpha?
features/rewrite-clj?)))
(defn read-file [file]
(let [f (io/file file)]
@ -337,7 +339,17 @@ When no eval opts or subcommand is provided, the implicit subcommand is repl.")
features/spec-alpha? (-> (assoc ;; spec
'clojure.spec.alpha @(resolve 'babashka.impl.spec/spec-namespace)
'clojure.spec.gen.alpha @(resolve 'babashka.impl.spec/gen-namespace)
'clojure.spec.test.alpha @(resolve 'babashka.impl.spec/test-namespace)))))
'clojure.spec.test.alpha @(resolve 'babashka.impl.spec/test-namespace)))
features/rewrite-clj? (assoc 'rewrite-clj.node
@(resolve 'babashka.impl.rewrite-clj/node-namespace)
'rewrite-clj.paredit
@(resolve 'babashka.impl.rewrite-clj/paredit-namespace)
'rewrite-clj.parser
@(resolve 'babashka.impl.rewrite-clj/parser-namespace)
'rewrite-clj.zip
@(resolve 'babashka.impl.rewrite-clj/zip-namespace)
'rewrite-clj.zip.subedit
@(resolve 'babashka.impl.rewrite-clj/subedit-namespace))))
(def imports
'{ArithmeticException java.lang.ArithmeticException

View file

@ -189,6 +189,13 @@
(test-namespaces 'failjure.test-core)
(test-namespaces 'rewrite-clj.parser-test
'rewrite-clj.node-test
'rewrite-clj.zip-test
'rewrite-clj.paredit-test
'rewrite-clj.zip.subedit-test
'rewrite-clj.node.coercer-test)
;;;; final exit code
(let [{:keys [:test :fail :error] :as m} @status]

View file

@ -0,0 +1,192 @@
(ns rewrite-clj.node.coercer-test
(:require [clojure.test :refer [deftest testing is are]]
[rewrite-clj.node :as node]
[rewrite-clj.parser :as p]))
(deftest t-sexpr->node->sexpr-roundtrip
(testing "simple cases roundtrip"
(are [?sexpr expected-tag ]
(let [n (node/coerce ?sexpr)]
(is (node/node? n))
(is (= ?sexpr (node/sexpr n)))
(is (string? (node/string n)))
(is (= expected-tag (node/tag n)) "tag")
(is (not (meta n)))
(is (= (type ?sexpr) (type (node/sexpr n)))))
;; numbers
;; note that we do have an integer-node, but rewrite-clj never parses to it
;; so we never coerce to it either
3 :token ;;:token
3N :token ;;:token
3.14 :token ;;:token
3.14M :token ;;:token
3e14 :token ;;:token
;; ratios are not valid in cljs
#?@(:clj [3/4 :token ;;:token
]
)
;; symbol/keyword/string/...
'symbol :token ;;:symbol
'namespace/symbol :token ;;:symbol
:keyword :token ;;:keyword
:1.5.1 :token ;;:keyword
::keyword :token ;;:keyword
::1.5.1 :token ;;:keyword
:namespace/keyword :token ;;:keyword
"" :token ;;:string
"hello, over there!" :token ;;:string
"multi\nline" :multi-line ;;:string
" " :token ;;:string
"\n" :multi-line ;;:string
"\n\n" :multi-line ;;:string
"," :token ;;:string
;; seqs
[] :vector ;;:seq
[1 2 3] :vector ;;:seq
() :list ;;:seq
'() :list ;;:seq
(list 1 2 3) :list ;;:seq
#{} :set ;;:seq
#{1 2 3} :set ;;:seq
;; date
#inst "2014-11-26T00:05:23" :token ;; :token
))
(testing "multi-line string newline variants are normalized"
(let [s "hey\nyou\rover\r\nthere"
n (node/coerce s)]
(is (= "hey\nyou\nover\nthere" (node/sexpr n))))))
(deftest
t-quoted-list-reader-location-metadata-elided
(are [?sexpr expected-meta-keys]
(let [n (node/coerce ?sexpr)]
(is (node/node? n))
(is (= expected-meta-keys (node/string n)))
(is (string? (node/string n)))
(is (= ?sexpr (node/sexpr n)))
(is (not (meta n)))
(is (= (type ?sexpr) (type (node/sexpr n)))))
'(1 2 3) "(1 2 3)"
'^:other-meta (4 5 6) "^{:other-meta true} (4 5 6)"))
(deftest t-maps
(are [?sexpr]
(let [n (node/coerce ?sexpr)]
(is (node/node? n))
(is (= :map (node/tag n)))
;; (is (= :seq protocols/node-type n))
(is (string? (node/string n)))
(is (= ?sexpr (node/sexpr n)))
;; we do not restore to original map (hash-map or array-map),
;; checking if we convert to any map is sufficient
(is (map? (node/sexpr n))))
{}
{:a 1 :b 2}
(hash-map)
(hash-map :a 0 :b 1)
(array-map)
(array-map :d 4 :e 5)))
(deftest t-namespaced-maps-coerce-to-maps
(are [?sexpr]
(let [n (node/coerce ?sexpr)]
(is (node/node? n))
(is (= :map (node/tag n)))
;; (is (= :seq (protocols/node-type n)))
(is (string? (node/string n)))
(is (= ?sexpr (node/sexpr n)))
(is (map? (node/sexpr n))))
#:prefix {:a 1 :b 2}
#::{:c 3 :d 4}
#::p{:e 5 :f 6}))
(deftest t-sexpr->node->sexpr-roundtrip-for-regex
(are [?in]
(let [n (node/coerce ?in)]
(is (node/node? n))
(is (= :regex (node/tag n)))
;; (is (= :regex (protocols/node-type n)))
(is (string? (node/string n)))
#_(is (= (list 're-pattern (regex/pattern-string-for-regex ?in))
(node/sexpr n))))
#"abc"
#"a\nb\nc"
#"a\.\s*"
#"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"))
#_(deftest
^:skip-for-sci ;; sci, by design has its own var type, so skip this one for sci
t-vars
(let [n (node/coerce #'identity)]
(is (node/node? n))
(is (= :var (node/tag n)))
#_(is (= :reader (protocols/node-type n)))
(is (= '(var #?(:clj clojure.core/identity :cljs cljs.core/identity)) (node/sexpr n)))))
(deftest t-nil
(let [n (node/coerce nil)]
(is (node/node? n))
(is (= :token (node/tag n)))
#_(is (= :token (protocols/node-type n)))
(is (= nil (node/sexpr n)))
(is (= n (p/parse-string "nil")))))
(defrecord Foo-Bar [a])
#_(deftest
^:skip-for-sci ;; records have special metadata in sci, so skip this one for sci
t-records
(let [v (Foo-Bar. 0)
n (node/coerce v)]
(is (node/node? n))
;; records are represented by rewrite-clj reader macro nodes
(is (= :reader-macro (node/tag n)))
(is (= (pr-str v) (node/string n)))))
(deftest t-nodes-coerce-to-themselves
(testing "parsed nodes"
;; lean on the parser to create node structures
(are [?s ?tag #_?type]
(let [n (p/parse-string ?s)]
(is (= n (node/coerce n)))
(is (= ?tag (node/tag n)))
#_(is (= ?type (protocols/node-type n))))
";; comment" :comment ;;:comment
"#! comment" :comment ;;:comment
"#(+ 1 %)" :fn ;;:fn
":my-kw" :token ;;:keyword
"^:m1 [1 2 3]" :meta ;;:meta
"#:p1{:a 1 :b 2}" :namespaced-map ;;:namespaced-map
"'a" :quote ;;:quote
"#'var" :var ;;:reader
"#=eval" :eval ;;:reader
"@deref" :deref ;;:deref
"#mymacro 44" :reader-macro ;;:reader-macro
"#\"regex\"" :regex ;;:regex
"[1 2 3]" :vector ;;:seq
"42" :token ;;:token
"sym" :token ;;:symbol
"#_ 99" :uneval ;;:uneval
" " :whitespace ;;:whitespace
"," :comma ;;:comma
"\n" :newline ;;:newline
))
(testing "parsed forms nodes"
(let [n (p/parse-string-all "(def a 1)")]
(is (= n (node/coerce n)))
(is (= :forms (node/tag n)))))
(testing "map qualifier node"
(let [n (node/map-qualifier-node false "prefix")]
(is (= n (node/coerce n)))))
(testing "nodes that are not parsed, but can be created manually"
(let [n (node/integer-node 10)]
(is (= n (node/coerce n))))
(let [n (node/string-node "my-string")]
(is (= n (node/coerce n))))))

View file

@ -0,0 +1,212 @@
(ns rewrite-clj.node-test
"This test namespace originated from rewrite-cljs."
(:require [clojure.test :refer [deftest is are testing]]
[rewrite-clj.node :as n]
;; [rewrite-clj.node.protocols :as proto]
[rewrite-clj.parser :as p]))
(deftest nodes-convert-to-strings-and-sexpr-ability
(testing "easily parseable"
(are [?in ?expected-tag #_#_?expected-type ?expected-sexpr-able?]
(let [n (p/parse-string ?in)]
(is (= ?in (str n)))
(is (= ?expected-tag (n/tag n)))
#_(is (= ?expected-type (proto/node-type n)))
#_(is (= ?expected-sexpr-able? (n/sexpr-able? n))))
"," :comma ;;:comma false
"; comment" :comment ;;:comment false
"#! comment" :comment ;;:comment false
"@deref" :deref ;;:deref true
"#(fn %1)" :fn ;;:fn true
":my-kw" :token ;;:keyword true
"^:meta b" :meta ;;:meta true
"#:prefix {:a 1}" :namespaced-map ;;:namespaced-map true
"\n" :newline ;;:newline false
"'quoted" :quote ;;:quote true
"#booya 32" :reader-macro ;;:reader-macro true
"#'myvar" :var ;;:reader true
"#\"regex\"" :regex ;;:regex true
"[1 2 3]" :vector ;;:seq true
"\"string\"" :token ;;:string true
"symbol" :token ;;:symbol true
"43" :token ;;:token true
"#_ nope" :uneval ;;:uneval false
" " :whitespace ;;:whitespace false
)
)
(testing "map qualifier"
(are [?auto-resolved ?prefix ?expected-str]
(let [n (n/map-qualifier-node ?auto-resolved ?prefix)]
(is (= ?expected-str (str n)))
(is (= :map-qualifier (n/tag n)))
#_(is (= :map-qualifier (proto/node-type n)))
(is (= true (n/sexpr-able? n))))
false "prefix" ":prefix"
true nil "::"
true "nsalias" "::nsalias"))
(testing "integer"
(let [n (n/integer-node 42)]
(is (= :token (n/tag n)))
#_(is (= :int (proto/node-type n)))
(is (= "42" (str n)))
(is (n/sexpr-able? n))))
(testing "forms node"
(let [n (p/parse-string-all "5 7")]
(is (= "5 7" (str n)))
(is (n/sexpr-able? n)))))
(deftest namespaced-keyword
(is (= ":dill/dall"
(n/string (n/keyword-node :dill/dall)))))
(deftest funky-keywords
(is (= ":%dummy.*"
(n/string (n/keyword-node :%dummy.*)))))
(deftest regex-node
(let [sample "(re-find #\"(?i)RUN\" s)"
sample2 "(re-find #\"(?m)^rss\\s+(\\d+)$\")"
sample3 "(->> (str/split container-name #\"/\"))"]
(is (= sample (-> sample p/parse-string n/string)))
(is (= sample2 (-> sample2 p/parse-string n/string)))
(is (= sample3 (-> sample3 p/parse-string n/string)))))
(deftest regex-with-newlines
(let [sample "(re-find #\"Hello
\\nJalla\")"]
(is (= sample (-> sample p/parse-string n/string)))))
(deftest reader-conditionals
(testing "Simple reader conditional"
(let [sample "#?(:clj bar)"
res (p/parse-string sample)]
(is (= sample (n/string res)))
(is (= :reader-macro (n/tag res)))
(is (= [:token :list] (map n/tag (n/children res))))))
(testing "Reader conditional with space before list"
(let [sample "#? (:clj bar)"
sample2 "#?@ (:clj bar)"]
(is (= sample (-> sample p/parse-string n/string)))
(is (= sample2 (-> sample2 p/parse-string n/string)))))
(testing "Reader conditional with splice"
(let [sample
"(:require [clojure.string :as s]
#?@(:clj [[clj-time.format :as tf]
[clj-time.coerce :as tc]]
:cljs [[cljs-time.coerce :as tc]
[cljs-time.format :as tf]]))"
res (p/parse-string sample)]
(is (= sample (n/string res))))))
(deftest t-node?
(is (not (n/node? nil)))
(is (not (n/node? 42)))
(is (not (n/node? "just a string")))
(is (not (n/node? {:a 1})))
(is (not (n/node? (first {:a 1}))))
(is (n/node? (n/list-node (list 1 2 3))))
(is (n/node? (n/string-node "123"))))
(deftest t-sexpr-on-map-qualifiable-nodes
(let [opts {:auto-resolve (fn [alias]
(if (= :current alias)
'my.current.ns
(get {'my-alias 'my.aliased.ns
'nsmap-alias 'nsmap.aliased.ns}
alias
(symbol (str alias "-unresolved")))))}
sexpr-default n/sexpr
sexpr-custom #(n/sexpr % opts)
map-qualifier (n/map-qualifier-node false "nsmap-prefix")
map-qualifier-current-ns (n/map-qualifier-node true nil)
map-qualifier-ns-alias (n/map-qualifier-node true "nsmap-alias")]
(testing "qualified nodes are unaffected by resolver"
(are [?result ?node]
(do
(is (= ?result (-> ?node sexpr-default)))
(is (= ?result (-> ?node sexpr-custom))))
:my-kw (n/keyword-node :my-kw)
'my-sym (n/token-node 'my-sym)
:_/my-kw (n/keyword-node :_/my-kw)
'_/my-sym (n/token-node '_/my-sym)
:my-prefix/my-kw (n/keyword-node :my-prefix/my-kw)
'my-prefix/my-sym (n/token-node 'my-prefix/my-sym)))
(testing "auto-resolve qualified key nodes are affected by resolver"
(are [?result-default ?result-custom ?node]
(do
(is (= ?result-default (-> ?node sexpr-default)))
(is (= ?result-custom (-> ?node sexpr-custom))))
:?_current-ns_?/my-kw :my.current.ns/my-kw (n/keyword-node :my-kw true)
:??_my-alias_??/my-kw :my.aliased.ns/my-kw (n/keyword-node :my-alias/my-kw true)))
(testing "map qualified nodes can be affected by resolver"
(are [?result ?node]
(do
(is (= ?result (-> ?node (n/map-context-apply map-qualifier) sexpr-default)))
(is (= ?result (-> ?node (n/map-context-apply map-qualifier) sexpr-custom))) )
:nsmap-prefix/my-kw (n/keyword-node :my-kw)
'nsmap-prefix/my-sym (n/token-node 'my-sym)))
(testing "map qualified auto-resolve current-ns nodes can be affected by resolver"
(are [?result-default ?result-custom ?node]
(do
(is (= ?result-default (-> ?node (n/map-context-apply map-qualifier-current-ns) sexpr-default)))
(is (= ?result-custom (-> ?node (n/map-context-apply map-qualifier-current-ns) sexpr-custom))))
:?_current-ns_?/my-kw :my.current.ns/my-kw (n/keyword-node :my-kw)
'?_current-ns_?/my-sym 'my.current.ns/my-sym (n/token-node 'my-sym)))
(testing "map qualified auto-resolve ns-alias nodes can be affected by resolver"
(are [?result-default ?result-custom ?node]
(do
(is (= ?result-default (-> ?node (n/map-context-apply map-qualifier-ns-alias) sexpr-default)))
(is (= ?result-custom (-> ?node (n/map-context-apply map-qualifier-ns-alias) sexpr-custom))))
:??_nsmap-alias_??/my-kw :nsmap.aliased.ns/my-kw (n/keyword-node :my-kw)
'??_nsmap-alias_??/my-sym 'nsmap.aliased.ns/my-sym (n/token-node 'my-sym)))
(testing "map qualified nodes that are unaffected by resolver"
(are [?result ?node]
(do
(is (= ?result (-> ?node (n/map-context-apply map-qualifier) sexpr-default)))
(is (= ?result (-> ?node (n/map-context-apply map-qualifier) sexpr-custom)))
(is (= ?result (-> ?node (n/map-context-apply map-qualifier-current-ns) sexpr-default)))
(is (= ?result (-> ?node (n/map-context-apply map-qualifier-current-ns) sexpr-custom)))
(is (= ?result (-> ?node (n/map-context-apply map-qualifier-ns-alias) sexpr-default)))
(is (= ?result (-> ?node (n/map-context-apply map-qualifier-ns-alias) sexpr-custom))) )
:my-kw (n/keyword-node :_/my-kw)
'my-sym (n/token-node '_/my-sym)
:my-prefix/my-kw (n/keyword-node :my-prefix/my-kw)
'my-prefix/my-sym (n/token-node 'my-prefix/my-sym)))
(testing "when auto-resolver returns nil, bare or already qualified kw is returned"
(let [opts {:auto-resolve (fn [_alias])}]
(is (= :my-kw (-> (n/keyword-node :my-kw true) (n/sexpr opts))))
(is (= :my-kw (-> (n/keyword-node :my-alias/my-kw true) (n/sexpr opts))))
(is (= :my-kw
(-> (n/keyword-node :foo/my-kw true)
(assoc :map-qualifier {:auto-resolved? true :prefix "nsmap-alias"})
(n/sexpr opts))))
(is (= :foo/my-kw
(-> (n/keyword-node :foo/my-kw false)
(assoc :map-qualifier {:auto-resolved? true :prefix "nsmap-alias"})
(n/sexpr opts)))) ))))
(deftest t-sexpr-on-map-qualifier-node
(testing "with default auto-resolve"
(let [default-mqn-sexpr (fn [s] (-> s p/parse-string n/children first n/sexpr))]
(is (= 'prefix (default-mqn-sexpr "#:prefix {:a 1 :b 2}")))
(is (= '?_current-ns_? (default-mqn-sexpr "#:: {:a 1 :b 2}")))
(is (= '??_my-ns-alias_?? (default-mqn-sexpr "#::my-ns-alias {:a 1 :b 2}")))))
(testing "with custom auto-resolve"
(let [opts {:auto-resolve (fn [alias]
(if (= :current alias)
'my.current.ns
(get {'my-alias 'my.aliased.ns
'nsmap-alias 'nsmap.aliased.ns}
alias
(symbol (str alias "-unresolved")))))}
custom-mqn-sexpr (fn [s] (-> s p/parse-string n/children first (n/sexpr opts)))]
(is (= 'prefix (custom-mqn-sexpr "#:prefix {:a 1 :b 2}")))
(is (= 'my.current.ns (custom-mqn-sexpr "#:: {:a 1 :b 2}")))
(is (= 'my.aliased.ns (custom-mqn-sexpr "#::my-alias {:a 1 :b 2}")))
(is (= 'my-alias-nope-unresolved (custom-mqn-sexpr "#::my-alias-nope {:a 1 :b 2}"))))))

View file

@ -0,0 +1,556 @@
(ns rewrite-clj.paredit-test
(:require [clojure.test :refer [deftest is]]
[rewrite-clj.paredit :as pe]
[rewrite-clj.zip :as z]))
;; helper
(defn move-n [loc f n]
(->> loc (iterate f) (take n) last))
(deftest kill-to-end-of-sexpr
(let [res (-> "[1 2 3 4]"
z/of-string
z/down z/right*
pe/kill)]
(is (= "[1]" (-> res z/root-string)))
(is (= "1" (-> res z/string)))))
(deftest kill-to-end-of-line
(let [res (-> "[1 2] ; useless comment"
z/of-string
z/right*
pe/kill)]
(is (= "[1 2]" (-> res z/root-string)))
(is (= "[1 2]" (-> res z/string)))))
(deftest kill-to-wipe-all-sexpr-contents
(let [res (-> "[1 2 3 4]"
z/of-string
z/down
pe/kill)]
(is (= "[]" (-> res z/root-string)))
(is (= "[]" (-> res z/string)))))
(deftest kill-to-wipe-all-sexpr-contents-in-nested-seq
(let [res (-> "[[1 2 3 4]]"
z/of-string
z/down
pe/kill)]
(is (= "[]" (-> res z/root-string)))
(is (= "[]" (-> res z/string)))))
(deftest kill-when-left-is-sexpr
(let [res (-> "[1 2 3 4] 2"
z/of-string
z/right*
pe/kill)]
(is (= "[1 2 3 4]" (-> res z/root-string)))
(is (= "[1 2 3 4]" (-> res z/string)))))
(deftest kill-it-all
(let [res (-> "[1 2 3 4] 5"
z/of-string
pe/kill)]
(is (= "" (-> res z/root-string)))
(is (= "" (-> res z/string)))))
(deftest kill-at-pos-when-in-empty-seq
(let [res (-> "[] 5"
(z/of-string {:track-position? true})
(pe/kill-at-pos {:row 1 :col 2}))]
(is (= "5" (-> res z/root-string)))
(is (= "5" (-> res z/string)))))
(deftest kill-inside-comment
(is (= "; dill" (-> "; dilldall"
(z/of-string {:track-position? true})
(pe/kill-at-pos {:row 1 :col 7})
z/root-string))))
(deftest kill-at-pos-when-string
(let [res (-> "(str \"Hello \" \"World!\")"
(z/of-string {:track-position? true})
z/down
(pe/kill-at-pos {:row 1 :col 9}))]
(is (= "(str \"He\" \"World!\")" (-> res z/root-string)))))
(deftest kill-at-pos-when-string-multiline
(let [sample "(str \"
First line
Second Line
Third Line
\")"
expected "(str \"
First line
Second\")"
res (-> (z/of-string sample {:track-position? true})
z/down
(pe/kill-at-pos {:row 3 :col 9}))]
(is (= expected (-> res z/root-string)))))
(deftest kill-at-pos-multiline-aligned
(let [sample "
(println \"Hello
There
World\")"]
(is (= "\n(println \"Hello\")" (-> (z/of-string sample {:track-position? true})
(pe/kill-at-pos {:row 2 :col 16})
(z/root-string))))))
(deftest kill-at-pos-when-empty-string
(is (= "" (-> (z/of-string "\"\"" {:track-position? true})
(pe/kill-at-pos {:row 1 :col 1}) z/root-string))))
(deftest kill-one-at-pos
(let [sample "[10 20 30]" ]
(is (= "[10 30]"
(-> (z/of-string sample {:track-position? true})
(pe/kill-one-at-pos {:row 1 :col 4}) ; at whitespace
z/root-string)))
(is (= "[10 30]"
(-> (z/of-string sample {:track-position? true})
(pe/kill-one-at-pos {:row 1 :col 5})
z/root-string)))))
(deftest kill-one-at-pos-new-zloc-is-left-node
(let [sample "[[10] 20 30]"]
(is (= "[10]"
(-> (z/of-string sample {:track-position? true})
(pe/kill-one-at-pos {:row 1 :col 6})
z/string)))
(is (= "[10]"
(-> (z/of-string sample {:track-position? true})
(pe/kill-one-at-pos {:row 1 :col 7})
z/string)))))
(deftest kill-one-at-pos-keep-linebreaks
(let [sample (z/of-string "[10\n 20\n 30]" {:track-position? true})]
(is (= "[20\n 30]"
(-> sample (pe/kill-one-at-pos {:row 1 :col 2}) z/root-string)))
(is (= "[10\n 30]"
(-> sample (pe/kill-one-at-pos {:row 2 :col 1}) z/root-string)))
(is (= "[10\n 20]"
(-> sample (pe/kill-one-at-pos {:row 3 :col 1}) z/root-string)))))
(deftest kill-one-at-pos-in-comment
(let [sample (z/of-string "; hello world" {:track-position? true})]
(is (= "; hello "
(-> (pe/kill-one-at-pos sample {:row 1 :col 8}) z/root-string)))
(is (= "; hello "
(-> (pe/kill-one-at-pos sample {:row 1 :col 9}) z/root-string)))
(is (= "; hello "
(-> (pe/kill-one-at-pos sample {:row 1 :col 13}) z/root-string)))
(is (= "; world"
(-> (pe/kill-one-at-pos sample {:row 1 :col 2}) z/root-string)))))
(deftest kill-one-at-pos-in-string
(let [sample (z/of-string "\"hello world\"" {:track-position? true})]
(is (= "\"hello \""
(-> (pe/kill-one-at-pos sample {:row 1 :col 7}) z/root-string)))
(is (= "\"hello \""
(-> (pe/kill-one-at-pos sample {:row 1 :col 8}) z/root-string)))
(is (= "\"hello \""
(-> (pe/kill-one-at-pos sample {:row 1 :col 12}) z/root-string)))
(is (= "\" world\""
(-> (pe/kill-one-at-pos sample {:row 1 :col 2}) z/root-string)))))
(deftest kill-one-at-pos-in-multiline-string
(let [sample (z/of-string "\"foo bar do\n lorem\"" {:track-position? true})]
(is (= "\" bar do\n lorem\""
(-> (pe/kill-one-at-pos sample {:row 1 :col 2}) z/root-string)))
(is (= "\"foo bar do\n \""
(-> (pe/kill-one-at-pos sample {:row 2 :col 1}) z/root-string)))
(is (= "\"foo bar \n lorem\""
(-> (pe/kill-one-at-pos sample {:row 1 :col 10}) z/root-string)))))
(deftest slurp-forward-and-keep-loc-rightmost
(let [res (-> "[[1 2] 3 4]"
z/of-string
z/down z/down z/right
pe/slurp-forward)]
(is (= "[[1 2 3] 4]" (-> res z/root-string)))
(is (= "2" (-> res z/string)))))
(deftest slurp-forward-and-keep-loc-leftmost
(let [res (-> "[[1 2] 3 4]"
z/of-string
z/down z/down
pe/slurp-forward)]
(is (= "[[1 2 3] 4]" (-> res z/root-string)))
(is (= "1" (-> res z/string)))))
(deftest slurp-forward-from-empty-sexpr
(let [res (-> "[[] 1 2 3]"
z/of-string
z/down
pe/slurp-forward)]
(is (= "[[1] 2 3]" (-> res z/root-string)))
(is (= "1" (-> res z/string)))))
(deftest slurp-forward-from-whitespace-node
(let [res (-> "[[1 2] 3 4]"
z/of-string
z/down z/down z/right*
pe/slurp-forward)]
(is (= "[[1 2 3] 4]" (-> res z/root-string)))
(is (= " " (-> res z/string)))))
(deftest slurp-forward-nested
(let [res (-> "[[[1 2]] 3 4]"
z/of-string
z/down z/down z/down
pe/slurp-forward)]
(is (= "[[[1 2] 3] 4]" (-> res z/root-string)))
(is (= "1" (-> res z/string)))))
(deftest slurp-forward-nested-silly
(let [res (-> "[[[[[1 2]]]] 3 4]"
z/of-string
z/down z/down z/down z/down z/down
pe/slurp-forward)]
(is (= "[[[[[1 2]]] 3] 4]" (-> res z/root-string)))
(is (= "1" (-> res z/string)))))
(deftest slurp-forward-when-last-is-sexpr
(let [res (-> "[1 [2 [3 4]] 5]"
z/of-string
z/down z/right z/down ;at 2
pe/slurp-forward)]
(is (= "[1 [2 [3 4] 5]]" (-> res z/root-string))
(= "2" (-> res z/string)))))
(deftest slurp-forward-keep-linebreak
(let [sample "
(let [dill]
{:a 1}
{:b 2})"
expected "\n(let [dill \n{:a 1}]\n {:b 2})"]
(is (= expected (-> sample
z/of-string
z/down z/right z/down
pe/slurp-forward
z/root-string)))))
(deftest slurp-forward-fully
(is (= "[1 [2 3 4]]" (-> (z/of-string "[1 [2] 3 4]")
z/down z/right z/down
pe/slurp-forward-fully
z/root-string))))
(deftest slurp-backward-and-keep-loc-leftmost
(let [res (-> "[1 2 [3 4]]"
z/of-string
z/down z/rightmost z/down
pe/slurp-backward)]
(is (= "[1 [2 3 4]]" (-> res z/root-string)))
(is (= "3" (-> res z/string)))))
(deftest slurp-backward-and-keep-loc-rightmost
(let [res (-> "[1 2 [3 4]]"
z/of-string
z/down z/rightmost z/down z/rightmost
pe/slurp-backward)]
(is (= "[1 [2 3 4]]" (-> res z/root-string)))
(is (= "4" (-> res z/string)))))
(deftest slurp-backward-from-empty-sexpr
(let [res (-> "[1 2 3 4 []]"
z/of-string
z/down z/rightmost
pe/slurp-backward)]
(is (= "[1 2 3 [4]]" (-> res z/root-string)))
(is (= "4" (-> res z/string)))))
(deftest slurp-backward-nested
(let [res (-> "[1 2 [[3 4]]]"
z/of-string
z/down z/rightmost z/down z/down z/rightmost
pe/slurp-backward)]
(is (= "[1 [2 [3 4]]]" (-> res z/root-string)))
(is (= "4" (-> res z/string)))))
(deftest slurp-backward-nested-silly
(let [res (-> "[1 2 [[[3 4]]]]"
z/of-string
z/down z/rightmost z/down z/down z/down z/rightmost
pe/slurp-backward)]
(is (= "[1 [2 [[3 4]]]]" (-> res z/root-string)))
(is (= "4" (-> res z/string)))))
(deftest slurp-backward-keep-linebreaks-and-comments
(let [res (-> "[1 2 ;dill\n [3 4]]"
z/of-string
z/down z/rightmost z/down
pe/slurp-backward)]
(is (= "[1 [2 ;dill\n 3 4]]" (-> res z/root-string)))))
(deftest slurp-backward-fully
(is (= "[[1 2 3 4] 5]" (-> (z/of-string "[1 2 3 [4] 5]")
z/down z/rightmost z/left z/down
pe/slurp-backward-fully
z/root-string))))
(deftest barf-forward-and-keep-loc
(let [res (-> "[[1 2 3] 4]"
z/of-string
z/down z/down z/right; position at 2
pe/barf-forward)]
(is (= "[[1 2] 3 4]" (-> res z/root-string)))
(is (= "2" (-> res z/string)))))
(deftest barf-forward-at-leftmost
(let [res (-> "[[1 2 3] 4]"
z/of-string
z/down z/down
pe/barf-forward)]
(is (= "[[1 2] 3 4]" (-> res z/root-string)))
(is (= "1" (-> res z/string)))))
(deftest barf-forward-at-rightmost-moves-out-of-sexrp
(let [res (-> "[[1 2 3] 4]"
z/of-string
z/down z/down z/rightmost; position at 3
pe/barf-forward)]
(is (= "[[1 2] 3 4]" (-> res z/root-string)))
(is (= "3" (-> res z/string)))))
(deftest barf-forward-at-rightmost-which-is-a-whitespace-haha
(let [res (-> "[[1 2 3 ] 4]"
z/of-string
z/down z/down z/rightmost*; position at space at the end
pe/barf-forward)]
(is (= "[[1 2] 3 4]" (-> res z/root-string)))
(is (= "3" (-> res z/string)))))
(deftest barf-forward-at-when-only-one
(let [res (-> "[[1] 2]"
z/of-string
z/down z/down
pe/barf-forward)]
(is (= "[[] 1 2]" (-> res z/root-string)))
(is (= "1" (-> res z/string)))))
(deftest barf-backward-and-keep-current-loc
(let [res (-> "[1 [2 3 4]]"
z/of-string
z/down z/rightmost z/down z/rightmost ; position at 4
pe/barf-backward)]
(is (= "[1 2 [3 4]]" (-> res z/root-string)))
(is (= "4" (-> res z/string)))))
(deftest barf-backward-at-leftmost-moves-out-of-sexpr
(let [res (-> "[1 [2 3 4]]"
z/of-string
z/down z/rightmost z/down ; position at 2
pe/barf-backward)]
(is (= "[1 2 [3 4]]" (-> res z/root-string)))
(is (= "2" (-> res z/string)))))
(deftest wrap-around
(is (= "(1)" (-> (z/of-string "1") (pe/wrap-around :list) z/root-string)))
(is (= "[1]" (-> (z/of-string "1") (pe/wrap-around :vector) z/root-string)))
(is (= "{1}" (-> (z/of-string "1") (pe/wrap-around :map) z/root-string)))
(is (= "#{1}" (-> (z/of-string "1") (pe/wrap-around :set) z/root-string))))
(deftest wrap-around-keeps-loc
(let [res (-> "1"
z/of-string
(pe/wrap-around :list))]
(is (= "1" (-> res z/string)))))
(deftest wrap-around-keeps-newlines
(is (= "[[1]\n 2]" (-> (z/of-string "[1\n 2]") z/down (pe/wrap-around :vector) z/root-string))))
(deftest wrap-around-fn
(is (= "(-> (#(+ 1 1)))" (-> (z/of-string "(-> #(+ 1 1))")
z/down z/right
(pe/wrap-around :list)
z/root-string))))
(deftest wrap-fully-forward-slurp
(is (= "[1 [2 3 4]]"
(-> (z/of-string "[1 2 3 4]")
z/down z/right
(pe/wrap-fully-forward-slurp :vector)
z/root-string))))
(deftest splice-killing-backward []
(let [res (-> (z/of-string "(foo (let ((x 5)) (sqrt n)) bar)")
z/down z/right z/down z/right z/right
pe/splice-killing-backward)]
(is (= "(foo (sqrt n) bar)" (z/root-string res)))
(is (= "(sqrt n)" (z/string res)))))
(deftest splice-killing-forward []
(let [res (-> (z/of-string "(a (b c d e) f)")
z/down z/right z/down z/right z/right
pe/splice-killing-forward)]
(is (= "(a b c f)" (z/root-string res)))
(is (= "c" (z/string res)))))
(deftest splice-killing-forward-at-leftmost []
(let [res (-> (z/of-string "(a (b c d e) f)")
z/down z/right z/down
pe/splice-killing-forward)]
(is (= "(a f)" (z/root-string res)))
(is (= "a" (z/string res)))))
(deftest split
(let [res (-> "[1 2]"
z/of-string
z/down
pe/split)]
(is (= "[1] [2]" (-> res z/root-string)))
(is (= "1" (-> res z/string)))))
(deftest split-includes-node-at-loc-as-left
(let [res (-> "[1 2 3 4]"
z/of-string
z/down z/right
pe/split)]
(is (= "[1 2] [3 4]" (-> res z/root-string)))
(is (= "2" (-> res z/string)))))
(deftest split-at-whitespace
(let [res (-> "[1 2 3 4]"
z/of-string
z/down z/right z/right*
pe/split)]
(is (= "[1 2] [3 4]" (-> res z/root-string)))
(is (= "2" (-> res z/string)))))
(deftest split-includes-comments-and-newlines
(let [sexpr "
[1 ;dill
2 ;dall
3 ;jalla
]"
expected "
[1 ;dill
2 ;dall
] [3 ;jalla
]"
res (-> sexpr
z/of-string
z/down z/right
pe/split)]
(is (= expected (-> res z/root-string)))
(is (= "2" (-> res z/string)))))
(deftest split-when-only-one-returns-self
(is (= "[1]" (-> (z/of-string "[1]")
z/down
pe/split
z/root-string)))
(is (= "[1 ;dill\n]" (-> (z/of-string "[1 ;dill\n]")
z/down
pe/split
z/root-string))))
(deftest split-at-pos-when-string
(is (= "(\"Hello \" \"World\")"
(-> (z/of-string "(\"Hello World\")" {:track-position? true})
(pe/split-at-pos {:row 1 :col 9})
z/root-string))))
(deftest join-simple
(let [res (-> "[1 2] [3 4]"
z/of-string
;z/down
z/right*
pe/join)]
(is (= "[1 2 3 4]" (-> res z/root-string)))
(is (= "3" (-> res z/string)))))
(deftest join-with-comments
(let [sexpr "
[[1 2] ; the first stuff
[3 4] ; the second stuff
]" expected "
[[1 2 ; the first stuff
3 4]; the second stuff
]"
res (-> sexpr
z/of-string
z/down z/right*
pe/join)]
(is (= expected (-> res z/root-string)))))
(deftest join-strings
(is (= "(\"Hello World\")" (-> (z/of-string "(\"Hello \" \"World\")")
z/down z/rightmost
pe/join
z/root-string))))
(deftest raise
(is (= "[1 3]"
(-> (z/of-string "[1 [2 3 4]]")
z/down z/right z/down z/right
pe/raise
z/root-string))))
(deftest move-to-prev-flat
(is (= "(+ 2 1)" (-> "(+ 1 2)"
z/of-string
z/down
z/rightmost
pe/move-to-prev
z/root-string))))
(deftest move-to-prev-when-prev-is-seq
(is (= "(+ 1 (+ 2 3 4))" (-> "(+ 1 (+ 2 3) 4)"
z/of-string
z/down
z/rightmost
pe/move-to-prev
z/root-string))))
(deftest move-to-prev-out-of-seq
(is (= "(+ 1 4 (+ 2 3))" (-> "(+ 1 (+ 2 3) 4)"
z/of-string
z/down
z/rightmost
(move-n pe/move-to-prev 6)
z/root-string))))

View file

@ -0,0 +1,578 @@
(ns ^{:doc "Tests for EDN parser."
:author "Yannick Scherer"}
rewrite-clj.parser-test
(:refer-clojure :exclude [read-string])
(:require [clojure.edn :as edn]
[clojure.test :refer [deftest is are]]
;; not available in bb (yet):
#_[clojure.tools.reader.edn :refer [read-string]]
[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
(are [?ws ?parsed]
(let [n (p/parse-string ?ws)]
(is (= :whitespace (node/tag n)))
(is (= ?parsed (node/string n))))
" " " "
" \n " " "))
(deftest t-parsing-whitespace-strings
(are [?ws ?children]
(let [n (p/parse-string-all ?ws)]
(is (= :forms (node/tag n)))
(is (= (.replace ?ws "\r\n" "\n") (node/string n)))
(is (= ?children (map (juxt node/tag node/string) (node/children n)))))
" \n " [[:whitespace " "]
[:newline "\n"]
[:whitespace " "]]
" \t \r\n \t " [[:whitespace " \t "]
[:newline "\n"]
[:whitespace " \t "]]))
#?(:clj
(deftest t-parsing-unicode-whitespace-strings
(are [?ws ?children]
(let [n (p/parse-string-all ?ws)]
(is (= :forms (node/tag n)))
(is (= (.replace ?ws "\r\n" "\n") (node/string n)))
(is (= ?children (map (juxt node/tag node/string) (node/children n)))))
"\u2028" [[:whitespace "\u2028"]])))
(deftest t-parsing-simple-data
(are [?s ?r]
(let [n (p/parse-string ?s)]
(is (= :token (node/tag n)))
(is (= ?s (node/string n)))
(is (= ?r (node/sexpr n))))
"0" 0
"0.1" 0.1
"12e10" 1.2e11
"2r1100" 12
"1N" 1N
":key" :key
"\\\\" \\
"\\a" \a
"\\space" \space
"\\'" \'
":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"))
(deftest t-parsing-garden-selectors
;; https://github.com/noprompt/garden
(are [?s ?r]
(let [n (p/parse-string ?s)
r (node/sexpr n)]
(is (= ?s (node/string n)))
(is (= :token (node/tag n)))
(is (keyword? r))
(is (= ?r r)))
":&:hover" :&:hover
;; clj clojure reader can't parse :&::before but we can create a keyword for it
":&::before" (keyword "&::before")))
(deftest t-ratios
(are [?s ?r]
(let [n (p/parse-string ?s)]
(is (= :token (node/tag n)))
(is (= ?s (node/string n)))
(is (= ?r (node/sexpr n))))
"3/4" #?(:clj 3/4
;; no ratios in cljs; they are evaluated on sexpr
:cljs 0.75)))
(deftest t-big-integers
(are [?s ?r]
(let [n (p/parse-string ?s)]
(is (= :token (node/tag n)))
(is (= ?s (node/string n)))
(is (= ?r (node/sexpr n))))
"1234567890123456789012345678901234567890" 1234567890123456789012345678901234567890N))
(deftest t-parsing-symbolic-inf-values
(are [?s ?r]
(let [n (p/parse-string ?s)]
(is (= :token (node/tag n)))
(is (= ?s (node/string n)))
(is (= ?r (node/sexpr n))))
"##Inf" '##Inf
"##-Inf" '##-Inf))
(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
(are [?s ?t ?ws ?sexpr]
(let [n (p/parse-string ?s)
children (node/children n)
c (map node/tag children)]
(is (= ?t (node/tag n)))
(is (= :token (last c)))
(is (= ?sexpr (node/sexpr n)))
(is (= 'sym (node/sexpr (last children))))
(is (= ?ws (vec (butlast c)))))
"@sym" :deref [] '(deref sym)
"@ sym" :deref [:whitespace] '(deref sym)
"'sym" :quote [] '(quote sym)
"' sym" :quote [:whitespace] '(quote sym)
"`sym" :syntax-quote [] '(quote sym)
"` sym" :syntax-quote [:whitespace] '(quote sym)
"~sym" :unquote [] '(unquote sym)
"~ sym" :unquote [:whitespace] '(unquote sym)
"~@sym" :unquote-splicing [] '(unquote-splicing sym)
"~@ sym" :unquote-splicing [:whitespace] '(unquote-splicing sym)
"#=sym" :eval [] '(eval 'sym)
"#= sym" :eval [:whitespace] '(eval 'sym)
"#'sym" :var [] '(var sym)
"#'\nsym" :var [:newline] '(var sym)))
(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
(are [?s ?expected-sexpr]
(let [n (p/parse-string ?s)]
(is (= :regex (node/tag n)))
(is (= (count ?s) (node/length n)))
(is (= ?expected-sexpr (node/sexpr n))))
"#\"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])?")))
(deftest t-parsing-strings
(are [?s ?tag ?sexpr]
(let [n (p/parse-string ?s)]
(is (= ?tag (node/tag n)))
(is (= ?s (node/string n)))
(is (= ?sexpr (node/sexpr n))))
"\"123\"" :token "123"
"\"123\\n456\"" :token "123\n456"
"\"123\n456\"" :multi-line "123\n456"))
(deftest t-parsing-seqs
(are [?s ?t ?w ?c]
(let [n (p/parse-string ?s)
children (node/children n)
fq (frequencies (map node/tag children))]
(is (= ?t (node/tag n)))
(is (= (.trim ?s) (node/string n)))
(is (= (node/sexpr n) (edn/read-string ?s)))
(is (= ?w (:whitespace fq 0)))
(is (= ?c (:token fq 0))))
"(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))
(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
(are [?s ?t]
(let [n (p/parse-string ?s)]
(is (= ?t (node/tag n)))
(is (= ?s (node/string n))))
"{:a}" :map
"{:r 1 :u}" :map))
(deftest t-parsing-metadata
(are [?s ?t ?mt]
(let [s (str ?s " s")
n (p/parse-string s)
[mta ws sym] (node/children n)]
(is (= ?t (node/tag n)))
(is (= s (node/string n)))
(is (= 's (node/sexpr n)))
(is (= {:private true} (meta (node/sexpr n))))
(is (= ?mt (node/tag mta)))
(is (= :whitespace (node/tag ws)))
(is (= :token (node/tag sym)))
(is (= 's (node/sexpr sym))))
"^:private" :meta :token
"^{:private true}" :meta :map
"#^:private" :meta* :token
"#^{:private true}" :meta* :map))
(deftest t-parsing-multiple-metadata-forms
(are [?s ?expected-meta-tag ?expected-tag-on-metadata]
(let [s (str ?s " s")
n (p/parse-string s)
[mdata ws inner-n] (node/children n)
[inner-mdata inner-ws sym] (node/children inner-n)]
;; outer meta
(is (= ?expected-meta-tag (node/tag n)))
(is (= {:private true :awe true} (meta (node/sexpr n))))
(is (= ?expected-tag-on-metadata (node/tag mdata)))
(is (= :whitespace (node/tag ws)))
;; inner meta
(is (= ?expected-meta-tag (node/tag inner-n)))
(is (= {:awe true} (meta (node/sexpr inner-n))))
(is (= ?expected-tag-on-metadata (node/tag inner-mdata)))
(is (= :whitespace (node/tag inner-ws)))
;; symbol
(is (= s (node/string n)))
(is (= 's (node/sexpr sym))))
"^:private ^:awe" :meta :token
"^{:private true} ^{:awe true}" :meta :map
"#^:private #^:awe" :meta* :token
"#^{:private true} #^{:awe true}" :meta* :map))
(deftest t-parsing-reader-macros
(are [?s ?t ?children]
(let [n (p/parse-string ?s)]
(is (= ?t (node/tag n)))
(is (= ?s (node/string n)))
(is (= ?children (map node/tag (node/children n)))))
"#'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]))
(deftest t-parsing-anonymous-fn
(are [?s ?t ?sexpr-match ?children]
(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)))))
"#(+ % 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]))
(deftest t-parsing-comments
(are [?s]
(let [n (p/parse-string ?s)]
(is (node/printable-only? n))
(is (= :comment (node/tag n)))
(is (= ?s (node/string n))))
"; 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"))
(deftest t-parsing-auto-resolve-keywords
(are [?s ?sexpr-default ?sexpr-custom]
(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))}))))
"::key" :?_current-ns_?/key :my.current.ns/key
"::xyz/key" :??_xyz_??/key :my.aliased.ns/key))
(deftest t-parsing-qualified-maps
(are [?s ?sexpr]
(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))))
"#: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}}))
(deftest t-parsing-auto-resolve-current-ns-maps
(are [?s ?sexpr-default ?sexpr-custom]
(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)}))))
"#::{: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}}))
(deftest parsing-auto-resolve-ns-alias-maps
(are [?s ?sexpr-default ?sexpr-custom]
(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))}))))
"#::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}}))
(deftest t-parsing-exceptions
(are [?s ?p]
(is (thrown-with-msg? ExceptionInfo ?p (p/parse-string ?s)))
"#" #".*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.*"
"##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.*"))
(deftest t-sexpr-exceptions
(are [?s]
(is (thrown-with-msg? ExceptionInfo #"unsupported operation.*" (node/sexpr (p/parse-string ?s))))
"#_42" ;; reader ignore/discard
";; can't sexpr me!" ;; comment
" " ;; whitespace
))
(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))]
(are [?pos ?end ?t ?s ?sexpr]
(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)))
[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)))
(deftest t-os-specific-line-endings
(are [?in ?expected]
(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")))
"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"))

View file

@ -0,0 +1,51 @@
(ns rewrite-clj.zip.subedit-test
(:require [clojure.test :refer [deftest testing is]]
[rewrite-clj.zip :as z]
;; not available in bb:
#_[rewrite-clj.zip.base :as zbase]))
(deftest t-trees
(let [root (z/of-string "[1 #{2 [3 4] 5} 6]")]
(testing "modifying subtrees"
(let [loc (z/subedit-> root
z/next
z/next
z/next
(z/replace* 'x))]
(is (= :vector (z/tag loc)))
(is (= "[1 #{x [3 4] 5} 6]" (z/string loc)))))
(testing "modifying the whole tree"
(let [loc (z/edit-> (-> root z/next z/next z/next)
z/prev z/prev
(z/replace* 'x))]
(is (= :token (z/tag loc)))
(is (= "2" (z/string loc)))
(is (= "[x #{2 [3 4] 5} 6]" (z/root-string loc)))))))
(deftest zipper-retains-options
(let [zloc (z/of-string "(1 (2 (3 4 ::my-kw)))" {:auto-resolve (fn [_x] 'custom-resolved)})
#_#_orig-opts (zbase/get-opts zloc)]
(testing "sanity - without subzip"
(is (= :custom-resolved/my-kw (-> zloc
z/down z/right
z/down z/right
z/down z/rightmost z/sexpr))))
(testing "subzip"
(let [sub-zloc (-> zloc z/up* z/subzip z/down*)]
;; (is (= orig-opts (zbase/get-opts sub-zloc)))
(is (= :custom-resolved/my-kw (-> sub-zloc
z/down z/right
z/down z/right
z/down z/rightmost z/sexpr)))))
(testing "edit-node"
(let [edited-zloc (-> zloc (z/edit-node
(fn [zloc-edit]
(-> zloc-edit
z/down z/right
z/down (z/replace* 'x)))))]
(is (= 'x (-> edited-zloc z/down z/right z/down z/sexpr)))
;; (is (= orig-opts (zbase/get-opts edited-zloc)))
(is (= :custom-resolved/my-kw (-> edited-zloc
z/down z/right
z/down z/right
z/down z/rightmost z/sexpr)))))))

View file

@ -0,0 +1,172 @@
(ns rewrite-clj.zip-test
"This test namespace originated from rewrite-cljs."
(:require [clojure.string :as string]
[clojure.test :refer [deftest testing is are]]
[rewrite-clj.node :as n]
[rewrite-clj.zip :as z]))
(deftest of-string-simple-sexpr
(let [sexpr "(+ 1 2)"]
(is (= sexpr (-> sexpr z/of-string z/root-string)))))
(deftest manipulate-sexpr
(let [sexpr
(string/join
"\n" [""
" ^{:dynamic true} (+ 1 1"
" (+ 2 2)"
" (reduce + [1 3 4]))"])
expected
(string/join
"\n" [""
" ^{:dynamic true} (+ 1 1"
" (+ 2 2)"
" (reduce + [6 7 [1 2]]))"])]
(is (= expected (-> (z/of-string sexpr {:track-position? true})
(z/find-tag-by-pos {:row 4 :col 19} :vector) ;; should find [1 3 4] col 19 points to element 4 in vector
(z/replace [5 6 7]) ;; replaces [1 3 4] with [5 6 7]
(z/append-child [1 2]) ;; appends [1 2] to [5 6 7] giving [5 6 [1 2]]
z/down ;; navigate to 5
z/remove ;; remove 5 giving [6 7 [1 2]]
z/root-string)))))
(deftest t-rowcolumn-positions-from-position-tracking-zipper
;; if you update this test, please also review/update:
;; rewrite-clj.parser-test.t-rowcolumn-metadata-from-clojure-tools-reader
(let [s (str
;12345678901234
"(defn f\n"
" [x]\n"
" (println x))")
positions (->> (z/of-string s {:track-position? true})
(iterate z/next)
(take-while #(not (z/end? %)))
(reduce (fn [acc zloc]
(let [[start end] (z/position-span zloc)]
(assoc acc start {:node (z/node zloc) :end-pos end})))
{}))]
(are [?pos ?end ?t ?s ?sexpr]
(let [{:keys [node end-pos]} (positions ?pos)]
(is (= ?t (n/tag node)))
(is (= ?s (n/string node)))
(is (= ?sexpr (n/sexpr node)))
(is (= ?end end-pos)))
[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)))
(deftest namespaced-keywords
(is (= ":dill" (-> ":dill" z/of-string z/root-string)))
(is (= "::dill" (-> "::dill" z/of-string z/root-string)))
(is (= ":dill/dall" (-> ":dill/dall" z/of-string z/root-string)))
(is (= "::dill/dall" (-> "::dill/dall" z/of-string z/root-string)))
(is (= ":%dill.*" (-> ":%dill.*" z/of-string z/root-string))))
(deftest sexpr-udpates-correctly-for-namespaced-map-keys
(testing "on parse"
(is (= '(:prefix/a 1 :prefix/b 2 prefix/c 3)
(-> "#:prefix {:a 1 :b 2 c 3}"
z/of-string
z/down
z/rightmost
z/child-sexprs))))
(testing "on insert new key val"
(is (= '(:prefix/a 1 :prefix/b 2 prefix/c 3 prefix/d 4)
(-> "#:prefix {:a 1 :b 2 c 3}"
z/of-string
z/down
z/rightmost
(z/append-child 'd)
(z/append-child 4)
z/up ;; changes and also nsmap context are applied when moving up to nsmap
z/down
z/rightmost
z/child-sexprs))))
(testing "on update existing key val"
(is (= '(:prefix/a 1 :prefix/b2 2 prefix/c 3)
(-> "#:prefix {:a 1 :b 2 c 3}"
z/of-string
z/down
z/rightmost
z/down
z/right
z/right
(z/replace :b2)
z/up ;; changes and also nsmap context are applied when moving up to nsmap
z/up
z/down
z/rightmost
z/child-sexprs))))
(testing "on update creating unbalanced map (which rewrite-clj allows) context is cleared/applied as appropriate"
(is (= '(:prefix/hi :a prefix/b :c prefix/d e prefix/f)
(-> "#:prefix {:a b :c d e f}"
z/of-string
z/down
z/rightmost
(z/insert-child :hi)
z/up ;; changes and also nsmap context are applied when moving up to nsmap
z/down
z/rightmost
z/child-sexprs))))
(testing "namespaced map qualifier can be changed and affect sexpr of its map keys"
(is (= '(:??_ns-alias_??/a 1 :??_ns-alias_??/b 2 :c 3)
(-> "#:prefix {:a 1 :b 2 :_/c 3}"
z/of-string
z/down
(z/replace (n/map-qualifier-node true "ns-alias"))
z/up
z/down
z/rightmost
z/child-sexprs))))
(testing "node context can be be explicitly removed when moving node out of namespaced map"
(is (= '[{:prefix/b 2 :prefix/c 3}
{:a 1 :z 99}]
(let [zloc (-> "[#:prefix {:a 1 :b 2 :c 3}{:z 99}]"
z/of-string
z/down
z/down
z/rightmost
z/down)
move-me1 (-> zloc z/node n/map-context-clear) ;; if we don't clear the map context it will remain
zloc (-> zloc z/remove z/down)
move-me2 (-> zloc z/node)
zloc (z/remove zloc)]
(-> zloc
z/up
z/right
(z/insert-child move-me2)
(z/insert-child move-me1)
z/up
z/sexpr)))))
(testing "node context can be explicitly reapplied to entire zloc downward"
(is (= '[{:prefix/b 2 :prefix/c 3}
{:a 1 :z 99}]
(let [zloc (-> "[#:prefix {:a 1 :b 2 :c 3}{:z 99}]"
z/of-string
z/down
z/down
z/rightmost
z/down)
move-me1 (-> zloc z/node) ;; notice we don't clear context here
zloc (-> zloc z/remove z/down)
move-me2 (-> zloc z/node)
zloc (z/remove zloc)]
(-> zloc
z/up
z/right
(z/insert-child move-me2)
(z/insert-child move-me1)
z/up
z/reapply-context ;; but we do reapply context to tree before doing a sexpr
z/sexpr))))))
(deftest t-sexpr-able
;; spot check, more thorough testing done on node tests
(is (= false (-> "," z/of-string z/next* z/sexpr-able?)))
(is (= true (-> "heyy" z/of-string z/sexpr-able?))))

View file

@ -83,3 +83,12 @@
(is (= (:message m2) "1 is not equal to 2"))
(is (= (:line m2) 6))
(is (= m3 '{:type :end-test-var, :var bar}))))
(deftest are-with-is-test
(let [output (bb "
(do (require '[clojure.test :as t])
(t/deftest foo (t/are [x]
(t/is (thrown-with-msg? Exception #\"\" x))
(throw (ex-info \"\" {})))))
(t/run-tests *ns*)")]
(is (str/includes? output "Ran 1 tests containing 2 assertions."))))