diff --git a/README.md b/README.md index 78fd8f3f..33cdf770 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,9 @@ selectively enabled or disabled: true if cljfmt should collapse consecutive blank lines. This will convert `(foo)\n\n\n(bar)` to `(foo)\n\n(bar)`. Defaults to true. +* `:sort-ns-requires?` - + true if cljfmt should sort namespace `(:require)` vectors lexicographically + Defaults to false. You can also configure the behavior of cljfmt: diff --git a/cljfmt/src/cljfmt/core.cljc b/cljfmt/src/cljfmt/core.cljc index 955419ca..c65dd7d9 100644 --- a/cljfmt/src/cljfmt/core.cljc +++ b/cljfmt/src/cljfmt/core.cljc @@ -297,6 +297,34 @@ (defn remove-trailing-whitespace [form] (transform form edit-all trailing-whitespace? zip/remove)) + +(defn sort-requires + [require-zloc] + (z/replace + require-zloc + (n/replace-children + (z/node require-zloc) + (cons + (n/keyword-node :require) + ; because we want to keep the current whitespace and only reorder the requires + (loop [unsorted-requires (->> require-zloc z/node n/children rest) + sorted-requires (->> unsorted-requires (filter #(= :vector (n/tag %))) (sort #(compare (n/sexpr %1) (n/sexpr %2)))) + new-children []] + (if (empty? unsorted-requires) + new-children + (let [unsorted-require (first unsorted-requires)] + (if (= :vector (n/tag unsorted-require)) + (recur (rest unsorted-requires) (rest sorted-requires) (conj new-children (first sorted-requires))) + (recur (rest unsorted-requires) sorted-requires (conj new-children (first unsorted-requires))))))))))) + +(defn require-node? [zloc] + (and + (= :require (z/sexpr (z/down zloc))) + (= (symbol "ns") (z/sexpr (z/next (z/up zloc)))))) + +(defn sort-ns-requires [form] + (transform form edit-all require-node? sort-requires)) + (defn reformat-form [form & [{:as opts}]] (-> form (cond-> (:remove-consecutive-blank-lines? opts true) @@ -308,7 +336,9 @@ (cond-> (:indentation? opts true) (reindent (:indents opts default-indents))) (cond-> (:remove-trailing-whitespace? opts true) - remove-trailing-whitespace))) + remove-trailing-whitespace) + (cond-> (:sort-ns-requires? opts false) + sort-ns-requires))) (defn reformat-string [form-string & [options]] (-> (p/parse-string-all form-string) diff --git a/cljfmt/test/cljfmt/core_test.cljc b/cljfmt/test/cljfmt/core_test.cljc index 2df1024a..8eec851e 100644 --- a/cljfmt/test/cljfmt/core_test.cljc +++ b/cljfmt/test/cljfmt/core_test.cljc @@ -234,3 +234,24 @@ (is (= (reformat-string "(juxt +' -')") "(juxt +' -')")) (is (= (reformat-string "#\"(?i)foo\"") "#\"(?i)foo\"")) (is (= (reformat-string "#\"a\nb\"") "#\"a\nb\""))) + +(deftest sorting-ns-requires + (is (= (reformat-string "(ns foo)" + {:sort-ns-requires? true}) + "(ns foo)")) + (is (= (reformat-string "(ns foo (:require [b :refer :all] [c :refer :all] [a :refer :all]))" + {:sort-ns-requires? true}) + "(ns foo (:require [a :refer :all] [b :refer :all] [c :refer :all]))")) + (is (= (reformat-string "(ns foo (:require [b :refer :all] [c :refer :all] [a :refer :all])) (def test {:require [1 2 3]})" + {:sort-ns-requires? true}) + "(ns foo (:require [a :refer :all] [b :refer :all] [c :refer :all])) (def test {:require [1 2 3]})")) + (is (= (reformat-string "(ns foo\n (:require [c :refer :all]\n [b :refer :all]\n [a :refer :all]))" + {:sort-ns-requires? true}) + "(ns foo\n (:require [a :refer :all]\n [b :refer :all]\n [c :refer :all]))")) + (is (= (reformat-string "(ns foo\n (:require [c :refer :all]\n [b :refer :all]\n [a :refer :all])\n (:require [d :refer :all]\n [f :refer :all]\n [e :refer :all]))" + {:sort-ns-requires? true}) + "(ns foo\n (:require [a :refer :all]\n [b :refer :all]\n [c :refer :all])\n (:require [d :refer :all]\n [e :refer :all]\n [f :refer :all]))")) + (is (= (reformat-string "(ns foo\n (:require [c :refer :all]\n baz\n [b :refer :all]\n [a :refer :all]\n bar))" + {:sort-ns-requires? true}) + "(ns foo\n (:require [a :refer :all]\n baz\n [b :refer :all]\n [c :refer :all]\n bar))"))) +