Skip to content

Commit

Permalink
align maps
Browse files Browse the repository at this point in the history
  • Loading branch information
rsh-blip committed Mar 22, 2023
1 parent 05ee079 commit 8a6f27e
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 0 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ selectively enabled or disabled:
other references in the `ns` forms at the top of your namespaces.
Defaults to false.

* `:align-maps?` -
True if cljfmt should left align the values of maps.

This will convert:
```clojure
{:foo 1
:barbaz 2}
```
To:
```clojure
{:foo 1
:barbaz 2}
```
Defaults to false.

You can also configure the behavior of cljfmt:

* `:paths` - determines which directories to include in the
Expand Down
87 changes: 87 additions & 0 deletions cljfmt/src/cljfmt/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,90 @@
(defn sort-ns-references [form]
(transform form edit-all ns-reference? sort-arguments))

(defn- node-width [zloc]
(-> zloc z/node n/string count))

(defn- node-column [zloc]
(loop [zloc (z/left* zloc), n 0]
(if (or (nil? zloc) (line-break? zloc))
n
(recur (z/left* zloc)
(if (clojure-whitespace? zloc) n (inc n))))))

(defn- group-separator? [zloc]
(= (z/string zloc) "\n\n"))

(defn- node-group [zloc]
(loop [zloc (z/left* zloc), n 0]
(if (nil? zloc)
n
(recur (z/left* zloc)
(if (group-separator? zloc) (inc n) n)))))

(defn- comma-after? [zloc]
(let [right (z/right* zloc)]
(or (comma? right)
(and (z/whitespace? right) (comma? (z/right* right))))))

(defn- max-group-column-widths [zloc]
(loop [zloc (z/down zloc), max-widths {}]
(if (nil? zloc)
max-widths
(let [width (if (comma-after? zloc)
(inc (node-width zloc))
(node-width zloc))
column (node-column zloc)
group (node-group zloc)]
(recur (z/right zloc)
(update-in max-widths [group column] (fnil max 0) width))))))

(defn- quote? [zloc]
(-> zloc
z/node
n/tag
(= :quote)))

(defn- remove-space-right [zloc]
(let [right (z/right* zloc)]
(if (space? right)
(if (quote? zloc)
(z/up (z/remove* right))
(z/remove* right))
zloc)))

(defn- insert-space-right [zloc n]
(let [right (z/right* zloc)]
(if (comma? right)
(insert-space-right (remove-space-right right) (dec n))
(z/insert-space-right zloc n))))

(defn- set-spacing-right [zloc n]
(-> zloc (remove-space-right) (insert-space-right n)))

(defn- map-children [zloc f]
(if-let [zloc (z/down zloc)]
(loop [zloc zloc]
(let [zloc (f zloc)]
(if-let [zloc (z/right zloc)]
(recur zloc)
(z/up zloc))))
zloc))

(defn- pad-node [zloc width]
(set-spacing-right zloc (- width (node-width zloc))))

(defn- end-of-line? [zloc]
(line-break? (skip-whitespace-and-commas (z/right* zloc))))

(defn- align-form-columns [zloc]
(let [max-widths (max-group-column-widths zloc)]
(map-children zloc #(cond-> %
(and (z/right %) (not (end-of-line? %)))
(pad-node (inc (get-in max-widths [(node-group %) (node-column %)])))))))

(defn align-maps [form]
(transform form edit-all z/map? align-form-columns))

(def default-options
{:indentation? true
:insert-missing-whitespace? true
Expand All @@ -495,6 +579,7 @@
:remove-trailing-whitespace? true
:split-keypairs-over-multiple-lines? false
:sort-ns-references? false
:align-maps? false
:indents default-indents
:alias-map {}})

Expand All @@ -516,6 +601,8 @@
insert-missing-whitespace)
(cond-> (:remove-multiple-non-indenting-spaces? opts)
remove-multiple-non-indenting-spaces)
(cond-> (:align-maps? opts)
align-maps)
(cond-> (:indentation? opts)
(reindent (:indents opts) (:alias-map opts)))
(cond-> (:remove-trailing-whitespace? opts)
Expand Down
174 changes: 174 additions & 0 deletions cljfmt/test/cljfmt/core_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -1336,3 +1336,177 @@
" ^{:x 1} b"
" [c]))"]
{:sort-ns-references? true})))

(deftest test-align-maps
(testing "straightforward test cases"
(testing "sanity"
(is (reformats-to?
["(def x 1)"]
["(def x 1)"]
{:align-maps? true})))
(testing "no op 1"
(is (reformats-to?
["{:a 1}"]
["{:a 1}"]
{:align-maps? true})))
(testing "no op 2"
(is (reformats-to?
["{:a 1"
" :b 2}"]
["{:a 1"
" :b 2}"]
{:align-maps? true})))
(testing "empty"
(is (reformats-to?
["{}"]
["{}"]
{:align-maps? true})))
(testing "simple"
(is (reformats-to?
["{:x 1"
" :longer 2}"]
["{:x 1"
" :longer 2}"]
{:align-maps? true})))
(testing "nested simple"
(is (reformats-to?
["{:x {:x 1}"
" :longer 2}"]
["{:x {:x 1}"
" :longer 2}"]
{:align-maps? true})))
(testing "nested align"
(is (reformats-to?
["{:x {:x 1"
" :longer 2}"
" :longer 2}"]
["{:x {:x 1"
" :longer 2}"
" :longer 2}"]
{:align-maps? true})))
(testing "align many"
(is (reformats-to?
["{:a 1"
" :longer 2"
" :b 3}"]
["{:a 1"
" :longer 2"
" :b 3}"]
{:align-maps? true})))
(testing "preserves comments"
(is (reformats-to?
["{:a 1 ;; comment"
" :longer 2}"]
["{:a 1 ;; comment"
" :longer 2}"]
{:align-maps? true}))))
(testing "non-trivial test cases"
(testing "idnentation after align"
(is (reformats-to?
["(def m {{:a 1"
":b 2} [x"
"y]"
":d [z]})"]
["(def m {{:a 1"
" :b 2} [x"
" y]"
" :d [z]})"])))
(testing "cljs map values"
(is (reformats-to?
["{:indents {'thing.core/defthing [[:inner 0]]"
"'let [[:inner 0]]}"
"#?@(:cljs [:alias-map {}])}"]
["{:indents {'thing.core/defthing [[:inner 0]]"
" 'let [[:inner 0]]}"
" #?@(:cljs [:alias-map {}])}"]
{:align-maps? true})))
(testing "indentation off #1"
(is (reformats-to?
["{ :a 1"
" :longer 2}"]
["{:a 1"
" :longer 2}"]
{:align-maps? true})))
(testing "indentation off #2"
(is (reformats-to?
["{ :a 1"
" :longer 2}"]
["{:a 1"
" :longer 2}"]
{:align-maps? true})))
(testing "indentation off #3"
(is (reformats-to?
["{:a 1"
" :longer 2}"]
["{:a 1"
" :longer 2}"]
{:align-maps? true})))
(testing "columns"
(testing "multi-value line"
(is (reformats-to?
["{:a 1 :b 2"
" :longer 3}"]
["{:a 1 :b 2"
" :longer 3}"]
{:align-maps? true})))
(testing "multi-value line"
(is (reformats-to?
["{:a 1 :longer-a 2"
" :longer-b 3 :c 4}"]
["{:a 1 :longer-a 2"
" :longer-b 3 :c 4}"]
{:align-maps? true})))
(testing "multi-value commas"
(is (reformats-to?
["{:a 1, :longer-a 2"
" :longer-b 3 , :c 4}"]
["{:a 1, :longer-a 2"
" :longer-b 3, :c 4}"]
{:align-maps? true})))
(testing "multi-value uneven"
(is (reformats-to?
["{:a 1 :longer-a 2 :c 3"
" :longer-b 4 :d 5}"]
["{:a 1 :longer-a 2 :c 3"
" :longer-b 4 :d 5}"]
{:align-maps? true})))
(testing "multi-value groups 1"
(is (reformats-to?
["{:a 1 :longer-a 2"
" :longer-b 3 :c 4"
""
" :d 5 :e 6"
" :fg 7 :h 8}"]
["{:a 1 :longer-a 2"
" :longer-b 3 :c 4"
""
" :d 5 :e 6"
" :fg 7 :h 8}"]
{:align-maps? true})))
(testing "multi-value groups 2"
(is (reformats-to?
["{:a 1 :longer-a 2"
" :longer-b 3 :c 4"
""
""
" :d 5 :e 6"
" :fg 7 :h 8"
""
" :i 9 :jklmno 10"
" :p 11 :q :value}"]
["{:a 1 :longer-a 2"
" :longer-b 3 :c 4"
""
" :d 5 :e 6"
" :fg 7 :h 8"
""
" :i 9 :jklmno 10"
" :p 11 :q :value}"]
{:align-maps? true})))
(testing "multi-value partial commas"
(is (reformats-to?
["{:a 1 :longer-a 2"
" :longer-b 3 , :c 4}"]
["{:a 1 :longer-a 2"
" :longer-b 3, :c 4}"]
{:align-maps? true}))))))

0 comments on commit 8a6f27e

Please sign in to comment.