Skip to content

Commit

Permalink
Add comment prefix formatting rule.
Browse files Browse the repository at this point in the history
Fixes #30
  • Loading branch information
greglook committed Mar 30, 2021
1 parent 48d52c9 commit bb79e3a
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 47 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
`:break-libs?` which controls whether requires and imports will start on a
new line.
[#35](//github.com/greglook/cljstyle/issues/35)
- A new `:comments` formatting rule standardizes comment prefixes,
differentiating between inline comments (`;`) and leading comments (`;;`).
Prefixes are configurable.
[#30](//github.com/greglook/cljstyle/issues/30)


## [0.14.0] - 2020-11-07
Expand Down
15 changes: 15 additions & 0 deletions doc/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,21 @@ This rule requires all files to end with a newline character. One will be added
if it is not present. There is no configuration for this rule beyond the
`:enabled?` state.

### `:comments`

This rule standardizes comment formatting by requiring that inline and leading
comments have regular prefixes.

* `:inline-prefix`

Prefix to use after the semicolon for inline comments. An inline comment
begins on the same line following another form.

* `:leading-prefix`

Prefix to use after the semicolon for leading comments. A leading comment is
generally the first thing on its line.

### `:vars`

This rule corrects formatting of var definition forms like `def`. There is no
Expand Down
23 changes: 23 additions & 0 deletions src/cljstyle/config.clj
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@
(s/keys :opt-un [:cljstyle.config.rules.global/enabled?]))


;; #### Rule: Comments

;; Prefix to use for comments on the same line after other forms.
(s/def :cljstyle.config.rules.comments/inline-prefix
(s/and string? #(str/starts-with? % ";")))


;; Prefix to use for comments on their own lines.
(s/def :cljstyle.config.rules.comments/leading-prefix
(s/and string? #(str/starts-with? % ";")))


(s/def :cljstyle.config.rules/comments
(s/keys :opt-un [:cljstyle.config.rules.global/enabled?
:cljstyle.config.rules.comments/inline-prefix
:cljstyle.config.rules.comments/leading-prefix]))


;; #### Rule: Vars

(s/def :cljstyle.config.rules/vars
Expand Down Expand Up @@ -317,6 +335,11 @@
:eof-newline
{:enabled? true}

:comments
{:enabled? true
:inline-prefix " "
:leading-prefix "; "}

:vars
{:enabled? true}

Expand Down
55 changes: 55 additions & 0 deletions src/cljstyle/format/comment.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
(ns cljstyle.format.comment
(:require
[cljstyle.format.zloc :as zl]
[clojure.string :as str]
[rewrite-clj.node :as n]
[rewrite-clj.zip :as z]))


(defn- comment-form?
"True if the node at this location is a comment."
[zloc _]
(zl/comment? zloc))


(defn- leading?
"True if the node at this location is a 'leading' comment. This generally
means it's the first non-whitespace thing on the line.
"
[zloc]
(let [prior (z/skip z/left* zl/space? (z/prev* zloc))]
(or (zl/root? prior)
(zl/comment? prior)
(z/linebreak? prior)
;; Comments which are the first thing in a data structure are also
;; considered leading comments, as they are generally meant to align
;; with the subesquent comments or elements in the structure.
(and (nil? (z/left* zloc))
(contains? #{:map :vector :list :set}
(z/tag (z/up zloc)))))))


(defn- edit-comment
[zloc rule-config]
(if-let [prefix (if (leading? zloc)
(:leading-prefix rule-config)
(:inline-prefix rule-config))]
;; Check comment prefix.
(let [curr-comment (zl/zstr zloc)]
(if (str/starts-with? curr-comment (str ";" prefix))
;; Prefix is already correct.
zloc
;; Fix comment.
(z/replace zloc
(-> curr-comment
(subs 1)
(str/replace #"^;* ?" prefix)
(str/replace #" *\n$" "\n")
(n/comment-node)))))
;; Configured to no-op.
zloc))


(def format-comments
"Rule to fix comment formatting."
[:comments nil comment-form? edit-comment])
4 changes: 3 additions & 1 deletion src/cljstyle/format/core.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(ns cljstyle.format.core
"Core formatting logic which ties together all rules."
(:require
[cljstyle.format.comment :as comment]
[cljstyle.format.fn :as fn]
[cljstyle.format.indent :as indent]
[cljstyle.format.line :as line]
Expand Down Expand Up @@ -114,7 +115,8 @@
type/format-protocols
type/format-types
type/format-reifies
type/format-proxies]
type/format-proxies
comment/format-comments]
rules-config
durations)
(apply-top-rules
Expand Down
141 changes: 141 additions & 0 deletions test/cljstyle/format/comment_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
(ns cljstyle.format.comment-test
(:require
[cljstyle.format.comment :as comment]
[cljstyle.test-util]
[clojure.test :refer [deftest testing is]]))


(def default-config
{:enabled? true
:inline-prefix " "
:leading-prefix "; "})


(deftest top-level
(testing "correct prefix"
(is (rule-reformatted?
comment/format-comments default-config
";; standalone comment"
";; standalone comment"))
(is (rule-reformatted?
comment/format-comments default-config
";; comment line one\n;; comment line two\n"
";; comment line one\n;; comment line two\n"))
(is (rule-reformatted?
comment/format-comments default-config
"abc\n;; standalone comment"
"abc\n;; standalone comment"))
(is (rule-reformatted?
comment/format-comments default-config
"abc\n;; standalone comment\nxyz"
"abc\n;; standalone comment\nxyz")))
(testing "incorrect prefix"
(is (rule-reformatted?
comment/format-comments default-config
";bad comment without space"
";; bad comment without space"))
(is (rule-reformatted?
comment/format-comments default-config
"; bad comment with space"
";; bad comment with space")))
(testing "trailing whitespace"
(is (rule-reformatted?
comment/format-comments default-config
";; one\n; \n;; three\n"
";; one\n;;\n;; three\n"))))


(deftest inline-comments
(testing "correct prefix"
(is (rule-reformatted?
comment/format-comments default-config
"(+ 1 2) ; inline comment"
"(+ 1 2) ; inline comment"))
(is (rule-reformatted?
comment/format-comments default-config
"[x ; comment about x\n y ; about y\n z]"
"[x ; comment about x\n y ; about y\n z]")))
(testing "incorrect prefix"
(is (rule-reformatted?
comment/format-comments default-config
"(+ 1 2) ;; inline comment"
"(+ 1 2) ; inline comment"))
(is (rule-reformatted?
comment/format-comments default-config
"[x ;; comment about x\n y ;about y\n z]"
"[x ; comment about x\n y ; about y\n z]"))))


(deftest leading-comments
(testing "correct prefix"
(is (rule-reformatted?
comment/format-comments default-config
"(cond
;; some exposition about case one
(= 1 x)
(do (one-thing))
;; some comment about the
;; second case
(two? x)
(different-thing x)
,,,)"
"(cond
;; some exposition about case one
(= 1 x)
(do (one-thing))
;; some comment about the
;; second case
(two? x)
(different-thing x)
,,,)")))
(testing "incorrect prefix"
(is (rule-reformatted?
comment/format-comments default-config
"(cond
; some exposition about case one
(= 1 x)
(do (one-thing))
;some comment about the
;;second case
(two? x)
(different-thing x)
,,,)"
"(cond
;; some exposition about case one
(= 1 x)
(do (one-thing))
;; some comment about the
;; second case
(two? x)
(different-thing x)
,,,)"))))


(deftest special-cases
(testing "in structure literals"
(is (rule-reformatted?
comment/format-comments default-config
"(let [;an inline comment
; which is really the start of many
x 123]
(* 2 x))"
"(let [;; an inline comment
;; which is really the start of many
x 123]
(* 2 x))"))
(is (rule-reformatted?
comment/format-comments default-config
"{;; multiline comment
; in a map
:x a}"
"{;; multiline comment
;; in a map
:x a}"))))
46 changes: 0 additions & 46 deletions test/cljstyle/format/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -129,52 +129,6 @@
"#::foo{:x #::bar{}}"))))


(deftest comment-handling
(testing "inline comments"
(is (reformatted?
fmt/reformat-form default-rules
"(let [;foo\n x (foo bar\n baz)]\n x)"
"(let [;foo\n x (foo bar\n baz)]\n x)")))
(testing "leading comments"
(is (reformatted?
fmt/reformat-form default-rules
";foo\n(def x 1)"
";foo\n(def x 1)"))
(is (reformatted?
fmt/reformat-form default-rules
"(ns foo.core)\n\n;; foo\n(defn foo [x]\n(inc x))"
"(ns foo.core)\n\n;; foo\n(defn foo\n [x]\n (inc x))"))
(is (reformatted?
fmt/reformat-form default-rules
";; foo\n(ns foo\n(:require bar))"
";; foo\n(ns foo\n (:require\n [bar]))"))
(is (reformatted?
fmt/reformat-form default-rules
"(defn foo [x]\n ;; +1\n(inc x))"
"(defn foo\n [x]\n ;; +1\n (inc x))"))
(is (reformatted?
fmt/reformat-form default-rules
"(binding [x 1] ; foo\nx)"
"(binding [x 1] ; foo\n x)")))
(testing "preceding closing delimiter"
(is (reformatted?
fmt/reformat-form default-rules
"(;a\n\n ;b\n )"
"(;a\n\n ;b\n )"))
(is (reformatted?
fmt/reformat-form default-rules
"(foo a ; b\nc ; d\n)"
"(foo a ; b\n c ; d\n )"))
(is (reformatted?
fmt/reformat-form default-rules
"(do\na ; b\nc ; d\n)"
"(do\n a ; b\n c ; d\n )"))
(is (reformatted?
fmt/reformat-form default-rules
"(let [x [1 2 ;; test1\n2 3 ;; test2\n]])"
"(let [x [1 2 ;; test1\n 2 3 ;; test2\n ]])"))))


(deftest metadata-handling
(is (reformatted?
fmt/reformat-form default-rules
Expand Down

0 comments on commit bb79e3a

Please sign in to comment.