diff --git a/README.md b/README.md index 35af4ab9..51ceffc1 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,13 @@ In order to load the standard configuration file from Leiningen, add the Defaults to `:community` +* `:ignore-lines-with-only-uneval-tags?` - + true if cljfmt should preserve linebreaks after `#_` uneval tags that + appear on their own line. When true (default), `#_` followed by a newline + will not be moved to the same line as the following form. When false, + `#_` tags will be formatted onto the same line as the form they comment + out. Defaults to true. + * `:indent-line-comments?` - true if cljfmt should align whole-line `;;` comments with the code. Defaults to false. diff --git a/cljfmt/src/cljfmt/core.cljc b/cljfmt/src/cljfmt/core.cljc index 1d8b29c4..c47fda76 100644 --- a/cljfmt/src/cljfmt/core.cljc +++ b/cljfmt/src/cljfmt/core.cljc @@ -61,9 +61,16 @@ (defn- comment? [zloc] (some-> zloc z/node n/comment?)) -(defn- surrounding-whitespace? [zloc] +(defn- uneval? [zloc] + (= (z/tag zloc) :uneval)) + +(defn- surrounding-whitespace? [zloc opts] (and (not (top? zloc)) (clojure-whitespace? zloc) + (not (and (:ignore-lines-with-only-uneval-tags? opts) + (z/linebreak? zloc) + (nil? (z/left* zloc)) + (uneval? (z/up* zloc)))) (or (and (nil? (z/left* zloc)) ;; don't convert ~ @ to ~@ (not (unquote-deref? (z/right* zloc))) @@ -71,9 +78,6 @@ (not (comment? (z/right* zloc)))) (nil? (z/skip z/right* clojure-whitespace? zloc))))) -(defn remove-surrounding-whitespace [form] - (transform form edit-all surrounding-whitespace? z/remove*)) - (defn- element? [zloc] (and zloc (not (z/whitespace-or-comment? zloc)))) @@ -209,9 +213,6 @@ (defn- coll-indent [zloc] (-> zloc z/leftmost* margin)) -(defn- uneval? [zloc] - (= (z/tag zloc) :uneval)) - (defn- index-of [zloc] (->> (iterate z/left zloc) (remove uneval?) @@ -236,11 +237,24 @@ :cursive (cursive-two-space-list-indent? zloc) :zprint (zprint-two-space-list-indent? zloc))) +(defn- first-form-in-line? [zloc] + (if-let [zloc (z/left* zloc)] + (if (space? zloc) + (recur zloc) + (or (z/linebreak? zloc) (comment? zloc))) + true)) + (defn- list-indent [zloc context] - (if (> (index-of zloc) 1) - (-> zloc z/leftmost* z/right margin) - (cond-> (coll-indent zloc) - (two-space-list-indent? zloc context) inc))) + (let [idx (index-of zloc) + has-uneval-before? #(and (= idx 1) (-> zloc z/left* uneval?))] + (if (or (> idx 1) (has-uneval-before?)) + (let [first-arg (-> zloc z/leftmost* z/right)] + (cond-> (margin first-arg) + (not (or (not (uneval? first-arg)) (first-form-in-line? first-arg))) + inc)) + (cond-> (coll-indent zloc) + (two-space-list-indent? zloc context) + inc)))) (def indent-size 2) @@ -340,14 +354,6 @@ (z/leftmost zloc) (repeat n z/right))) -(defn- first-form-in-line? [zloc] - (and (some? zloc) - (if-let [zloc (z/left* zloc)] - (if (space? zloc) - (recur zloc) - (or (z/linebreak? zloc) (comment? zloc))) - true))) - (defn- block-indent [zloc key idx context] (when (form-matches-key? zloc key context) (let [zloc-after-idx (some-> zloc (nth-form (inc idx)))] @@ -378,6 +384,7 @@ :extra-blank-line-forms {} :extra-indents {} :function-arguments-indentation :community + :ignore-lines-with-only-uneval-tags? true :indent-line-comments? false :indentation? true :indents default-indents @@ -405,6 +412,12 @@ (when (form-matches-key? zloc sym context) (list-indent zloc context)))) +(defn remove-surrounding-whitespace + ([form] + (remove-surrounding-whitespace form default-options)) + ([form opts] + (transform form edit-all #(surrounding-whitespace? % opts) z/remove*))) + (defn- make-indenter [[key opts] context] (apply some-fn (map (partial indenter-fn key context) opts))) @@ -433,6 +446,8 @@ (reader-conditional? gp) (coll-indent zloc) (#{:list :fn} tag) (custom-indent zloc indents context) (= :meta tag) (indent-amount (z/up zloc) indents context) + (= :uneval tag) (cond-> (margin (z/up zloc)) + (not (first-form-in-line? (z/up zloc))) inc) :else (coll-indent zloc)))) (defn- indent-line [zloc indents context] @@ -827,7 +842,7 @@ (cond-> (:remove-consecutive-blank-lines? opts) remove-consecutive-blank-lines) (cond-> (:remove-surrounding-whitespace? opts) - remove-surrounding-whitespace) + (remove-surrounding-whitespace opts)) (cond-> (:insert-missing-whitespace? opts) insert-missing-whitespace) (cond-> (:remove-multiple-non-indenting-spaces? opts) diff --git a/cljfmt/src/cljfmt/main.clj b/cljfmt/src/cljfmt/main.clj index a2ad90ca..6c124218 100644 --- a/cljfmt/src/cljfmt/main.clj +++ b/cljfmt/src/cljfmt/main.clj @@ -38,6 +38,9 @@ :validate [#{:community :cursive :zprint} "Must be one of community, cursive, or zprint"] :id :function-arguments-indentation] + [nil "--[no-]ignore-lines-with-only-uneval-tags" + :default (:ignore-lines-with-only-uneval-tags? defaults) + :id :ignore-lines-with-only-uneval-tags?] [nil "--[no-]indentation" :default (:indentation? defaults) :id :indentation?] diff --git a/cljfmt/test/cljfmt/core_test.cljc b/cljfmt/test/cljfmt/core_test.cljc index a2e5860c..ff831f82 100644 --- a/cljfmt/test/cljfmt/core_test.cljc +++ b/cljfmt/test/cljfmt/core_test.cljc @@ -1039,7 +1039,160 @@ ["#?(:cljs (bar 1) :clj (foo 2))"])) (is (reformats-to? ["#?@(:cljs[foo bar] :clj[baz quux])"] - ["#?@(:cljs [foo bar] :clj [baz quux])"])))) + ["#?@(:cljs [foo bar] :clj [baz quux])"]))) + + (testing "uneval on own line" + (is (reformats-to? + ["#_" + "(defn foobar []" + " (do-this)" + " (let [x 1]" + " (with-blah []" + " (stuff))))"] + ["#_" + "(defn foobar []" + " (do-this)" + " (let [x 1]" + " (with-blah []" + " (stuff))))"] + {:ignore-lines-with-only-uneval-tags? true})) + (is (reformats-to? + ["#_" + "{:foo :bar}"] + ["#_" + "{:foo :bar}"] + {:ignore-lines-with-only-uneval-tags? true})) + (is (reformats-to? + ["(foo #_" + " bar" + " baz)"] + ["(foo #_" + " bar" + " baz)"] + {:ignore-lines-with-only-uneval-tags? true})) + (testing "uneval on same line still gets whitespace" + (is (reformats-to? + ["(foo #_bar baz)"] + ["(foo #_bar baz)"] + {:ignore-lines-with-only-uneval-tags? true}))) + (testing "multiple unevals on own lines" + (is (reformats-to? + ["#_" + "(defn foo [])" + "#_" + "(defn bar [])"] + ["#_" + "(defn foo [])" + "#_" + "(defn bar [])"] + {:ignore-lines-with-only-uneval-tags? true}))) + (testing "uneval with vector" + (is (reformats-to? + ["#_" + "[1 2 3]"] + ["#_" + "[1 2 3]"] + {:ignore-lines-with-only-uneval-tags? true}))) + (testing "uneval with map" + (is (reformats-to? + ["#_" + "{:a 1" + " :b 2}"] + ["#_" + "{:a 1" + " :b 2}"] + {:ignore-lines-with-only-uneval-tags? true}))) + (testing "uneval as function argument" + (is (reformats-to? + ["(foo" + " #_" + " bar" + " baz)"] + ["(foo" + " #_" + " bar" + " baz)"] + {:ignore-lines-with-only-uneval-tags? true})) + (testing "with more than one argument" + (is (reformats-to? + ["(foo" + " #_" + " x" + " y" + " z)"] + ["(foo" + " #_" + " x" + " y" + " z)"] + {:ignore-lines-with-only-uneval-tags? true})))) + (testing "uneval before closing paren" + (is (reformats-to? + ["(foo bar" + " #_" + " baz)"] + ["(foo bar" + " #_" + " baz)"] + {:ignore-lines-with-only-uneval-tags? true}))) + (testing "multiple unevals inline" + (is (reformats-to? + ["(foo #_ bar #_ baz qux)"] + ["(foo #_bar #_baz qux)"] + {:ignore-lines-with-only-uneval-tags? true}))) + (testing "uneval with reader conditional" + (is (reformats-to? + ["#_" + "#?(:clj (foo)" + " :cljs (bar))"] + ["#_" + "#?(:clj (foo)" + " :cljs (bar))"] + {:ignore-lines-with-only-uneval-tags? true}))) + (testing "uneval with cursive indentation" + (is (reformats-to? + ["(foo" + " #_" + " bar" + " baz)"] + ["(foo" + " #_" + " bar" + " baz)"] + {:function-arguments-indentation :cursive + :ignore-lines-with-only-uneval-tags? true}))) + (testing "uneval inline with cursive indentation" + (is (reformats-to? + ["(foo #_" + " bar" + " baz)"] + ["(foo #_" + " bar" + " baz)"] + {:function-arguments-indentation :cursive + :ignore-lines-with-only-uneval-tags? true}))) + (testing "uneval with zprint indentation" + (is (reformats-to? + ["(foo" + " #_" + " bar" + " baz)"] + ["(foo" + " #_" + " bar" + " baz)"] + {:function-arguments-indentation :zprint + :ignore-lines-with-only-uneval-tags? true}))) + (testing "uneval inline with zprint indentation" + (is (reformats-to? + ["(foo #_" + " bar" + " baz)"] + ["(foo #_" + " bar" + " baz)"] + {:function-arguments-indentation :zprint + :ignore-lines-with-only-uneval-tags? true}))))) (deftest test-consecutive-blank-lines (is (reformats-to?