Skip to content

Commit 23daaf0

Browse files
committed
Add optional sorting for ns references
Add :sort-ns-references? option (default false) that will sort the references (requires, includes etc.) contained in a ns form. Sorting is alphanumeric and ignores brackets and metadata when determing order. Closes #251.
1 parent 7a20ec9 commit 23daaf0

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

cljfmt/src/cljfmt/core.cljc

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,90 @@
400400
(defn remove-multiple-non-indenting-spaces [form]
401401
(transform form edit-all non-indenting-whitespace? replace-with-one-space))
402402

403+
(def ^:private ns-reference-symbols
404+
#{:import :require :require-macros :use})
405+
406+
(defn- ns-reference? [zloc]
407+
(and (z/list? zloc)
408+
(some-> zloc z/up ns-form?)
409+
(-> zloc z/sexpr first ns-reference-symbols)))
410+
411+
(defn- re-indexes [re s]
412+
(let [matcher #?(:clj (re-matcher re s)
413+
:cljs (js/RegExp. (.-source re) "g"))
414+
next-match #?(:clj #(when (.find matcher)
415+
[(.start matcher) (.end matcher)])
416+
:cljs #(when-let [result (.exec matcher s)]
417+
[(.-index result) (.-lastIndex matcher)]))]
418+
(take-while some? (repeatedly next-match))))
419+
420+
(defn- re-seq-matcher [re charmap coll]
421+
{:pre (every? charmap coll)}
422+
(let [s (apply str (map charmap coll))
423+
v (vec coll)]
424+
(for [[start end] (re-indexes re s)]
425+
{:value (subvec v start end)
426+
:start start
427+
:end end})))
428+
429+
(defn- find-elements-with-comments [nodes]
430+
(re-seq-matcher #"(CNS*)*E(S*C)?"
431+
#(case (n/tag %)
432+
(:whitespace :comma) \S
433+
:comment \C
434+
:newline \N
435+
\E)
436+
nodes))
437+
438+
(defn- splice-into [coll splices]
439+
(letfn [(splice [v i splices]
440+
(when-let [[{:keys [value start end]} & splices] (seq splices)]
441+
(lazy-cat (subvec v i start) value (splice v end splices))))]
442+
(splice (vec coll) 0 splices)))
443+
444+
(defn- add-newlines-after-comments [nodes]
445+
(mapcat #(if (n/comment? %) [% (n/newlines 1)] [%]) nodes))
446+
447+
(defn- remove-newlines-after-comments [nodes]
448+
(mapcat #(when-not (and %1 (n/comment? %1) (n/linebreak? %2)) [%2])
449+
(cons nil nodes)
450+
nodes))
451+
452+
(defn- sort-node-arguments-by [f nodes]
453+
(let [nodes (add-newlines-after-comments nodes)
454+
args (rest (find-elements-with-comments nodes))
455+
sorted (sort-by f (map :value args))]
456+
(->> sorted
457+
(map #(assoc %1 :value %2) args)
458+
(splice-into nodes)
459+
(remove-newlines-after-comments))))
460+
461+
(defn- update-children [zloc f]
462+
(let [node (z/node zloc)]
463+
(z/replace zloc (n/replace-children node (f (n/children node))))))
464+
465+
(defn- nodes-string [nodes]
466+
(apply str (map n/string nodes)))
467+
468+
(defn- remove-node-metadata [nodes]
469+
(mapcat #(if (= (n/tag %) :meta)
470+
(rest (n/children %))
471+
[%])
472+
nodes))
473+
474+
(defn- node-sort-string [nodes]
475+
(-> (remove (some-fn n/comment? n/whitespace?) nodes)
476+
(remove-node-metadata)
477+
(nodes-string)
478+
(str/replace #"[\[\]\(\)\{\}]" "")
479+
(str/trim)))
480+
481+
(defn sort-arguments [zloc]
482+
(update-children zloc #(sort-node-arguments-by node-sort-string %)))
483+
484+
(defn sort-ns-references [form]
485+
(transform form edit-all ns-reference? sort-arguments))
486+
403487
(def default-options
404488
{:indentation? true
405489
:insert-missing-whitespace? true
@@ -408,6 +492,7 @@
408492
:remove-surrounding-whitespace? true
409493
:remove-trailing-whitespace? true
410494
:split-keypairs-over-multiple-lines? false
495+
:sort-ns-references? false
411496
:indents default-indents
412497
:alias-map {}})
413498

@@ -417,6 +502,8 @@
417502
([form opts]
418503
(let [opts (merge default-options opts)]
419504
(-> form
505+
(cond-> (:sort-ns-references? opts)
506+
sort-ns-references)
420507
(cond-> (:split-keypairs-over-multiple-lines? opts)
421508
(split-keypairs-over-multiple-lines))
422509
(cond-> (:remove-consecutive-blank-lines? opts)

cljfmt/test/cljfmt/core_test.cljc

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,3 +1236,71 @@
12361236
(is (= ((wrap-normalize-newlines identity) "foo\nbar\nbaz") "foo\nbar\nbaz"))
12371237
(is (= ((wrap-normalize-newlines identity) "foo\r\nbar\r\nbaz") "foo\r\nbar\r\nbaz"))
12381238
(is (= ((wrap-normalize-newlines identity) "foobarbaz") "foobarbaz")))
1239+
1240+
(deftest test-sort-ns-references
1241+
(is (reformats-to?
1242+
["(ns foo"
1243+
" (:require b c a))"]
1244+
["(ns foo"
1245+
" (:require a b c))"]
1246+
{:sort-ns-references? true}))
1247+
(is (reformats-to?
1248+
["(ns foo"
1249+
" (:require b"
1250+
" c"
1251+
" a))"]
1252+
["(ns foo"
1253+
" (:require a"
1254+
" b"
1255+
" c))"]
1256+
{:sort-ns-references? true}))
1257+
(is (reformats-to?
1258+
["(ns foo"
1259+
" (:require b"
1260+
" [c :as d]"
1261+
" a))"]
1262+
["(ns foo"
1263+
" (:require a"
1264+
" b"
1265+
" [c :as d]))"]
1266+
{:sort-ns-references? true}))
1267+
(is (reformats-to?
1268+
["(ns foo.bar"
1269+
" (:require [c]"
1270+
" [a.b :as b] ;; aabb"
1271+
" ;; bbb"
1272+
" b))"]
1273+
["(ns foo.bar"
1274+
" (:require [a.b :as b] ;; aabb"
1275+
" ;; bbb"
1276+
" b"
1277+
" [c]))"]
1278+
{:sort-ns-references? true}))
1279+
(is (reformats-to?
1280+
["(ns foo.bar"
1281+
" (:require"
1282+
" [c]"
1283+
" [a.b :as b] ;; aabb"
1284+
" ;; bbb"
1285+
" b))"]
1286+
["(ns foo.bar"
1287+
" (:require"
1288+
" [a.b :as b] ;; aabb"
1289+
" ;; bbb"
1290+
" b"
1291+
" [c]))"]
1292+
{:sort-ns-references? true}))
1293+
(is (reformats-to?
1294+
["(ns foo.bar"
1295+
" (:require"
1296+
" [c]"
1297+
" ^:keep a"
1298+
" #?(:clj d)"
1299+
" ^{:x 1} b))"]
1300+
["(ns foo.bar"
1301+
" (:require"
1302+
" #?(:clj d)"
1303+
" ^:keep a"
1304+
" ^{:x 1} b"
1305+
" [c]))"]
1306+
{:sort-ns-references? true})))

0 commit comments

Comments
 (0)