Skip to content

Commit d56cb64

Browse files
committed
Remove blank lines in forms
1 parent 9dad2dd commit d56cb64

File tree

4 files changed

+196
-17
lines changed

4 files changed

+196
-17
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,10 +247,23 @@ In order to load the standard configuration file from Leiningen, add the
247247
true if cljfmt should align the keys and values of maps such that they
248248
line up in columns. Defaults to false. **Experimental.**
249249

250+
* `:blank-line-forms` - map of symbols that tell cljfmt which forms are allowed
251+
to have blank lines inside of them. The value may be either `:all`, which
252+
means blank lines are allowed between all elements in the form, e.g. `{'cond
253+
:all}` to allow blank lines between any of the elements inside `cond`; or it
254+
may be a set of element indexes that are allowed to have blank lines, e.g.
255+
`{'let #{0}}`, to allow blank lines in the binding of a `let` form. This
256+
option will **replace** the default blank line forms; use `:extra-blank-line`
257+
forms to add additional ones. Used by `:remove-blank-lines-in-forms?`.
258+
**Experimental.**
259+
250260
* `:extra-aligned-forms` -
251261
the same as `:aligned-forms`, except that this will **append** to the
252262
default aligned forms. **Experimental.**
253263

264+
* `:extra-blank-line-forms` - the same as `:blank-line-forms`, except that this
265+
will **append** to the default blank line forms. **Experimental.**
266+
254267
* `:extra-indents` -
255268
the same as `:indents`, except that this will **append** to the
256269
default indents.
@@ -281,6 +294,12 @@ In order to load the standard configuration file from Leiningen, add the
281294
See [INDENTS.md][] for a complete explanation. This will **replace**
282295
the default indents.
283296

297+
* `:remove-blank-lines-in-forms?` - whether to remove blank lines inside forms
298+
per the [community style recommendation][no-blank-lines]. By default, this
299+
does not remove blank lines in pairwise constructs like `cond` or within
300+
binding vectors; configure these with `:blank-line-forms` and/or
301+
`:extra-blank-line-forms`. Defaults to false. **Experimental**.
302+
284303
* `:remove-consecutive-blank-lines?` -
285304
true if cljfmt should collapse consecutive blank lines. This will
286305
convert `(foo)\n\n\n(bar)` to `(foo)\n\n(bar)`. Defaults to true.
@@ -315,6 +334,7 @@ In order to load the standard configuration file from Leiningen, add the
315334

316335
[indents.md]: docs/INDENTS.md
317336
[community style recommendation]: https://guide.clojure.style/#one-space-indent
337+
[no-blank-lines]: https://guide.clojure.style/#no-blank-lines-within-def-forms
318338

319339
### Runtime Options
320340

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{binding #{0}
2+
comment :all
3+
cond :all
4+
doseq #{0}
5+
for #{0}
6+
go-loop #{0}
7+
let #{0}
8+
loop #{0}
9+
with-local-vars #{0}
10+
with-open #{0}
11+
with-redefs #{0}}

cljfmt/src/cljfmt/core.cljc

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -358,18 +358,24 @@
358358
(def default-aligned-forms
359359
(read-resource "cljfmt/aligned_forms/clojure.clj"))
360360

361+
(def blank-line-forms
362+
(read-resource "cljfmt/blank_line_forms/clojure.clj"))
363+
361364
(def default-options
362365
{:alias-map {}
363366
:align-binding-columns? false
364367
:align-map-columns? false
365368
:aligned-forms default-aligned-forms
369+
:blank-line-forms blank-line-forms
366370
:extra-aligned-forms {}
371+
:extra-blank-line-forms {}
367372
:extra-indents {}
368373
:function-arguments-indentation :community
369374
:indent-line-comments? false
370375
:indentation? true
371376
:indents default-indents
372377
:insert-missing-whitespace? true
378+
:remove-blank-lines-in-forms? false
373379
:remove-consecutive-blank-lines? true
374380
:remove-multiple-non-indenting-spaces? false
375381
:remove-surrounding-whitespace? true
@@ -678,7 +684,8 @@
678684
(and (z/list? (z/up zloc))
679685
(some (fn [[k indexes]]
680686
(and (form-matches-key? zloc k context)
681-
(contains? (set indexes) (dec (index-of zloc)))))
687+
(or (= :all indexes)
688+
(contains? (set indexes) (dec (index-of zloc))))))
682689
aligned-forms)))
683690

684691
(defn align-form-columns [form aligned-forms alias-map]
@@ -687,20 +694,40 @@
687694
aligned? #(aligned-form? % aligned-forms context)]
688695
(transform form edit-all aligned? align-columns)))
689696

690-
(defn realign-form
691-
"Realign a rewrite-clj form such that the columns line up into columns."
692-
[form]
693-
(-> form z/of-node align-columns z/root))
694-
695-
(defn- unalign-from-space [zloc]
696-
(pad-node (z/right* zloc) (- 1 (node-str-length zloc))))
697-
698-
(defn unalign-form
699-
"Remove any consecutive non-indenting whitespace within the form."
700-
[form]
701-
(-> form z/of-node z/down
702-
(edit-all non-indenting-whitespace? unalign-from-space z/right*)
703-
z/root))
697+
(defn- blank-line-rule [zloc blank-line-forms context]
698+
(when-let [form-symb (form-symbol zloc)]
699+
(some blank-line-forms
700+
[(remove-namespace form-symb)
701+
(fully-qualified-symbol form-symb context)])))
702+
703+
(defn- blank-line-in-form? [zloc blank-line-forms context]
704+
(and (z/linebreak? zloc)
705+
(> (count-newlines zloc) 1)
706+
(cond
707+
(z/list? (z/up zloc))
708+
(not= (blank-line-rule zloc blank-line-forms context)
709+
:all)
710+
711+
(z/list? (z/up (z/up zloc)))
712+
(let [index (dec (index-of (z/up zloc)))
713+
rule (blank-line-rule
714+
(z/up zloc)
715+
blank-line-forms
716+
context)
717+
allowed-indexes (if (set? rule)
718+
rule
719+
#{})]
720+
(not (allowed-indexes index))))))
721+
722+
(defn- replace-with-single-newline [zloc]
723+
(z/replace zloc (n/newline-node "\n")))
724+
725+
(defn remove-blank-lines-in-forms
726+
[form blank-line-forms alias-map]
727+
(let [ns-name (find-namespace (z/of-node form))
728+
context {:alias-map alias-map, :ns-name ns-name}
729+
blank-line? #(blank-line-in-form? % blank-line-forms context)]
730+
(transform form edit-all blank-line? replace-with-single-newline)))
704731

705732
#?(:clj
706733
(defn- ns-require-form? [zloc]
@@ -747,14 +774,16 @@
747774
(defn reformat-form
748775
"Reformats a rewrite-clj form data structure. Accepts a map of
749776
[formatting options][1]. See also: [[reformat-string]].
750-
777+
751778
[1]: https://github.com/weavejester/cljfmt#formatting-options"
752779
([form]
753780
(reformat-form form {}))
754781
([form options]
755782
(let [opts (merge default-options options)
756783
indents (merge (:indents opts) (:extra-indents opts))
757784
aligned (merge (:aligned-forms opts) (:extra-aligned-forms opts))
785+
blank (merge (:blank-line-forms opts)
786+
(:extra-blank-line-forms opts))
758787
alias-map #?(:clj (merge (alias-map-for-form form)
759788
(stringify-map (:alias-map opts)))
760789
:cljs (stringify-map (:alias-map opts)))]
@@ -778,7 +807,9 @@
778807
(cond-> (:align-form-columns? opts)
779808
(align-form-columns aligned alias-map))
780809
(cond-> (:remove-trailing-whitespace? opts)
781-
remove-trailing-whitespace)))))
810+
remove-trailing-whitespace)
811+
(cond-> (:remove-blank-lines-in-forms? opts)
812+
(remove-blank-lines-in-forms blank alias-map))))))
782813

783814
(defn reformat-string
784815
"Reformat a string. Accepts a map of [formatting options][1].

cljfmt/test/cljfmt/core_test.cljc

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,123 @@
11881188
""])
11891189
"blank lines at end of string are not affected by :remove-consecutive-blank-lines?"))
11901190

1191+
(deftest test-blank-lines-in-forms
1192+
(is (reformats-to?
1193+
["(defn x []"
1194+
" (do-something!)"
1195+
""
1196+
" (do-something-else!)"
1197+
""
1198+
""
1199+
" (do-a-third-thing!))"]
1200+
["(defn x []"
1201+
" (do-something!)"
1202+
" (do-something-else!)"
1203+
" (do-a-third-thing!))"]
1204+
{:remove-blank-lines-in-forms? true}))
1205+
(is (reformats-to?
1206+
["(let [x 1"
1207+
" y 2]"
1208+
" (do-something!)"
1209+
""
1210+
" (do-something-else!))"]
1211+
["(let [x 1"
1212+
" y 2]"
1213+
" (do-something!)"
1214+
" (do-something-else!))"]
1215+
{:remove-blank-lines-in-forms? true}))
1216+
(testing "ignore pairwise forms"
1217+
(testing "known clojure.core forms"
1218+
(testing "cond"
1219+
(let [form ["(cond"
1220+
" (map? x)"
1221+
" :map"
1222+
""
1223+
" (sequential? x)"
1224+
" :seq)"]]
1225+
(is (reformats-to? form form {:remove-blank-lines-in-forms? true})))))
1226+
(testing "custom forms in :extra-blank-line-forms"
1227+
(testing "from another namespace"
1228+
(let [form ["(ns x"
1229+
" (:require [better-cond.core :as b]))"
1230+
""
1231+
"(b/cond*"
1232+
" (map? x)"
1233+
" :map"
1234+
""
1235+
" (sequential? x)"
1236+
" :seq)"]]
1237+
(is (reformats-to?
1238+
form
1239+
form
1240+
{:remove-blank-lines-in-forms? true
1241+
:extra-indents '{better-cond.core/cond* [[:block 0]]}
1242+
:extra-blank-line-forms '{better-cond.core/cond* :all}
1243+
#?@(:cljs [:alias-map {"b" "better-cond.core"}])}))))
1244+
(testing "from the current namespace"
1245+
(let [form ["(ns better-cond.core)"
1246+
""
1247+
"(cond*"
1248+
" (map? x)"
1249+
" :map"
1250+
""
1251+
" (sequential? x)"
1252+
" :seq)"]]
1253+
(is (reformats-to?
1254+
form
1255+
form
1256+
{:remove-blank-lines-in-forms? true
1257+
:extra-indents '{better-cond.core/cond* [[:block 0]]}
1258+
:extra-blank-line-forms '{better-cond.core/cond* :all}})))))
1259+
(testing "handle reader conditionals"
1260+
(let [form ["(#?(:clj cond :cljs cond)"
1261+
" (map? x)"
1262+
" :map"
1263+
""
1264+
" (sequential? x)"
1265+
" :seq)"]]
1266+
(is (reformats-to? form form {:remove-blank-lines-in-forms? true}))))
1267+
(testing (str ":blank-line-forms :all should only exempt the immediate"
1268+
" children of the form")
1269+
(is (reformats-to?
1270+
["(cond"
1271+
" (= x :a)"
1272+
" :a"
1273+
""
1274+
" (= x :b)"
1275+
" (do"
1276+
" (do-something!)"
1277+
""
1278+
" {:x x}))"]
1279+
["(cond"
1280+
" (= x :a)"
1281+
" :a"
1282+
""
1283+
" (= x :b)"
1284+
" (do"
1285+
" (do-something!)"
1286+
" {:x x}))"]
1287+
{:remove-blank-lines-in-forms? true}))))
1288+
(testing "ignore binding forms"
1289+
(let [form ["(let [a"
1290+
" 1"
1291+
""
1292+
" b"
1293+
" 2]"
1294+
" (+ a b))"]]
1295+
(is (reformats-to? form form {:remove-blank-lines-in-forms? true}))))
1296+
(testing "ignore top-level newlines"
1297+
(let [form ["(def x 1)"
1298+
""
1299+
"(def x 2)"]]
1300+
(is (reformats-to? form form {:remove-blank-lines-in-forms? true}))))
1301+
(testing "Ignore blank lines when parent and/or grandparent forms are not lists"
1302+
(let [form ["[:a"
1303+
" [:b"
1304+
""
1305+
" :c]]"]]
1306+
(is (reformats-to? form form {:remove-blank-lines-in-forms? true})))))
1307+
11911308
(deftest test-trailing-whitespace
11921309
(testing "trailing-whitespace removed"
11931310
(is (reformats-to?

0 commit comments

Comments
 (0)