Skip to content

Commit 3693b3f

Browse files
committed
align maps
1 parent 05ee079 commit 3693b3f

File tree

3 files changed

+266
-0
lines changed

3 files changed

+266
-0
lines changed

README.md

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

157+
* `:align-maps?` -
158+
True if cljfmt should left align the values of maps.
159+
160+
This will convert:
161+
```clojure
162+
{:foo 1
163+
:barbaz 2}
164+
```
165+
To:
166+
```clojure
167+
{:foo 1
168+
:barbaz 2}
169+
```
170+
Defaults to false.
171+
157172
You can also configure the behavior of cljfmt:
158173

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

cljfmt/src/cljfmt/core.cljc

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,80 @@
486486
(defn sort-ns-references [form]
487487
(transform form edit-all ns-reference? sort-arguments))
488488

489+
(defn- node-width [zloc]
490+
(-> zloc z/node n/string count))
491+
492+
(defn- node-column [zloc]
493+
(loop [zloc (z/left* zloc), n 0]
494+
(if (or (nil? zloc) (line-break? zloc))
495+
n
496+
(recur (z/left* zloc)
497+
(if (clojure-whitespace? zloc) n (inc n))))))
498+
499+
(defn- group-separator? [zloc]
500+
(= (z/string zloc) "\n\n"))
501+
502+
(defn- node-group [zloc]
503+
(loop [zloc (z/left* zloc), n 0]
504+
(if (nil? zloc)
505+
n
506+
(recur (z/left* zloc)
507+
(if (group-separator? zloc) (inc n) n)))))
508+
509+
(defn- comma-after? [zloc]
510+
(let [right (z/right* zloc)]
511+
(or (comma? right)
512+
(and (z/whitespace? right) (comma? (z/right* right))))))
513+
514+
(defn- max-group-column-widths [zloc]
515+
(loop [zloc (z/down zloc), max-widths {}]
516+
(if (nil? zloc)
517+
max-widths
518+
(let [width (if (comma-after? zloc)
519+
(inc (node-width zloc))
520+
(node-width zloc))
521+
column (node-column zloc)
522+
group (node-group zloc)]
523+
(recur (z/right zloc)
524+
(update-in max-widths [group column] (fnil max 0) width))))))
525+
526+
(defn- remove-space-right [zloc]
527+
(let [right (z/right* zloc)]
528+
(if (space? right) (z/remove* right) zloc)))
529+
530+
(defn- insert-space-right [zloc n]
531+
(let [right (z/right* zloc)]
532+
(if (comma? right)
533+
(insert-space-right (remove-space-right right) (dec n))
534+
(z/insert-space-right zloc n))))
535+
536+
(defn- set-spacing-right [zloc n]
537+
(-> zloc (remove-space-right) (insert-space-right n)))
538+
539+
(defn- map-children [zloc f]
540+
(if-let [zloc (z/down zloc)]
541+
(loop [zloc zloc]
542+
(let [zloc (f zloc)]
543+
(if-let [zloc (z/right zloc)]
544+
(recur zloc)
545+
(z/up zloc))))
546+
zloc))
547+
548+
(defn- pad-node [zloc width]
549+
(set-spacing-right zloc (- width (node-width zloc))))
550+
551+
(defn- end-of-line? [zloc]
552+
(line-break? (skip-whitespace-and-commas (z/right* zloc))))
553+
554+
(defn- align-form-columns [zloc]
555+
(let [max-widths (max-group-column-widths zloc)]
556+
(map-children zloc #(cond-> %
557+
(and (z/right %) (not (end-of-line? %)))
558+
(pad-node (inc (get-in max-widths [(node-group %) (node-column %)])))))))
559+
560+
(defn align-maps [form]
561+
(transform form edit-all z/map? align-form-columns))
562+
489563
(def default-options
490564
{:indentation? true
491565
:insert-missing-whitespace? true
@@ -495,6 +569,7 @@
495569
:remove-trailing-whitespace? true
496570
:split-keypairs-over-multiple-lines? false
497571
:sort-ns-references? false
572+
:align-maps? false
498573
:indents default-indents
499574
:alias-map {}})
500575

@@ -516,6 +591,8 @@
516591
insert-missing-whitespace)
517592
(cond-> (:remove-multiple-non-indenting-spaces? opts)
518593
remove-multiple-non-indenting-spaces)
594+
(cond-> (:align-maps? opts)
595+
align-maps)
519596
(cond-> (:indentation? opts)
520597
(reindent (:indents opts) (:alias-map opts)))
521598
(cond-> (:remove-trailing-whitespace? opts)

cljfmt/test/cljfmt/core_test.cljc

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,3 +1336,177 @@
13361336
" ^{:x 1} b"
13371337
" [c]))"]
13381338
{:sort-ns-references? true})))
1339+
1340+
(deftest test-align-maps
1341+
(testing "straightforward test cases"
1342+
(testing "sanity"
1343+
(is (reformats-to?
1344+
["(def x 1)"]
1345+
["(def x 1)"]
1346+
{:align-maps? true})))
1347+
(testing "no op 1"
1348+
(is (reformats-to?
1349+
["{:a 1}"]
1350+
["{:a 1}"]
1351+
{:align-maps? true})))
1352+
(testing "no op 2"
1353+
(is (reformats-to?
1354+
["{:a 1"
1355+
" :b 2}"]
1356+
["{:a 1"
1357+
" :b 2}"]
1358+
{:align-maps? true})))
1359+
(testing "empty"
1360+
(is (reformats-to?
1361+
["{}"]
1362+
["{}"]
1363+
{:align-maps? true})))
1364+
(testing "simple"
1365+
(is (reformats-to?
1366+
["{:x 1"
1367+
" :longer 2}"]
1368+
["{:x 1"
1369+
" :longer 2}"]
1370+
{:align-maps? true})))
1371+
(testing "nested simple"
1372+
(is (reformats-to?
1373+
["{:x {:x 1}"
1374+
" :longer 2}"]
1375+
["{:x {:x 1}"
1376+
" :longer 2}"]
1377+
{:align-maps? true})))
1378+
(testing "nested align"
1379+
(is (reformats-to?
1380+
["{:x {:x 1"
1381+
" :longer 2}"
1382+
" :longer 2}"]
1383+
["{:x {:x 1"
1384+
" :longer 2}"
1385+
" :longer 2}"]
1386+
{:align-maps? true})))
1387+
(testing "align many"
1388+
(is (reformats-to?
1389+
["{:a 1"
1390+
" :longer 2"
1391+
" :b 3}"]
1392+
["{:a 1"
1393+
" :longer 2"
1394+
" :b 3}"]
1395+
{:align-maps? true})))
1396+
(testing "preserves comments"
1397+
(is (reformats-to?
1398+
["{:a 1 ;; comment"
1399+
" :longer 2}"]
1400+
["{:a 1 ;; comment"
1401+
" :longer 2}"]
1402+
{:align-maps? true}))))
1403+
(testing "non-trivial test cases"
1404+
(testing "idnentation after align"
1405+
(is (reformats-to?
1406+
["(def m {{:a 1"
1407+
":b 2} [x"
1408+
"y]"
1409+
":d [z]})"]
1410+
["(def m {{:a 1"
1411+
" :b 2} [x"
1412+
" y]"
1413+
" :d [z]})"])))
1414+
(testing "cljs map values"
1415+
(is (reformats-to?
1416+
["{:indents {'thing.core/defthing [[:inner 0]]"
1417+
"'let [[:inner 0]]}"
1418+
"#?@(:cljs [:alias-map {}])}"]
1419+
["{:indents {'thing.core/defthing [[:inner 0]]"
1420+
" 'let [[:inner 0]]}"
1421+
" #?@(:cljs [:alias-map {}])}"]
1422+
{:align-maps? true})))
1423+
(testing "indentation off #1"
1424+
(is (reformats-to?
1425+
["{ :a 1"
1426+
" :longer 2}"]
1427+
["{:a 1"
1428+
" :longer 2}"]
1429+
{:align-maps? true})))
1430+
(testing "indentation off #2"
1431+
(is (reformats-to?
1432+
["{ :a 1"
1433+
" :longer 2}"]
1434+
["{:a 1"
1435+
" :longer 2}"]
1436+
{:align-maps? true})))
1437+
(testing "indentation off #3"
1438+
(is (reformats-to?
1439+
["{:a 1"
1440+
" :longer 2}"]
1441+
["{:a 1"
1442+
" :longer 2}"]
1443+
{:align-maps? true})))
1444+
(testing "columns"
1445+
(testing "multi-value line"
1446+
(is (reformats-to?
1447+
["{:a 1 :b 2"
1448+
" :longer 3}"]
1449+
["{:a 1 :b 2"
1450+
" :longer 3}"]
1451+
{:align-maps? true})))
1452+
(testing "multi-value line"
1453+
(is (reformats-to?
1454+
["{:a 1 :longer-a 2"
1455+
" :longer-b 3 :c 4}"]
1456+
["{:a 1 :longer-a 2"
1457+
" :longer-b 3 :c 4}"]
1458+
{:align-maps? true})))
1459+
(testing "multi-value commas"
1460+
(is (reformats-to?
1461+
["{:a 1, :longer-a 2"
1462+
" :longer-b 3 , :c 4}"]
1463+
["{:a 1, :longer-a 2"
1464+
" :longer-b 3, :c 4}"]
1465+
{:align-maps? true})))
1466+
(testing "multi-value uneven"
1467+
(is (reformats-to?
1468+
["{:a 1 :longer-a 2 :c 3"
1469+
" :longer-b 4 :d 5}"]
1470+
["{:a 1 :longer-a 2 :c 3"
1471+
" :longer-b 4 :d 5}"]
1472+
{:align-maps? true})))
1473+
(testing "multi-value groups 1"
1474+
(is (reformats-to?
1475+
["{:a 1 :longer-a 2"
1476+
" :longer-b 3 :c 4"
1477+
""
1478+
" :d 5 :e 6"
1479+
" :fg 7 :h 8}"]
1480+
["{:a 1 :longer-a 2"
1481+
" :longer-b 3 :c 4"
1482+
""
1483+
" :d 5 :e 6"
1484+
" :fg 7 :h 8}"]
1485+
{:align-maps? true})))
1486+
(testing "multi-value groups 2"
1487+
(is (reformats-to?
1488+
["{:a 1 :longer-a 2"
1489+
" :longer-b 3 :c 4"
1490+
""
1491+
""
1492+
" :d 5 :e 6"
1493+
" :fg 7 :h 8"
1494+
""
1495+
" :i 9 :jklmno 10"
1496+
" :p 11 :q :value}"]
1497+
["{:a 1 :longer-a 2"
1498+
" :longer-b 3 :c 4"
1499+
""
1500+
" :d 5 :e 6"
1501+
" :fg 7 :h 8"
1502+
""
1503+
" :i 9 :jklmno 10"
1504+
" :p 11 :q :value}"]
1505+
{:align-maps? true})))
1506+
(testing "multi-value partial commas"
1507+
(is (reformats-to?
1508+
["{:a 1 :longer-a 2"
1509+
" :longer-b 3 , :c 4}"]
1510+
["{:a 1 :longer-a 2"
1511+
" :longer-b 3, :c 4}"]
1512+
{:align-maps? true}))))))

0 commit comments

Comments
 (0)