Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

align associative (Attempt to solve issue #36) #299

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}))))
Comment on lines +1396 to +1402
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there!
Does it make sense to also add a test where one of the values is longer than the other?
ex:

    (testing "preserves comments"
      (is (reformats-to?
           ["{:a 1 ;; comment"
            " :longer 200}"]
           ["{:a      1 ;; comment"
            " :longer 200}"]
           {:align-maps? true}))))

or

    (testing "preserves comments"
      (is (reformats-to?
           ["{:a 1 ;; comment"
            " :longer 200}"]
           ["{:a      1   ;; comment"
            " :longer 200}"]
           {:align-maps? true}))))

(I'm actually unsure of what would be the correct expected case, but would guess case1)

(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}))))))