Skip to content

Commit

Permalink
Make spec-assertion-thrown? cljs-compatible
Browse files Browse the repository at this point in the history
Part of #80
  • Loading branch information
vemv committed Dec 29, 2019
1 parent 8218478 commit 8f668d6
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 63 deletions.
17 changes: 5 additions & 12 deletions src/nedap/speced/def.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
(:require
#?(:clj [clojure.test :as test])
#?(:clj [nedap.speced.def.impl.fn :as impl.fn] :cljs [nedap.speced.def.impl.dummy :as impl.fn])
#?(:clj nedap.speced.def.impl.spec-assertion.jvm)
#?(:clj [clojure.core.specs.alpha :as specs] :cljs [cljs.core.specs.alpha :as specs])
#?(:clj [nedap.speced.def.impl.letfn :as impl.letfn])
#?(:clj [nedap.speced.def.impl.let-impl :as let-impl])
#?(:clj [nedap.speced.def.impl.spec-assertion])
[clojure.spec.alpha :as spec]
[nedap.speced.def.doc :refer [doc-registry rebl-doc-registry]]
[nedap.speced.def.impl.def-with-doc]
[nedap.speced.def.impl.defn :as impl.defn]
[nedap.speced.def.impl.defprotocol]
[nedap.speced.def.impl.doc :as impl.doc]
[nedap.speced.def.impl.satisfies]
[nedap.speced.def.impl.spec-assertion]
[nedap.utils.spec.api :refer [check!]])
#?(:cljs (:require-macros [nedap.speced.def :refer [def-with-doc]])))
#?(:cljs (:require-macros [nedap.speced.def :refer [def-with-doc]] [nedap.speced.def.impl.spec-assertion.cljs])))

#?(:clj
(defmacro def-with-doc
Expand Down Expand Up @@ -116,7 +117,8 @@
"Emits a spec-backed `letfn`, which uses `nedap.utils.spec.api/check!` at runtime
to verify that any metadata-annotated symbols satisfy the specs denoted by that metadata.
Has the exact same signature as `#'clojure.core/letfn`, with full support for arbitrarily nested destructuring and multiple arities.
Has the exact same signature as `#'clojure.core/letfn`,
with full support for arbitrarily nested destructuring and multiple arities.
Spec metadata follows the `:nedap.speced.def.specs/spec-metadata` 'syntaxes'.
Expand Down Expand Up @@ -144,12 +146,3 @@
"Can be summed to an existing spec (also passed as metadata),
for indicating that that spec is nilable."
any?)

#?(:clj
(defmethod test/assert-expr 'spec-assertion-thrown? [msg form]
;; (is (spec-assertion-thrown? s expr))
;; Asserts that evaluating expr throws an ExceptionInfo related to spec-symbol s.
;; Returns the exception thrown.
(clojure.core/let [spec-sym (second form)
body (nthnext form 2)]
(nedap.speced.def.impl.spec-assertion/spec-assertion-thrown? msg spec-sym body))))
5 changes: 3 additions & 2 deletions src/nedap/speced/def/impl/parsing.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,9 @@
(count)))]
:post [(check! (partial proper-spec-metadata? clj? metadata-map) %)]}
(let [metadata-map (cond-> metadata-map
(-> metadata-map :tag #?(:clj class?
:cljs fail)) (update :tag class->symbol))
(and clj?
(-> metadata-map :tag #?(:clj class?
:cljs fail))), (update :tag class->symbol))
nilable? (->> metadata-map keys (some #{nilable}))]
(->> metadata-map
(remove spec-directive?)
Expand Down
34 changes: 34 additions & 0 deletions src/nedap/speced/def/impl/spec_assertion.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
(ns nedap.speced.def.impl.spec-assertion
(:require
[clojure.test :as test]
[nedap.speced.def.impl.parsing :refer [extract-specs-from-metadata]]
[nedap.utils.spec.api :refer [check!]])
(:import
(clojure.lang ExceptionInfo)))

(defn spec-assertion-thrown? [clj? msg spec-sym body]
{:pre [(check! boolean? clj?
some? spec-sym
sequential? body)]}
(let [reporter (if clj?
`test/do-report
'cljs.test/do-report)]
`(try
(with-out-str ;; silently execute body
~@body)
(~reporter {:type :fail, :message ~msg :expected '~spec-sym, :actual "no spec failure"})
(catch ~(if clj?
`ExceptionInfo
'ExceptionInfo) e#
(let [spec# (:spec (ex-data e#))
inferred-specs# (set (map :spec (extract-specs-from-metadata {:tag ~spec-sym} ~clj?)))]

;; rethrow if no spec failure is found
(when-not spec#
(throw e#))

(if (or (= ~spec-sym spec#)
(contains? inferred-specs# spec#))
(~reporter {:type :pass, :message ~msg :expected '~spec-sym, :actual nil})
(~reporter {:type :fail, :message ~msg :expected '~spec-sym, :actual spec#}))
e#)))))
24 changes: 0 additions & 24 deletions src/nedap/speced/def/impl/spec_assertion.cljc

This file was deleted.

10 changes: 10 additions & 0 deletions src/nedap/speced/def/impl/spec_assertion/cljs.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(ns nedap.speced.def.impl.spec-assertion.cljs
(:require
[cljs.test :as test]
#?(:clj [nedap.speced.def.impl.spec-assertion :as impl.spec-assertion])))

#?(:clj
(defmethod test/assert-expr 'spec-assertion-thrown? [_env msg form]
(clojure.core/let [spec-sym (second form)
body (nthnext form 2)]
(impl.spec-assertion/spec-assertion-thrown? false msg spec-sym body))))
9 changes: 9 additions & 0 deletions src/nedap/speced/def/impl/spec_assertion/jvm.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(ns nedap.speced.def.impl.spec-assertion.jvm
(:require
[clojure.test :as test]
[nedap.speced.def.impl.spec-assertion :as impl.spec-assertion]))

(defmethod test/assert-expr 'spec-assertion-thrown? [msg form]
(clojure.core/let [spec-sym (second form)
body (nthnext form 2)]
(impl.spec-assertion/spec-assertion-thrown? true msg spec-sym body)))
8 changes: 5 additions & 3 deletions src/nedap/speced/def/impl/type_hinting.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
(contains? s)))

(defn clj-type-hint? [x]
(assert (not= *ns* this-ns) (str "For an accurate `resolve` call (see `#'clj-type-hint?`)."
(pr-str [*ns* this-ns])))
(or (#?(:clj class?
:cljs (assert false)) x)
(and (symbol? x)
Expand All @@ -91,17 +93,17 @@

(defn type-hint?
([x]
(assert (not= *ns* this-ns) "For an accurate `resolve` call (see `#'clj-type-hint?`).")
#?(:clj (clj-type-hint? x)
:cljs (cljs-type-hint? x)))

([x clj?]
(assert (not= *ns* this-ns) "For an accurate `resolve` call (see `#'clj-type-hint?`).")
(assert (boolean? clj?))
#?(:clj (if clj?
(clj-type-hint? x)
(cljs-type-hint? x))
:cljs (assert false))))
:cljs (do
(assert (not clj?))
(cljs-type-hint? x)))))

(defn ensure-proper-type-hint
[clj? imeta]
Expand Down
59 changes: 37 additions & 22 deletions test/unit/nedap/speced/def/spec_assertion.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,50 @@
[nedap.utils.spec.api :refer [check!]]))

(spec/def ::age number?)
(speced/defn accepts-age [^::age x] x)
(speced/defn ^::age returns-age [x] x)
(speced/defn accepts-number [^number? x] x)
(speced/defn ^number? returns-number [x] x)
(def accepts-number-fn (speced/fn [^number? x] x))
(def returns-number-fn (speced/fn ^number? returns-number-fn [x] x))

(speced/defn accepts-age [^::age x]
x)

(speced/defn ^::age returns-age [x]
x)

(speced/defn accepts-number [^number? x]
x)

(speced/defn ^number? returns-number [x]
x)

(def accepts-number-fn (speced/fn [^number? x]
x))

(def returns-number-fn (speced/fn ^number? returns-number-fn [x]
x))

(speced/defprotocol ExampleProtocol
"Docstring"
(accepts-string [this ^string? value] "Docstring")
(accepts-string [this ^string? value]
"Docstring")

(^string?
returns-string [this value] "Docstring"))
returns-string [this value]
"Docstring"))

(defrecord ExampleRecord []
ExampleProtocol
(--accepts-string [this x] x)
(--returns-string [this x] x))
(--accepts-string [this x]
x)

(--returns-string [this x]
x))

(deftest spec-assertion-thrown?-defmethod
#?(:clj
(do
(is (spec-assertion-thrown? 'string? (check! string? 123)))
(is (spec-assertion-thrown? 'number? (check! number? "123")))
(is (spec-assertion-thrown? ::age (accepts-age "1234")))
(is (spec-assertion-thrown? ::age (returns-age "1234")))
(is (spec-assertion-thrown? 'number? (accepts-number "1234")))
(is (spec-assertion-thrown? 'number? (returns-number "1234")))
(is (spec-assertion-thrown? 'number? (accepts-number-fn "1234")))
(is (spec-assertion-thrown? 'number? (returns-number-fn "1234")))
(is (spec-assertion-thrown? 'string? (accepts-string (->ExampleRecord) 1234)))
(is (spec-assertion-thrown? 'string? (returns-string (->ExampleRecord) 1234))))))
(is (spec-assertion-thrown? 'string? (check! string? 123)))
(is (spec-assertion-thrown? 'number? (check! number? "123")))
(is (spec-assertion-thrown? ::age (accepts-age "1234")))
(is (spec-assertion-thrown? ::age (returns-age "1234")))
(is (spec-assertion-thrown? 'number? (accepts-number "1234")))
(is (spec-assertion-thrown? 'number? (returns-number "1234")))
(is (spec-assertion-thrown? 'number? (accepts-number-fn "1234")))
(is (spec-assertion-thrown? 'number? (returns-number-fn "1234")))
(is (spec-assertion-thrown? 'string? (accepts-string (->ExampleRecord) 1234)))
(is (spec-assertion-thrown? 'string? (returns-string (->ExampleRecord) 1234))))

0 comments on commit 8f668d6

Please sign in to comment.