Skip to content

Commit a9c0f05

Browse files
committed
align associative
1 parent 3418b7f commit a9c0f05

File tree

4 files changed

+162
-0
lines changed

4 files changed

+162
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ pom.xml.asc
88
.lein-*
99
.nrepl-port
1010
reports
11+
*.cpcache

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ selectively enabled or disabled:
154154
other references in the `ns` forms at the top of your namespaces.
155155
Defaults to false.
156156

157+
* `:align-associative?` -
158+
true if cljfmt should left align the values of maps and binding
159+
special forms (let, loop, binding). This will convert
160+
`{:foo 1\n:barbaz 2}` to `{:foo 1\n :barbaz 2}`
161+
and `(let [foo 1\n barbaz 2])` to `(let [foo 1\n barbaz 2])`.
162+
Defaults to false.
163+
157164
You can also configure the behavior of cljfmt:
158165

159166
* `:paths` - determines which directories to include in the

cljfmt/src/cljfmt/core.cljc

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,73 @@
7171
(not (namespaced-map? (z/up* zloc)))
7272
(element? (z/right* zloc))))
7373

74+
(def ^:private binding-keywords #{"doseq" "let" "loop" "binding" "with-open"
75+
"go-loop" "if-let" "when-some" "if-some" "for"
76+
"with-local-vars" "with-redefs"})
77+
78+
(defn ks->max-length [ks]
79+
(if (empty? ks)
80+
0
81+
(->> ks
82+
(apply max-key (comp count str))
83+
str
84+
count)))
85+
86+
(defn- aligner [zloc max-length align?]
87+
(cond
88+
(zero? max-length) (z/up zloc)
89+
(z/rightmost? zloc) (z/up zloc)
90+
align? (let [to-add (->> zloc
91+
z/sexpr
92+
str
93+
count
94+
(- max-length))
95+
new-zloc (z/insert-space-right zloc to-add)]
96+
(aligner (z/right new-zloc) max-length false))
97+
:else (aligner (z/right zloc) max-length true)))
98+
99+
(defn- align-binding [zloc]
100+
(let [se (z/sexpr zloc)
101+
ks (take-nth 2 se)
102+
max-length (ks->max-length ks)
103+
bindings (z/down zloc)]
104+
(if bindings
105+
(aligner bindings max-length true)
106+
zloc)))
107+
108+
(defn- align-map [zloc]
109+
(let [se (z/sexpr zloc)
110+
ks (keys se)
111+
max-length (ks->max-length ks)
112+
kvs (z/down zloc)]
113+
(if kvs
114+
(aligner kvs max-length true)
115+
zloc)))
116+
117+
(defn- binding? [zloc]
118+
(and (z/vector? zloc)
119+
(-> zloc z/sexpr count even?)
120+
(->> zloc
121+
z/left
122+
z/string
123+
binding-keywords)))
124+
125+
(defn align-map-or-binding [zloc]
126+
(cond
127+
(binding? zloc) (align-binding zloc)
128+
(z/map? zloc) (align-map zloc)
129+
:else zloc))
130+
131+
(defn- align-associative? [zloc]
132+
(or (binding? zloc)
133+
(z/map? zloc)))
134+
74135
(defn insert-missing-whitespace [form]
75136
(transform form edit-all missing-whitespace? z/insert-space-right))
76137

138+
(defn align-associative [form]
139+
(transform form edit-all align-associative? align-map-or-binding))
140+
77141
(defn- space? [zloc]
78142
(= (z/tag zloc) :whitespace))
79143

@@ -492,6 +556,7 @@
492556
:remove-surrounding-whitespace? true
493557
:remove-trailing-whitespace? true
494558
:split-keypairs-over-multiple-lines? false
559+
:align-associative? false
495560
:sort-ns-references? false
496561
:indents default-indents
497562
:alias-map {}})
@@ -512,6 +577,8 @@
512577
remove-surrounding-whitespace)
513578
(cond-> (:insert-missing-whitespace? opts)
514579
insert-missing-whitespace)
580+
(cond-> (:align-associative? opts)
581+
align-associative)
515582
(cond-> (:remove-multiple-non-indenting-spaces? opts)
516583
remove-multiple-non-indenting-spaces)
517584
(cond-> (:indentation? opts)

cljfmt/test/cljfmt/core_test.cljc

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,3 +1336,90 @@
13361336
" ^{:x 1} b"
13371337
" [c]))"]
13381338
{:sort-ns-references? true})))
1339+
1340+
(deftest test-align-associative
1341+
(testing "sanity"
1342+
(is (reformats-to?
1343+
["(def x 1)"]
1344+
["(def x 1)"]
1345+
{:align-associative? true})))
1346+
(testing "no op 0"
1347+
(is (reformats-to?
1348+
["(let [x 1"
1349+
" y 2])"]
1350+
["(let [x 1"
1351+
" y 2])"]
1352+
{:align-associative? true})))
1353+
(testing "no op 1"
1354+
(is (reformats-to?
1355+
["(let [x 1])"]
1356+
["(let [x 1])"]
1357+
{:align-associative? true})))
1358+
(testing "empty"
1359+
(is (reformats-to?
1360+
["(let [])"]
1361+
["(let [])"]
1362+
{:align-associative? true})))
1363+
(testing "simple binding"
1364+
(is (reformats-to?
1365+
["(let [x 1"
1366+
" longer 2])"]
1367+
["(let [x 1"
1368+
" longer 2])"]
1369+
{:align-associative? true})))
1370+
(testing "simple map"
1371+
(is (reformats-to?
1372+
["{:x 1"
1373+
" :longer 2}"]
1374+
["{:x 1"
1375+
" :longer 2}"]
1376+
{:align-associative? true})))
1377+
(testing "nested simple map"
1378+
(is (reformats-to?
1379+
["{:x {:x 1}"
1380+
" :longer 2}"]
1381+
["{:x {:x 1}"
1382+
" :longer 2}"]
1383+
{:align-associative? true})))
1384+
(testing "nested align map"
1385+
(is (reformats-to?
1386+
["{:x {:x 1"
1387+
" :longer 2}"
1388+
" :longer 2}"]
1389+
["{:x {:x 1"
1390+
" :longer 2}"
1391+
" :longer 2}"]
1392+
{:align-associative? true})))
1393+
(testing "nested align binding"
1394+
(is (reformats-to?
1395+
["(let [x (let [x 1"
1396+
" longer 2])"
1397+
" longer 2])"]
1398+
["(let [x (let [x 1"
1399+
" longer 2])"
1400+
" longer 2])"]
1401+
{:align-associative? true})))
1402+
(testing "align many map"
1403+
(is (reformats-to?
1404+
["{:a 1"
1405+
" :longer 2"
1406+
" :b 3}"]
1407+
["{:a 1"
1408+
" :longer 2"
1409+
" :b 3}"]
1410+
{:align-associative? true})))
1411+
(testing "binding align preserves comments"
1412+
(is (reformats-to?
1413+
["(let [a 1 ;; comment"
1414+
" longer 2])"]
1415+
["(let [a 1 ;; comment"
1416+
" longer 2])"]
1417+
{:align-associative? true}
1418+
)))
1419+
(testing "map align preserves comments"
1420+
(is (reformats-to?
1421+
["{:a 1 ;; comment"
1422+
" :longer 2}"]
1423+
["{:a 1 ;; comment"
1424+
" :longer 2}"]
1425+
{:align-associative? true}))))

0 commit comments

Comments
 (0)