Skip to content

Commit 99daa0d

Browse files
rsh-blipreutsharabani
authored andcommitted
align maps
1 parent fc3340d commit 99daa0d

File tree

5 files changed

+280
-0
lines changed

5 files changed

+280
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ pom.xml.asc
1010
reports
1111
.cpcache
1212
.clj-kondo
13+
.lsp

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,23 @@ In order to load the standard configuration file from Leiningen, add the
288288

289289
Defaults to `:community`
290290

291+
* `:align-maps?` -
292+
True if cljfmt should left align the values of maps.
293+
294+
This will convert:
295+
```clojure
296+
{:foo 1
297+
:barbaz 2}
298+
```
299+
To:
300+
```clojure
301+
{:foo 1
302+
:barbaz 2}
303+
```
304+
Defaults to `false`.
305+
306+
You can also configure the behavior of cljfmt:
307+
291308
[indents.md]: docs/INDENTS.md
292309
[community style recommendation]: https://guide.clojure.style/#one-space-indent
293310

cljfmt/src/cljfmt/core.cljc

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@
348348
:function-arguments-indentation :community
349349
:indents default-indents
350350
:extra-indents {}
351+
:align-maps? false
351352
:alias-map {}})
352353

353354
(defmulti ^:private indenter-fn
@@ -555,6 +556,90 @@
555556
(defn sort-ns-references [form]
556557
(transform form edit-all ns-reference? sort-arguments))
557558

559+
(defn- node-width [zloc]
560+
(-> zloc z/node n/string count))
561+
562+
(defn- node-column [zloc]
563+
(loop [zloc (z/left* zloc), n 0]
564+
(if (or (nil? zloc) (line-break? zloc))
565+
n
566+
(recur (z/left* zloc)
567+
(if (clojure-whitespace? zloc) n (inc n))))))
568+
569+
(defn- group-separator? [zloc]
570+
(= (z/string zloc) "\n\n"))
571+
572+
(defn- node-group [zloc]
573+
(loop [zloc (z/left* zloc), n 0]
574+
(if (nil? zloc)
575+
n
576+
(recur (z/left* zloc)
577+
(if (group-separator? zloc) (inc n) n)))))
578+
579+
(defn- comma-after? [zloc]
580+
(let [right (z/right* zloc)]
581+
(or (comma? right)
582+
(and (z/whitespace? right) (comma? (z/right* right))))))
583+
584+
(defn- max-group-column-widths [zloc]
585+
(loop [zloc (z/down zloc), max-widths {}]
586+
(if (nil? zloc)
587+
max-widths
588+
(let [width (if (comma-after? zloc)
589+
(inc (node-width zloc))
590+
(node-width zloc))
591+
column (node-column zloc)
592+
group (node-group zloc)]
593+
(recur (z/right zloc)
594+
(update-in max-widths [group column] (fnil max 0) width))))))
595+
596+
(defn- quote? [zloc]
597+
(-> zloc
598+
z/node
599+
n/tag
600+
(= :quote)))
601+
602+
(defn- remove-space-right [zloc]
603+
(let [right (z/right* zloc)]
604+
(if (space? right)
605+
(if (quote? zloc)
606+
(z/up (z/remove* right))
607+
(z/remove* right))
608+
zloc)))
609+
610+
(defn- insert-space-right [zloc n]
611+
(let [right (z/right* zloc)]
612+
(if (comma? right)
613+
(insert-space-right (remove-space-right right) (dec n))
614+
(z/insert-space-right zloc n))))
615+
616+
(defn- set-spacing-right [zloc n]
617+
(-> zloc (remove-space-right) (insert-space-right n)))
618+
619+
(defn- map-children [zloc f]
620+
(if-let [zloc (z/down zloc)]
621+
(loop [zloc zloc]
622+
(let [zloc (f zloc)]
623+
(if-let [zloc (z/right zloc)]
624+
(recur zloc)
625+
(z/up zloc))))
626+
zloc))
627+
628+
(defn- pad-node [zloc width]
629+
(set-spacing-right zloc (- width (node-width zloc))))
630+
631+
(defn- end-of-line? [zloc]
632+
(line-break? (skip-whitespace-and-commas (z/right* zloc))))
633+
634+
(defn- align-form-columns [zloc]
635+
(let [max-widths (max-group-column-widths zloc)]
636+
(map-children zloc #(cond-> %
637+
(and (z/right %) (not (end-of-line? %)))
638+
(pad-node (inc (get-in max-widths [(node-group %) (node-column %)])))))))
639+
640+
(defn align-maps [form]
641+
(transform form edit-all z/map? align-form-columns))
642+
558643
(defn reformat-form
559644
([form]
560645
(reformat-form form {}))
@@ -573,6 +658,8 @@
573658
insert-missing-whitespace)
574659
(cond-> (:remove-multiple-non-indenting-spaces? opts)
575660
remove-multiple-non-indenting-spaces)
661+
(cond-> (:align-maps? opts)
662+
align-maps)
576663
(cond-> (:indentation? opts)
577664
(reindent (merge (:indents opts) (:extra-indents opts))
578665
(:alias-map opts)

cljfmt/test/cljfmt/core_test.cljc

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,3 +1845,177 @@
18451845

18461846
(deftest test-clojure-12-syntax
18471847
(is (reformats-to? ["^Long/1 a"] ["^Long/1 a"])))
1848+
1849+
(deftest test-align-maps
1850+
(testing "straightforward test cases"
1851+
(testing "sanity"
1852+
(is (reformats-to?
1853+
["(def x 1)"]
1854+
["(def x 1)"]
1855+
{:align-maps? true})))
1856+
(testing "no op 1"
1857+
(is (reformats-to?
1858+
["{:a 1}"]
1859+
["{:a 1}"]
1860+
{:align-maps? true})))
1861+
(testing "no op 2"
1862+
(is (reformats-to?
1863+
["{:a 1"
1864+
" :b 2}"]
1865+
["{:a 1"
1866+
" :b 2}"]
1867+
{:align-maps? true})))
1868+
(testing "empty"
1869+
(is (reformats-to?
1870+
["{}"]
1871+
["{}"]
1872+
{:align-maps? true})))
1873+
(testing "simple"
1874+
(is (reformats-to?
1875+
["{:x 1"
1876+
" :longer 2}"]
1877+
["{:x 1"
1878+
" :longer 2}"]
1879+
{:align-maps? true})))
1880+
(testing "nested simple"
1881+
(is (reformats-to?
1882+
["{:x {:x 1}"
1883+
" :longer 2}"]
1884+
["{:x {:x 1}"
1885+
" :longer 2}"]
1886+
{:align-maps? true})))
1887+
(testing "nested align"
1888+
(is (reformats-to?
1889+
["{:x {:x 1"
1890+
" :longer 2}"
1891+
" :longer 2}"]
1892+
["{:x {:x 1"
1893+
" :longer 2}"
1894+
" :longer 2}"]
1895+
{:align-maps? true})))
1896+
(testing "align many"
1897+
(is (reformats-to?
1898+
["{:a 1"
1899+
" :longer 2"
1900+
" :b 3}"]
1901+
["{:a 1"
1902+
" :longer 2"
1903+
" :b 3}"]
1904+
{:align-maps? true})))
1905+
(testing "preserves comments"
1906+
(is (reformats-to?
1907+
["{:a 1 ;; comment"
1908+
" :longer 2}"]
1909+
["{:a 1 ;; comment"
1910+
" :longer 2}"]
1911+
{:align-maps? true}))))
1912+
(testing "non-trivial test cases"
1913+
(testing "idnentation after align"
1914+
(is (reformats-to?
1915+
["(def m {{:a 1"
1916+
":b 2} [x"
1917+
"y]"
1918+
":d [z]})"]
1919+
["(def m {{:a 1"
1920+
" :b 2} [x"
1921+
" y]"
1922+
" :d [z]})"])))
1923+
(testing "cljs map values"
1924+
(is (reformats-to?
1925+
["{:indents {'thing.core/defthing [[:inner 0]]"
1926+
"'let [[:inner 0]]}"
1927+
"#?@(:cljs [:alias-map {}])}"]
1928+
["{:indents {'thing.core/defthing [[:inner 0]]"
1929+
" 'let [[:inner 0]]}"
1930+
" #?@(:cljs [:alias-map {}])}"]
1931+
{:align-maps? true})))
1932+
(testing "indentation off #1"
1933+
(is (reformats-to?
1934+
["{ :a 1"
1935+
" :longer 2}"]
1936+
["{:a 1"
1937+
" :longer 2}"]
1938+
{:align-maps? true})))
1939+
(testing "indentation off #2"
1940+
(is (reformats-to?
1941+
["{ :a 1"
1942+
" :longer 2}"]
1943+
["{:a 1"
1944+
" :longer 2}"]
1945+
{:align-maps? true})))
1946+
(testing "indentation off #3"
1947+
(is (reformats-to?
1948+
["{:a 1"
1949+
" :longer 2}"]
1950+
["{:a 1"
1951+
" :longer 2}"]
1952+
{:align-maps? true})))
1953+
(testing "columns"
1954+
(testing "multi-value line"
1955+
(is (reformats-to?
1956+
["{:a 1 :b 2"
1957+
" :longer 3}"]
1958+
["{:a 1 :b 2"
1959+
" :longer 3}"]
1960+
{:align-maps? true})))
1961+
(testing "multi-value line"
1962+
(is (reformats-to?
1963+
["{:a 1 :longer-a 2"
1964+
" :longer-b 3 :c 4}"]
1965+
["{:a 1 :longer-a 2"
1966+
" :longer-b 3 :c 4}"]
1967+
{:align-maps? true})))
1968+
(testing "multi-value commas"
1969+
(is (reformats-to?
1970+
["{:a 1, :longer-a 2"
1971+
" :longer-b 3 , :c 4}"]
1972+
["{:a 1, :longer-a 2"
1973+
" :longer-b 3, :c 4}"]
1974+
{:align-maps? true})))
1975+
(testing "multi-value uneven"
1976+
(is (reformats-to?
1977+
["{:a 1 :longer-a 2 :c 3"
1978+
" :longer-b 4 :d 5}"]
1979+
["{:a 1 :longer-a 2 :c 3"
1980+
" :longer-b 4 :d 5}"]
1981+
{:align-maps? true})))
1982+
(testing "multi-value groups 1"
1983+
(is (reformats-to?
1984+
["{:a 1 :longer-a 2"
1985+
" :longer-b 3 :c 4"
1986+
""
1987+
" :d 5 :e 6"
1988+
" :fg 7 :h 8}"]
1989+
["{:a 1 :longer-a 2"
1990+
" :longer-b 3 :c 4"
1991+
""
1992+
" :d 5 :e 6"
1993+
" :fg 7 :h 8}"]
1994+
{:align-maps? true})))
1995+
(testing "multi-value groups 2"
1996+
(is (reformats-to?
1997+
["{:a 1 :longer-a 2"
1998+
" :longer-b 3 :c 4"
1999+
""
2000+
""
2001+
" :d 5 :e 6"
2002+
" :fg 7 :h 8"
2003+
""
2004+
" :i 9 :jklmno 10"
2005+
" :p 11 :q :value}"]
2006+
["{:a 1 :longer-a 2"
2007+
" :longer-b 3 :c 4"
2008+
""
2009+
" :d 5 :e 6"
2010+
" :fg 7 :h 8"
2011+
""
2012+
" :i 9 :jklmno 10"
2013+
" :p 11 :q :value}"]
2014+
{:align-maps? true})))
2015+
(testing "multi-value partial commas"
2016+
(is (reformats-to?
2017+
["{:a 1 :longer-a 2"
2018+
" :longer-b 3 , :c 4}"]
2019+
["{:a 1 :longer-a 2"
2020+
" :longer-b 3, :c 4}"]
2021+
{:align-maps? true}))))))

install.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ echo -n "Downloading cljfmt binaries... "
3737
curl -o /tmp/cljfmt.tar.gz -sL "$URL"
3838
echo "Done!"
3939

40+
sudo mkdir -p /usr/local/bin
4041
sudo tar -xzf /tmp/cljfmt.tar.gz -C /usr/local/bin
4142
echo "Extracted cljfmt into /usr/local/bin"
4243

0 commit comments

Comments
 (0)