Skip to content

Commit d08609f

Browse files
r0manbbatsov
authored andcommitted
Add support for Clojure Spec 2
1 parent 33c26b2 commit d08609f

File tree

4 files changed

+194
-2
lines changed

4 files changed

+194
-2
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## master (unreleased)
44

5+
### New features
6+
7+
- [#3249](https://github.com/clojure-emacs/cider/pull/3249): Add support for Clojure Spec 2.
8+
59
### Changes
610

711
- Bump the injected nREPL version to 1.0.

cider-browse-spec.el

+85-2
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,79 @@ Display TITLE at the top and SPECS are indented underneath."
142142

143143
(defun cider--spec-fn-p (value fn-name)
144144
"Return non nil if VALUE is clojure.spec.[alpha]/FN-NAME."
145-
(string-match-p (concat "^\\(clojure.spec\\|clojure.spec.alpha\\)/" fn-name "$") value))
145+
(string-match-p (concat "^\\(clojure.spec\\|clojure.spec.alpha\\|clojure.alpha.spec\\)/" fn-name "$") value))
146+
147+
(defun cider-browse-spec--render-schema-map (spec-form)
148+
"Render the s/schema map declaration SPEC-FORM."
149+
(let ((name-spec-pairs (seq-partition (cdaadr spec-form) 2)))
150+
(format "(s/schema\n {%s})"
151+
(string-join
152+
(thread-last
153+
(seq-sort-by #'car #'string< name-spec-pairs)
154+
(mapcar (lambda (s) (concat (cl-first s) " " (cider-browse-spec--pprint (cl-second s))))))
155+
"\n "))))
156+
157+
(defun cider-browse-spec--render-schema-vector (spec-form)
158+
"Render the s/schema vector declaration SPEC-FORM."
159+
(format "(s/schema\n [%s])"
160+
(string-join
161+
(thread-last
162+
(cl-second spec-form)
163+
(mapcar (lambda (s) (cider-browse-spec--pprint s))))
164+
"\n ")))
165+
166+
(defun cider-browse-spec--render-schema (spec-form)
167+
"Render the s/schema SPEC-FORM."
168+
(let ((schema-args (cl-second spec-form)))
169+
(if (and (listp schema-args)
170+
(nrepl-dict-p (cl-first schema-args)))
171+
(cider-browse-spec--render-schema-map spec-form)
172+
(cider-browse-spec--render-schema-vector spec-form))))
173+
174+
(defun cider-browse-spec--render-select (spec-form)
175+
"Render the s/select SPEC-FORM."
176+
(let ((keyset (cl-second spec-form))
177+
(selection (cl-third spec-form)))
178+
(format "(s/select\n %s\n [%s])"
179+
(cider-browse-spec--pprint keyset)
180+
(string-join
181+
(thread-last
182+
selection
183+
(mapcar (lambda (s) (cider-browse-spec--pprint s))))
184+
"\n "))))
185+
186+
(defun cider-browse-spec--render-union (spec-form)
187+
"Render the s/union SPEC-FORM."
188+
(let ((keyset (cl-second spec-form))
189+
(selection (cl-third spec-form)))
190+
(format "(s/union\n %s\n [%s])"
191+
(cider-browse-spec--pprint keyset)
192+
(string-join
193+
(thread-last
194+
selection
195+
(mapcar (lambda (s) (cider-browse-spec--pprint s))))
196+
"\n "))))
197+
198+
(defun cider-browse-spec--render-vector (spec-form)
199+
"Render SPEC-FORM as a vector."
200+
(format "[%s]" (string-join (mapcar #'cider-browse-spec--pprint spec-form))))
201+
202+
(defun cider-browse-spec--render-map-entry (spec-form)
203+
"Render SPEC-FORM as a map entry."
204+
(let ((key (cl-first spec-form))
205+
(value (cl-second spec-form)))
206+
(format "%s %s" (cider-browse-spec--pprint key)
207+
(if (listp value)
208+
(cider-browse-spec--render-vector value)
209+
(cider-browse-spec--pprint value)))))
210+
211+
(defun cider-browse-spec--render-map (spec-form)
212+
"Render SPEC-FORM as a map."
213+
(let ((map-entries (cl-rest spec-form)))
214+
(format "{%s}" (thread-last
215+
(seq-partition map-entries 2)
216+
(seq-map #'cider-browse-spec--render-map-entry)
217+
(string-join)))))
146218

147219
(defun cider-browse-spec--pprint (form)
148220
"Given a spec FORM builds a multi line string with a pretty render of that FORM."
@@ -158,7 +230,7 @@ Display TITLE at the top and SPECS are indented underneath."
158230
;; and remove all clojure.core ns
159231
(thread-last
160232
form
161-
(replace-regexp-in-string "^\\(clojure.spec\\|clojure.spec.alpha\\)/" "s/")
233+
(replace-regexp-in-string "^\\(clojure.spec\\|clojure.spec.alpha\\|clojure.alpha.spec\\)/" "s/")
162234
(replace-regexp-in-string "^\\(clojure.core\\)/" ""))))
163235

164236
((and (listp form) (stringp (cl-first form)))
@@ -254,10 +326,21 @@ Display TITLE at the top and SPECS are indented underneath."
254326
(cider-browse-spec--pprint (cl-second s)))))
255327
(cl-reduce #'concat)
256328
(format "%s")))
329+
;; prettier (s/schema )
330+
((cider--spec-fn-p form-tag "schema")
331+
(cider-browse-spec--render-schema form))
332+
;; prettier (s/select )
333+
((cider--spec-fn-p form-tag "select")
334+
(cider-browse-spec--render-select form))
335+
;; prettier (s/union )
336+
((cider--spec-fn-p form-tag "union")
337+
(cider-browse-spec--render-union form))
257338
;; every other with no special management
258339
(t (format "(%s %s)"
259340
(cider-browse-spec--pprint form-tag)
260341
(string-join (mapcar #'cider-browse-spec--pprint (cl-rest form)) " "))))))
342+
((nrepl-dict-p form)
343+
(cider-browse-spec--render-map form))
261344
(t (format "%s" form))))
262345

263346
(defun cider-browse-spec--pprint-indented (spec-form)

doc/modules/ROOT/pages/usage/misc_features.adoc

+18
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,24 @@ meets the spec.
318318

319319
image::spec_browser_gen_example.png[Spec Browser Example]
320320

321+
== Clojure Spec Versions
322+
323+
Clojure Spec has a bit of a history and is available in a couple of
324+
flavours:
325+
326+
* `spec` (aka `clojure.spec`, the original release, never shipped with Clojure)
327+
* `spec-alpha` (aka `clojure.spec.alpha`, the original release under a different name, ships with Clojure)
328+
* `spec-alpha-2` (aka `clojure.alpha.spec`, the evolution, separate library, but still experimental)
329+
330+
Cider supports the whole mix, but with a twist.
331+
332+
* When Cider shows a list of specs, the keys from all registries are
333+
shown. Registries are merged together from newest to oldest.
334+
335+
* When Cider operates on a spec, like looking up a spec or generating
336+
data for it, the operation is tried against all registries, from
337+
newest to oldest, with the first successful operation winning.
338+
321339
== Formatting Code with cljfmt
322340

323341
While CIDER has it's own code formatting (indentation) engine, you can also

test/cider-browse-spec-tests.el

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
;;; cider-browse-spec-tests.el -*- lexical-binding: t; -*-
2+
3+
;; Copyright © 2012-2022 r0man, Bozhidar Batsov
4+
5+
;; Author: r0man <[email protected]>
6+
;; Bozhidar Batsov <[email protected]>
7+
8+
;; This file is NOT part of GNU Emacs.
9+
10+
;; This program is free software: you can redistribute it and/or
11+
;; modify it under the terms of the GNU General Public License as
12+
;; published by the Free Software Foundation, either version 3 of the
13+
;; License, or (at your option) any later version.
14+
;;
15+
;; This program is distributed in the hope that it will be useful, but
16+
;; WITHOUT ANY WARRANTY; without even the implied warranty of
17+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18+
;; General Public License for more details.
19+
;;
20+
;; You should have received a copy of the GNU General Public License
21+
;; along with this program. If not, see `http://www.gnu.org/licenses/'.
22+
23+
;;; Commentary:
24+
25+
;; This file is part of CIDER
26+
27+
;;; Code:
28+
29+
(require 'buttercup)
30+
(require 'cider-browse-spec)
31+
32+
(defvar cider-browse-spec-tests--schema-vector-response
33+
'("clojure.alpha.spec/schema"
34+
(":example.customer/id" ":example.customer/name"))
35+
"The NREPL response for a s/schema vector spec.")
36+
37+
(defvar cider-browse-spec-tests--schema-map-response
38+
'("clojure.alpha.spec/schema"
39+
((dict ":id" ":example.customer/id"
40+
":name" ":example.customer/name")))
41+
"The NREPL response for a s/schema map spec.")
42+
43+
(defvar cider-browse-spec-tests--company-addr-response
44+
'("clojure.alpha.spec/union" ":test/addr"
45+
(":test/company" ":test/suite"))
46+
"The NREPL response for the :user/company-addr spec.")
47+
48+
(defvar cider-browse-spec-tests--movie-times-user-response
49+
'("clojure.alpha.spec/select" ":test/user"
50+
(":test/id" ":test/addr"
51+
(dict ":test/addr"
52+
(":test/zip"))))
53+
"The NREPL response for the :user/movie-times-user spec.")
54+
55+
(defun cider-browse-spec-tests--setup-spec-form (spec-form)
56+
"Setup the mocks to test rendering of SPEC-FORM."
57+
(spy-on 'sesman-current-session :and-return-value t)
58+
(spy-on 'cider-nrepl-op-supported-p :and-return-value t)
59+
(spy-on 'cider-connected-p :and-return-value nil)
60+
(spy-on 'cider--get-symbol-indent :and-return-value nil)
61+
(spy-on 'cider-sync-request:spec-form :and-return-value spec-form))
62+
63+
(describe "cider-browse-spec--browse"
64+
(it "raises user-error when cider is not connected."
65+
(spy-on 'sesman-current-session :and-return-value nil)
66+
(expect (cider-browse-spec--browse ":example/customer") :to-throw 'user-error))
67+
68+
(it "raises user-error when the `spec-form' op is not supported."
69+
(spy-on 'sesman-current-session :and-return-value t)
70+
(spy-on 'cider-nrepl-op-supported-p :and-return-value nil)
71+
(expect (cider-browse-spec--browse ":example/customer") :to-throw 'user-error))
72+
73+
(it "renders a s/schema map form"
74+
(cider-browse-spec-tests--setup-spec-form cider-browse-spec-tests--schema-map-response)
75+
(expect (cider-browse-spec--browse ":example/customer")))
76+
77+
(it "renders a s/schema vector form"
78+
(cider-browse-spec-tests--setup-spec-form cider-browse-spec-tests--schema-vector-response)
79+
(expect (cider-browse-spec--browse ":example/customer")))
80+
81+
(it "renders a s/select form"
82+
(cider-browse-spec-tests--setup-spec-form cider-browse-spec-tests--movie-times-user-response)
83+
(expect (cider-browse-spec--browse ":user/movie-times-user")))
84+
85+
(it "renders a s/union form"
86+
(cider-browse-spec-tests--setup-spec-form cider-browse-spec-tests--company-addr-response)
87+
(expect (cider-browse-spec--browse ":user/company-addr"))))

0 commit comments

Comments
 (0)