├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── boot.properties ├── build.boot ├── doc └── img │ ├── issue-details.png │ └── sample-report.png ├── src └── tolitius │ ├── boot │ └── helper.clj │ ├── boot_check.clj │ ├── checker │ ├── bikeshed.clj │ ├── eastwood.clj │ ├── kibit.clj │ └── yagni.clj │ ├── core │ ├── check.clj │ └── reporting.clj │ └── reporter │ └── html.clj └── test └── test ├── with_eastwood.clj ├── with_kibit.clj └── with_yagni.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | .repl* 7 | dev/resources/public/js/* 8 | figwheel_server.log 9 | build.xml 10 | doo-index.html 11 | *.jar 12 | *.class 13 | /.lein-* 14 | /.nrepl-port 15 | *.iml 16 | /.idea 17 | /.lein-repl-history 18 | /.nrepl-history 19 | /forms-* 20 | report* 21 | boot-check-report* 22 | .eastwood 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.9 2 | ###### Mon Feb 19 11:00:55 2018 -0500 3 | 4 | * empty temp directory on report generation [#18](https://github.com/tolitius/boot-check/pull/18) by [@voytech](https://github.com/voytech) 5 | 6 | ## 0.1.8 7 | ###### Thu Feb 15 14:03:52 2018 -0500 8 | 9 | * HTML+ reporting [#17](https://github.com/tolitius/boot-check/pull/17) by [@voytech](https://github.com/voytech) 10 | * Error Aggregation [#17](https://github.com/tolitius/boot-check/pull/17) by [@voytech](https://github.com/voytech) 11 | 12 | ## 0.1.7 13 | ###### Wed Jan 24 18:08:53 2018 -0500 14 | 15 | * updating bikeshed to 0.5.1 ([#16](https://github.com/tolitius/boot-check/issues/16)) 16 | - bikeshed options changed, it now requires `:check?` option to include possible checks: 17 | ```clojure 18 | (check/with-bikeshed :options {:check? #{:long-lines} 19 | :verbose true 20 | :max-line-length 42}) 21 | ``` 22 | or 23 | ```bash 24 | $ boot check/with-bikeshed -o '{:check? #{:long-lines :trailing-whitespace :var-redefs :bad-methods :name-collisions}}' 25 | ``` 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boot-check 2 | 3 | [Boot](https://github.com/boot-clj/boot) tasks to check, analyze and inspect Clojure/Script code. 4 | 5 | It relies on universe tested [kibit](https://github.com/jonase/kibit), 6 | [eastwood](https://github.com/jonase/eastwood), [yagni](https://github.com/venantius/yagni), [bikeshed](https://github.com/dakrone/lein-bikeshed) and other titans. 7 | 8 | [![Clojars Project](http://clojars.org/tolitius/boot-check/latest-version.svg)](http://clojars.org/tolitius/boot-check) 9 | 10 | - [Why](#why) 11 | - [Kibit](#kibit) 12 | - [From Command Line](#from-command-line) 13 | - [From within "build.boot"](#from-within-buildboot) 14 | - [Help](#help) 15 | - [Yagni](#yagni) 16 | - [From Command Line](#from-command-line-1) 17 | - [From within "build.boot"](#from-within-buildboot-1) 18 | - [Help](#help-1) 19 | - [Yagni entry points](#yagni-entry-points) 20 | - [Eastwood](#eastwood) 21 | - [From Command Line](#from-command-line-2) 22 | - [From within "build.boot"](#from-within-buildboot-2) 23 | - [Help](#help-2) 24 | - [Bikeshed](#bikeshed) 25 | - [From Command Line](#from-command-line-2) 26 | - [From within "build.boot"](#from-within-buildboot-2) 27 | - [Help](#help-2) 28 | - [Bikeshed options](#bikeshed-options) 29 | - [Handling Errors](#handling-errors) 30 | - [Kibit Exceptions](#kibit-exceptions) 31 | - [Yagni Exceptions](#yagni-exceptions) 32 | - [Eastwood Exceptions](#eastwood-exceptions) 33 | - [Bikeshed Exceptions](#bikeshed-exceptions) 34 | - [Aggregating Errors](#aggregating-errors) 35 | - [Reporting](#reporting) 36 | - [Demo](#demo) 37 | - [License](#license) 38 | 39 | ## Why 40 | 41 | To be able to reach out to multiple code analyzers as well as compose them as [Boot tasks](https://github.com/boot-clj/boot/wiki/Tasks): 42 | 43 | ```clojure 44 | (require '[tolitius.boot-check :as check]) 45 | ``` 46 | 47 | ```clojure 48 | (deftask check-sources [] 49 | (set-env! :source-paths #{"src" "test"}) 50 | (comp 51 | (check/with-yagni) 52 | (check/with-eastwood) 53 | (check/with-kibit) 54 | (check/with-bikeshed))) 55 | ``` 56 | 57 | You can choose the tools (tasks) that apply, i.e. use one or several, and `boot-check` will do the rest: integration with analyzers, dependencies, reports, etc.. 58 | 59 | All these tasks will run inside [Boot pods](https://github.com/boot-clj/boot/wiki/Pods). 60 | 61 | ## Kibit 62 | 63 | [kibit](https://github.com/jonase/kibit) is a static code analyzer for Clojure, ClojureScript, cljx and other Clojure variants. 64 | 65 | ### From Command Line 66 | 67 | To check your code directly from shell: 68 | 69 | ```bash 70 | $ boot check/with-kibit 71 | latest report from kibit.... [You Rock!] 72 | ``` 73 | 74 | In case there are [problems](test/test/with_kibit.clj): 75 | 76 | ```clojure 77 | (defn when-vs-if [] 78 | (if 42 42 nil)) 79 | 80 | (defn vec-vs-into [] 81 | (into [] 42)) 82 | ``` 83 | 84 | kibit will show suggestions: 85 | 86 | ```clojure 87 | $ boot check/with-kibit 88 | At ../.boot/cache/tmp/../fun/boot-check/yeg/-grrwi1/test/with_kibit.clj:4: 89 | Consider using: 90 | (when 42 42) 91 | instead of: 92 | (if 42 42 nil) 93 | 94 | At ../.boot/cache/tmp/../fun/boot-check/yeg/-grrwi1/test/with_kibit.clj:7: 95 | Consider using: 96 | (vec 42) 97 | instead of: 98 | (into [] 42) 99 | 100 | WARN: kibit found some problems: 101 | 102 | {:problems #{{:expr (if 42 42 nil), :line 4, :column 3, :alt (when 42 42)} 103 | {:expr (into [] 42), :line 7, :column 3, :alt (vec 42)}}} 104 | ``` 105 | 106 | ### From within "build.boot" 107 | 108 | To use `boot-check` tasks within `build.boot` is easy: 109 | 110 | ```clojure 111 | (require '[tolitius.boot-check :as check]) 112 | 113 | (deftask check-sources [] 114 | (set-env! :source-paths #{"src" "test"}) 115 | (comp 116 | (check/with-kibit))) 117 | ``` 118 | 119 | ### Help 120 | 121 | ```shell 122 | $ boot check/with-kibit -h 123 | Static code analyzer for Clojure, ClojureScript, cljx and other Clojure variants. 124 | 125 | This task will run all the kibit checks within a pod. 126 | 127 | At the moment it takes no arguments, but behold..! it will. (files, rules, reporters, etc..) 128 | 129 | Options: 130 | -h, --help Print this help info. 131 | -t, --throw-on-errors throw an exception if the check does not pass 132 | ``` 133 | 134 | ## Yagni 135 | 136 | [yagni](https://github.com/venantius/yagni) is a static code analyzer that helps you find unused code in your applications and libraries. 137 | 138 | ### From Command Line 139 | 140 | To check your code directly from shell: 141 | 142 | ```shell 143 | $ boot check/with-yagni 144 | latest report from yagni.... [You Rock!] 145 | ``` 146 | 147 | if Yagni finds [unused code](test/test/with_yagni.clj) it will gladly report the news: 148 | 149 | ```shell 150 | WARN: could not find any references to the following: 151 | 152 | tolitius.yagni/check 153 | test.with-yagni/func-the-second 154 | test.with-yagni/other-func 155 | tolitius.yagni/report 156 | test.with-kibit/vec-vs-into 157 | test.with-yagni/-main 158 | 159 | WARN: the following have references to them, but their parents do not: 160 | 161 | tolitius.yagni/yagni-deps 162 | tolitius.yagni/pp 163 | test.with-kibit/when-vs-if 164 | test.with-yagni/func 165 | test.with-yagni/notafunc 166 | ``` 167 | 168 | ### From within "build.boot" 169 | 170 | To use `boot-check` tasks within `build.boot` is easy: 171 | 172 | ```clojure 173 | (require '[tolitius.boot-check :as check]) 174 | 175 | (deftask check-sources [] 176 | (set-env! :source-paths #{"src" "test"}) 177 | (comp 178 | (check/with-yagni))) 179 | ``` 180 | 181 | ### Help 182 | 183 | ```shell 184 | $ boot check/with-yagni -h 185 | Static code analyzer for Clojure that helps you find unused code in your applications and libraries. 186 | 187 | This task will run all the yagni checks within a pod. 188 | 189 | Options: 190 | -h, --help Print this help info. 191 | -o, --options OPTIONS OPTIONS sets yagni options EDN map. 192 | -t, --throw-on-errors throw an exception if the check does not pass 193 | ``` 194 | 195 | #### Yagni entry points 196 | 197 | Yagni works by searching your codebase from an initial set of entrypoints. As libraries, multi-main programs, and certain other types of projects either tend to have no `:main` or many entrypoint methods, you can instead, optionally, enumerate a `list of entrypoints` for your project in options: 198 | 199 | ```clojure 200 | (check/with-yagni :options {:entry-points ["test.with-yagni/-main" 201 | "test.with-yagni/func-the-second" 202 | 42]}))) 203 | ``` 204 | 205 | check out the [example](https://github.com/tolitius/boot-check/blob/master/build.boot#L21-L23) in the `boot.build` of this project. 206 | 207 | ## Eastwood 208 | 209 | [eastwood](https://github.com/jonase/eastwood) is a Clojure [lint](http://en.wikipedia.org/wiki/Lint_%28software%29) tool that uses the [tools.analyzer](https://github.com/clojure/tools.analyzer) and [tools.analyzer.jvm](https://github.com/clojure/tools.analyzer.jvm) libraries to inspect namespaces and report possible problems. 210 | 211 | ### From Command Line 212 | 213 | To check your code directly from shell: 214 | 215 | ```shell 216 | $ boot check/with-eastwood 217 | latest report from eastwood.... [You Rock!] 218 | ``` 219 | if eastwood finds [problems](test/test/with_eastwood.clj) it will gladly report the news: 220 | 221 | ```shell 222 | == Linting test.with-kibit == 223 | ... /test/with_kibit.clj:4:3: constant-test: Test expression is always logical true or always logical false: 42 in form (if 42 42 nil) 224 | 225 | == Linting test.with-eastwood == 226 | ... /test/with_eastwood.clj:5:8: def-in-def: There is a def of a nested inside def nested-def 227 | 228 | == Warnings: 2 (not including reflection warnings) Exceptions thrown: 0 229 | 230 | WARN: eastwood found some problems ^^^ 231 | ``` 232 | 233 | ### From within "build.boot" 234 | 235 | To use `boot-check` tasks within `build.boot` is easy: 236 | 237 | ```clojure 238 | (require '[tolitius.boot-check :as check]) 239 | 240 | (deftask check-sources [] 241 | (set-env! :source-paths #{"src" "test"}) 242 | (comp 243 | (check/with-eastwood))) 244 | ``` 245 | 246 | ### Help 247 | 248 | ```shell 249 | $ boot check/with-eastwood -h 250 | Clojure lint tool that uses the tools.analyzer and tools.analyzer.jvm libraries to inspect namespaces and report possible problems 251 | 252 | This task will run all the eastwood checks within a pod. 253 | 254 | At the moment it takes no arguments, but behold..! it will. (linters, namespaces, etc.) 255 | 256 | Options: 257 | -h, --help Print this help info. 258 | -t, --throw-on-errors throw an exception if the check does not pass 259 | ``` 260 | 261 | ## Bikeshed 262 | 263 | [bikeshed](https://github.com/dakrone/lein-bikeshed) is a Clojure "checkstyle/pmd" tool that designed to tell you your code is bad, and that you should feel bad. 264 | 265 | ### From Command Line 266 | 267 | To check your code directly from shell: 268 | 269 | ```shell 270 | $ boot check/with-bikeshed 271 | latest report from bikeshed.... [You Rock!] 272 | ``` 273 | if bikeshed finds problems it will gladly report the news: 274 | 275 | ```shell 276 | Checking for lines longer than 80 characters. 277 | Badly formatted files: 278 | ../tolitius/boot_check.clj:8: [boot.core :as core :refer [deftask user-files tmp-file set-env! get-env]] 279 | ../tolitius/boot_check.clj:25: "Static code analyzer for Clojure, ClojureScript, cljx and other Clojure variants. 280 | ../tolitius/boot_check.clj:29: At the moment it takes no arguments, but behold..! it will. (files, rules, reporters, etc..)" 281 | ../tolitius/boot_check.clj:30: ;; [f files FILE #{sym} "the set of files to check."] ;; TODO: convert these to "tmp-dir/file" 282 | 283 | Checking for lines with trailing whitespace. 284 | Badly formatted files: 285 | ../tolitius/boot/helper.clj:6: (mapv #(.getAbsolutePath %) 286 | ../tolitius/checker/bikeshed.clj:7: '[[lein-bikeshed "0.2.0" :exclusions [org.clojure/tools.cli 287 | ../tolitius/checker/yagni.clj:33: (let [graph# (binding [*ns* (the-ns *ns*)] 288 | ../tolitius/boot/helper.clj:6: (mapv #(.getAbsolutePath %) 289 | ../tolitius/checker/bikeshed.clj:7: '[[lein-bikeshed "0.2.0" :exclusions [org.clojure/tools.cli 290 | ../tolitius/checker/yagni.clj:33: (let [graph# (binding [*ns* (the-ns *ns*)] 291 | 292 | Checking for files ending in blank lines. 293 | No files found. 294 | 295 | Checking for redefined var roots in source directories. 296 | No with-redefs found. 297 | 298 | Checking whether you keep up with your docstrings. 299 | 9/50 [18.00%] functions have docstrings. 300 | Use -v to list functions without docstrings 301 | 302 | WARN: bikeshed found some problems ^^^ 303 | ``` 304 | 305 | ### From within "build.boot" 306 | 307 | To use `boot-check` tasks within `build.boot` is easy: 308 | 309 | ```clojure 310 | (require '[tolitius.boot-check :as check]) 311 | 312 | (deftask check-sources [] 313 | (set-env! :source-paths #{"src" "test"}) 314 | (comp 315 | (check/with-bikeshed))) 316 | ``` 317 | 318 | ### Help 319 | 320 | ```shell 321 | $ boot check/with-bikeshed -h 322 | 323 | This task is backed by 'lein-bikeshed' which is designed to tell you your code is bad, and that you should feel bad 324 | 325 | This task will run bikeshed checks within a pod. 326 | 327 | Options: 328 | -h, --help Print this help info. 329 | -o, --options OPTIONS OPTIONS sets bikeshed options EDN map. 330 | -t, --throw-on-errors throw an exception if the check does not pass 331 | ``` 332 | 333 | ### Bikeshed Options 334 | 335 | Bikeshed takes some options: 336 | 337 | ```clojure 338 | (check/with-bikeshed :options {:check? #{:long-lines} 339 | :verbose true 340 | :max-line-length 42}) 341 | ``` 342 | 343 | or 344 | 345 | ``` 346 | $ boot check/with-bikeshed -o '{:check? #{:long-lines :trailing-whitespace :var-redefs :bad-methods :name-collisions}}' 347 | ``` 348 | 349 | check out the [example](https://github.com/tolitius/boot-check/blob/master/build.boot#L34-L36) in the boot.build of this project. 350 | 351 | ## Handling Errors 352 | 353 | All tasks (i.e. for kibit, yagni, eastwood, bikeshed, etc.) accept an optional flag: 354 | 355 | ``` 356 | -t, --throw-on-errors throw an exception if the check does not pass 357 | ``` 358 | 359 | that if set will report all the problems found with the task, and then throw an exception. 360 | 361 | Here are some examples: 362 | 363 | ```clojure 364 | boot.user=> (set-env! :source-paths #{"src" "test"}) 365 | ``` 366 | 367 | ### Kibit Exceptions 368 | 369 | ```clojure 370 | boot.user=> (boot (check/with-kibit "-t")) 371 | ... reporting problems here then throws: 372 | 373 | clojure.lang.ExceptionInfo: kibit checks fail 374 | 375 | boot.user=> *e 376 | #error {} 377 | :cause "kibit checks fail" 378 | :data {:causes ({:expr (if 42 42 nil), :line 4, :column 3, :alt (when 42 42)} {:expr (into [] 42), :line 7, :column 3, :alt (vec 42)})} 379 | ... 380 | ``` 381 | 382 | ### Yagni Exceptions 383 | 384 | ```clojure 385 | boot.user=> (boot (check/with-yagni "-t")) 386 | ... reporting problems here then throws: 387 | 388 | clojure.lang.ExceptionInfo: yagni checks fail 389 | 390 | boot.user=> *e 391 | #error {} 392 | :cause "yagni checks fail" 393 | :data {:causes {:no-refs #{tolitius.boot-check/with-eastwood test.with-yagni/other-func tolitius.boot-check/with-yagni tolitius.boot-check/with-bikeshed tolitius.boot-check/with-kibit test.with-eastwood/nested-def test.with-kibit/vec-vs-into test.with-eastwood/always-true}, :no-parent-refs #{tolitius.boot.helper/make-pod-pool tolitius.boot.helper/fileset->paths tolitius.checker.yagni/yagni-deps tolitius.checker.yagni/entry-points-file tolitius.checker.bikeshed/bikeshed-deps tolitius.checker.yagni/create-entry-points test.with-kibit/when-vs-if tolitius.checker.yagni/check tolitius.checker.yagni/pp tolitius.boot.helper/tmp-dir-paths test.with-eastwood/a tolitius.checker.yagni/report tolitius.checker.yagni/check-graph tolitius.checker.kibit/kibit-deps tolitius.checker.eastwood/check tolitius.checker.kibit/check tolitius.boot-check/pod-deps tolitius.boot-check/with-throw test.with-yagni/func tolitius.checker.bikeshed/check tolitius.checker.eastwood/eastwood-deps tolitius.boot-check/bootstrap}}} 394 | ... 395 | ``` 396 | 397 | 398 | ### Eastwood Exceptions 399 | 400 | ```clojure 401 | boot.user=> (boot (check/with-eastwood "-t")) 402 | ... reporting problems here then throws: 403 | 404 | clojure.lang.ExceptionInfo: eastwood checks fail 405 | 406 | boot.user=> *e 407 | #error {} 408 | :cause "eastwood checks fail" 409 | :data {:causes {:err nil, :warning-count 12, :exception-count 0}} 410 | ... 411 | ``` 412 | 413 | In case of Eastwood warnings are not returned, just their number of them. They are however reported (printed) as found. 414 | 415 | ### Bikeshed Exceptions 416 | 417 | ```clojure 418 | boot.user=> (boot (check/with-bikeshed "-t")) 419 | ... reporting problems here then throws: 420 | 421 | boot.user=> *e 422 | #error {} 423 | :cause "bikeshed checks fail" 424 | :data {:causes true} 425 | ... 426 | ``` 427 | 428 | In case of Bikeshed, no errors / warnings are retured, since its own internal checks just return true/false values. But the exception is raised nevertheless to indicate that some checks have failed. 429 | 430 | ### Aggregating Errors 431 | 432 | There are a couple of ways `boot-check` deals with exceptions: 433 | 434 | * report problems, but throw no exceptions 435 | 436 | this is a default behavior, here is an [example](build.boot#L53-L58) from build.boot: 437 | 438 | ```clojure 439 | (deftask check-all [] 440 | (comp 441 | (test-kibit) 442 | (test-yagni) 443 | (test-eastwood) 444 | (test-bikeshed)) 445 | ``` 446 | 447 | * force to throw exceptions when errors are found 448 | 449 | This is done by the `throw-on-error` boot task. Here are a couple of examples. 450 | 451 | This [example]( build.boot#L40-L44) would run eastwood checker and would throw an exception right after it in case eastwood finds problems: 452 | 453 | ```clojure 454 | (deftask test-eastwood-and-throw [] 455 | (set-env! :source-paths #{"src" "test"}) 456 | (comp 457 | (check/with-eastwood :options {:gen-report true :exclude-linters [:unused-ret-vals]}) 458 | (check/throw-on-errors))) 459 | ``` 460 | 461 | this [example](build.boot#L60-L66) would run all the checkers and if any of these checkers report errors it will _aggregate_ all of these errors and throw an exception including all of them: 462 | 463 | ```clojure 464 | (deftask check-all-and-throw [] 465 | (comp 466 | (test-kibit) 467 | (test-yagni) 468 | (test-eastwood) 469 | (test-bikeshed) 470 | (check/throw-on-errors))) 471 | ``` 472 | 473 | if errors are found their aggregate is thrown: 474 | 475 | ```clojure 476 | clojure.lang.ExceptionInfo: Some of code checkers have failed. 477 | causes: ({:category nil, 478 | :linter-tool :kibit, 479 | :key "kibit", 480 | :coords 481 | {:file nil, :line 5, :column 28, :line-end nil, :column-end nil}, 482 | :snippet nil, 483 | :issue-form nil, 484 | :id "5616d90d-d957-4b15-bf53-082a0f82892a", 485 | :severity :normal, 486 | :hint-form nil, 487 | :message 488 | "Consider changing [ (fn [options] (:reporter options)) ] with [ :reporter ]"} 489 | {:category nil, 490 | :linter-tool :yagni, 491 | :key :no-parent-refs, 492 | :coords 493 | {:file "tolitius/boot/helper.clj", 494 | :line 0, 495 | :column 0, 496 | :line-end nil, 497 | :column-end nil}, 498 | :snippet nil, 499 | :issue-form nil, 500 | :id "90249104-8b32-4125-9286-05f9fe3f13bf", 501 | :severity :normal, 502 | :hint-form nil, 503 | :message 504 | "Var tolitius.boot.helper/make-pod-pool is referenced by unused code"} 505 | ... ... 506 | ``` 507 | 508 | 509 | ## Reporting 510 | 511 | Besides reporting errors to standard output (stdout) which could be diffficult to inspect `boot-check` can generate other reports in different formats (default and built in is HTML) with a help of a `:gen-report` option which forces a particular checker task to report its warnings. 512 | 513 | All checkers with this option set to true will write found issues into a shared interim warnings file. Later this file will be used to generate the final report. `boot-check` allows plugging in new reporters. This can be done by implementing the following multimethod: 514 | 515 | ```clojure 516 | (defmethod tolitius.core.reporting/report :your-own-generator [issues options]) 517 | ``` 518 | 519 | After providing source code with a custom report generator a namespace containing that generator must be evaluated. 520 | 521 | There is an already implemented, built in HTML report generator that can be set as a default by including this namespace in `build.boot`: 522 | 523 | ```Clojure 524 | (require '[tolitius.reporter.html]) 525 | ``` 526 | 527 | which will load an HTML multimethod implementation. 528 | 529 | Here is an example of how to include a checker task into reporting: 530 | ```clojure 531 | (check/with-kibit :options {:gen-report :true}) 532 | ``` 533 | 534 | and how to override default html report generator: 535 | 536 | ```clojure 537 | (set-env! :boot-check-reporter :your-own-generator) 538 | (comp 539 | (check/with-kibit :options {:gen-report :true})) 540 | ``` 541 | 542 | A typical pipeline with reporting enabled (and additional `throw-on-errors` task) may look like this: 543 | 544 | ```clojure 545 | (deftask check-with-report [] 546 | (set-env! :boot-check-reporter :your-own-generator) ;;setup report generator 547 | (comp 548 | (test-kibit) ;; do not include in report - only stdout 549 | (test-eastwood :options {:gen-report true}) ;; include in report and print stdout 550 | (test-yagni) ;; do not include in report - only stdout 551 | (test-bikeshed :options {:gen-report true}) ;; include in report and print stdout 552 | (check/throw-on-errors))) ;;throw errors after all. 553 | ``` 554 | 555 | ### Other Reporting Options 556 | 557 | Currently boot-check supports following reporting options: 558 | 559 | * `boot-check-reporter`: a hook to a custom report generator implementation (described above) 560 | * `report-file-name`: a file name pattern to be used when generating report files 561 | * `report-path`: a file path where this report should be written to 562 | * `report-skip-time?`: whether a timestamp should be included in the file name. By default a timestamp is included but you may want to disable it for example to enable fast refreshing when report is already opened in the browser 563 | 564 | ### Report samples 565 | 566 | A "grid" view: 567 | 568 | ![sample boot check report](doc/img/sample-report.png) 569 | 570 | "Issue details" view (currently only code snippet is showing in here): 571 | 572 | ![issue details view](doc/img/issue-details.png) 573 | 574 | ### Report limitations 575 | 576 | Due to implementation details of some of checkers (bikeshed, kibit) some limitations exist regarding amount of information visible on report. 577 | - kibit currently does not return filenames, which makes it impossible to include it in the report (only stdout directly from kibit reports filenames) 578 | - bikeshed does not return issue details at all - it only returns some summary containing list of tests which has not passed. Because of that - reports only contain that summary returned from bikeshed. 579 | 580 | ## Demo 581 | 582 | Here is a boot check [demo project](https://github.com/tolitius/check-boot-check) which can be cloned and played with. 583 | 584 | ## License 585 | 586 | Copyright © 2018 toliitus 587 | 588 | Distributed under the Eclipse Public License either version 1.0 or (at) 589 | your option any later version. 590 | -------------------------------------------------------------------------------- /boot.properties: -------------------------------------------------------------------------------- 1 | BOOT_VERSION=2.7.1 2 | BOOT_CLOJURE_VERSION=1.10.1 3 | -------------------------------------------------------------------------------- /build.boot: -------------------------------------------------------------------------------- 1 | (def +version+ "0.1.13") 2 | 3 | (set-env! 4 | :source-paths #{"src"} 5 | :dependencies '[[boot/core "2.7.2"] 6 | [adzerk/bootlaces "0.1.13" :scope "test"] 7 | [hiccup "1.0.5"] 8 | [pandeiro/boot-http "0.8.3"]]) 9 | 10 | 11 | (require '[tolitius.boot-check :as check] 12 | '[adzerk.bootlaces :refer :all] 13 | '[boot.util] 14 | '[tolitius.reporter.html :refer :all] 15 | '[pandeiro.boot-http :refer :all]) 16 | 17 | (deftask test-kibit [] 18 | (set-env! :source-paths #{"src" "test"}) 19 | (comp 20 | (check/with-kibit :options {:gen-report true}))) 21 | 22 | (deftask test-yagni [] 23 | (set-env! :source-paths #{"src" "test"}) 24 | (comp 25 | (check/with-yagni :options {:gen-report true 26 | :entry-points ["test.with-yagni/-main" 27 | "test.with-yagni/func-the-second" 28 | 42]}))) 29 | 30 | (deftask test-eastwood [] 31 | (set-env! :source-paths #{"src" "test"} :boot-check-reporter :html) 32 | (comp 33 | (check/with-eastwood :options {:gen-report true :exclude-linters [:unused-ret-vals]}))) 34 | 35 | (deftask test-eastwood-no-report [] 36 | (set-env! :source-paths #{"src" "test"}) 37 | (comp 38 | (check/with-eastwood :options {:exclude-linters [:unused-ret-vals]}))) 39 | 40 | (deftask test-eastwood-and-throw [] 41 | (set-env! :source-paths #{"src" "test"}) 42 | (comp 43 | (check/with-eastwood :options {:gen-report true :exclude-linters [:unused-ret-vals]}) 44 | (check/throw-on-errors))) 45 | 46 | (deftask test-bikeshed [] 47 | (set-env! :source-paths #{"src" "test"}) 48 | (comp 49 | (check/with-bikeshed :options {:check? #{:long-lines :trailing-whitespace :var-redefs :bad-methods :name-collisions} 50 | :max-line-length 42 51 | :gen-report true}))) 52 | 53 | (deftask check-all [] 54 | (comp 55 | (test-kibit) 56 | (test-yagni) 57 | (test-eastwood) 58 | (test-bikeshed))) 59 | 60 | (deftask check-all-and-throw [] 61 | (comp 62 | (test-kibit) 63 | (test-yagni) 64 | (test-eastwood) 65 | (test-bikeshed) 66 | (check/throw-on-errors))) 67 | 68 | (deftask check-all-serve [] 69 | (comp 70 | (serve) 71 | (test-kibit) 72 | (test-yagni) 73 | (test-eastwood) 74 | (test-bikeshed) 75 | (wait))) 76 | 77 | (deftask check-all-serve-watch [] 78 | (comp 79 | (serve) 80 | (watch) 81 | (test-kibit) 82 | (test-yagni) 83 | (test-eastwood) 84 | (test-bikeshed))) 85 | 86 | (bootlaces! +version+) 87 | 88 | (task-options! 89 | pom {:project 'tolitius/boot-check 90 | :version +version+ 91 | :description "check / analyze Clojure/Script code" 92 | :url "https://github.com/tolitius/boot-check" 93 | :scm {:url "https://github.com/tolitius/boot-check"} 94 | :license {"Eclipse Public License" 95 | "http://www.eclipse.org/legal/epl-v10.html"}}) 96 | -------------------------------------------------------------------------------- /doc/img/issue-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tolitius/boot-check/e9d766532e7e8d2a7c4e11ee016922789e35d6d2/doc/img/issue-details.png -------------------------------------------------------------------------------- /doc/img/sample-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tolitius/boot-check/e9d766532e7e8d2a7c4e11ee016922789e35d6d2/doc/img/sample-report.png -------------------------------------------------------------------------------- /src/tolitius/boot/helper.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.boot.helper 2 | (:require [boot.core :as core] 3 | [boot.pod :as pod])) 4 | 5 | (defn tmp-dir-paths [fs] 6 | (mapv #(.getAbsolutePath %) 7 | (core/input-dirs fs))) 8 | 9 | (defn fileset->paths [fileset] 10 | (->> fileset 11 | core/user-files 12 | (mapv (comp #(.getAbsolutePath %) core/tmp-file)))) 13 | 14 | (defn make-pod-pool [deps init] 15 | (let [pod-deps (update-in (core/get-env) [:dependencies] 16 | into deps) 17 | pool (pod/pod-pool pod-deps :init init)] 18 | (core/cleanup (pool :shutdown)) 19 | pool)) 20 | 21 | (defn load-issue-related-file-part [inputs issue offset-lines] 22 | (when-let [file (-> issue :coords :file)] 23 | (let [line (-> issue :coords :line) 24 | start (- line offset-lines) 25 | end (+ line offset-lines) 26 | input-file (first (filter #(.endsWith % file) inputs))] 27 | (with-open [rdr (clojure.java.io/reader input-file)] 28 | (doall (filter #(and (<= start (first %)) (>= end (first %))) (map-indexed (fn [i v] [(inc i) v]) (line-seq rdr)))))))) 29 | -------------------------------------------------------------------------------- /src/tolitius/boot_check.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.boot-check 2 | {:boot/export-tasks true} 3 | (:require [tolitius.checker.yagni :as yagni :refer [yagni-deps]] 4 | [tolitius.checker.kibit :as kibit :refer [kibit-deps]] 5 | [tolitius.checker.eastwood :as eastwood :refer [eastwood-deps]] 6 | [tolitius.checker.bikeshed :as bikeshed :refer [bikeshed-deps]] 7 | [tolitius.boot.helper :refer :all] 8 | [tolitius.core.reporting :as r] 9 | [tolitius.core.check :as check] 10 | [boot.core :as core :refer [deftask]] 11 | [clojure.java.io :as io] 12 | [boot.pod :as pod])) 13 | 14 | (def ^:const interim-report-data-file "interim-data") 15 | 16 | (def ^:const default-report-file-name "boot-check-report") 17 | 18 | (def pod-deps 19 | '[[org.clojure/tools.namespace "0.2.11" :exclusions [org.clojure/clojure]]]) 20 | 21 | (defn- store-tmp-file [fileset tmpdir content filename] 22 | (let [content-file (io/file tmpdir filename)] 23 | (doto content-file 24 | io/make-parents 25 | (spit content)) 26 | (let [new (core/add-source fileset tmpdir)] 27 | (core/commit! new)))) 28 | 29 | (defn- load-issues [fileset] 30 | (if-let [issues (->> fileset core/input-files (core/by-name [interim-report-data-file]) first)] 31 | (read-string (-> issues core/tmp-file slurp)) 32 | [])) 33 | 34 | (defn- append-issues [fileset tmpdir issues] 35 | (let [content (concat (load-issues fileset) issues) 36 | str-content (pr-str content)] 37 | (store-tmp-file fileset tmpdir str-content interim-report-data-file))) 38 | 39 | (defn- start-date [fileset tmpdir] 40 | (if-let [datefile (->> fileset core/input-files (core/by-name ["timestamp"]) first)] 41 | fileset 42 | (store-tmp-file fileset tmpdir (.format (java.text.SimpleDateFormat. "yyyy_MM_dd_HH_mm_ss") (java.util.Date.)) "timestamp"))) 43 | 44 | (defn- check-start-date [fileset] 45 | (when-let [datefile (->> fileset core/input-files (core/by-name ["timestamp"]) first)] 46 | (-> datefile core/tmp-file slurp))) 47 | 48 | (defn- write-report [fileset tmpdir report filename] 49 | (store-tmp-file fileset tmpdir report filename)) 50 | 51 | (defn- do-report [fileset tmpdir issues options] 52 | (core/empty-dir! tmpdir) 53 | (let [reporter (core/get-env :boot-check-reporter :html) 54 | report-path (core/get-env :report-path "") 55 | report-file-name (core/get-env :report-file-name default-report-file-name) 56 | skip-time? (core/get-env :report-skip-time? false) 57 | fileset (append-issues (start-date fileset tmpdir) tmpdir issues) 58 | refreshed (load-issues fileset) 59 | ext-opts (assoc options :reporter reporter) 60 | report-extension (r/report-extension ext-opts) 61 | report-content (r/report refreshed ext-opts)] 62 | (let [date-suffix (if skip-time? "" (str "." (check-start-date fileset))) 63 | dated-report-file-name (str report-file-name date-suffix "." report-extension)] 64 | (boot.util/dbug (str "\nWriting report to current directory: " report-path dated-report-file-name "...\n")) 65 | (doto 66 | (io/file (str report-path dated-report-file-name)) 67 | io/make-parents 68 | (spit report-content))) 69 | (boot.util/dbug "\nWriting report to boot fileset TEMP directory...\n") 70 | (write-report fileset tmpdir report-content (str report-file-name "." report-extension)) 71 | fileset)) 72 | 73 | (defn bootstrap [fresh-pod] 74 | (doto fresh-pod 75 | (pod/with-eval-in 76 | (require '[clojure.java.io :as io] 77 | '[clojure.tools.namespace.find :refer [find-namespaces-in-dir]] 78 | '[tolitius.boot.helper :refer :all]) 79 | 80 | (defn all-ns* [& dirs] 81 | (distinct (mapcat #(find-namespaces-in-dir (io/file %)) dirs)))))) 82 | 83 | (defn- process-results [fileset tmpdir f msg throw? options] 84 | (when-let [{:keys [warnings]} (f)] 85 | (when (and (seq warnings) throw?) 86 | (boot.util/warn-deprecated (str "\nWARN: throw-on-errors OPTION should be replaced by adding throw-on-errors TASK at the end of pipeline!^^^ \n")) 87 | (throw (ex-info msg {:causes warnings}))) 88 | (if (true? (:gen-report options)) 89 | (do-report fileset tmpdir warnings options) 90 | fileset)) 91 | fileset) 92 | 93 | (deftask with-kibit 94 | "Static code analyzer for Clojure, ClojureScript, cljx and other Clojure variants. 95 | 96 | This task will run all the kibit checks within a pod. 97 | 98 | At the moment it takes no arguments, but behold..! it will. (files, rules, reporters, etc..)" 99 | ;; [f files FILE #{sym} "the set of files to check."] ;; TODO: convert these to "tmp-dir/file" 100 | [o options OPTIONS edn "kibit options EDN map" 101 | t throw-on-errors bool "throw an exception if the check does not pass"] 102 | (let [pod-pool (make-pod-pool (concat pod-deps kibit-deps) bootstrap) 103 | tmpdir (core/tmp-dir!)] 104 | (core/with-pre-wrap fileset 105 | (process-results fileset tmpdir 106 | #(kibit/check pod-pool fileset) ;; TODO with args 107 | "kibit checks fail" 108 | throw-on-errors options)))) 109 | 110 | (deftask with-yagni 111 | "Static code analyzer for Clojure that helps you find unused code in your applications and libraries. 112 | 113 | This task will run all the yagni checks within a pod." 114 | [o options OPTIONS edn "yagni options EDN map" 115 | t throw-on-errors bool "throw an exception if the check does not pass"] 116 | (let [pod-pool (make-pod-pool (concat pod-deps yagni-deps) bootstrap) 117 | tmpdir (core/tmp-dir!)] 118 | (core/with-pre-wrap fileset 119 | (process-results fileset tmpdir 120 | #(yagni/check pod-pool fileset options) ;; TODO with args 121 | "yagni checks fail" 122 | throw-on-errors options)))) 123 | 124 | (deftask with-eastwood 125 | "Clojure lint tool that uses the tools.analyzer and tools.analyzer.jvm libraries to inspect namespaces and report possible problems 126 | 127 | This task will run all the eastwood checks within a pod. 128 | 129 | At the moment it takes no arguments, but behold..! it will. (linters, namespaces, etc.)" 130 | ;; [f files FILE #{sym} "the set of files to check."] ;; TODO: convert these to "tmp-dir/file" 131 | [o options OPTIONS edn "eastwood options EDN map" 132 | t throw-on-errors bool "throw an exception if the check does not pass"] 133 | (let [pod-pool (make-pod-pool (concat pod-deps eastwood-deps) bootstrap) 134 | tmpdir (core/tmp-dir!)] 135 | (core/with-pre-wrap fileset 136 | (process-results fileset tmpdir 137 | #(eastwood/check pod-pool fileset options) 138 | "eastwood checks fail" 139 | throw-on-errors options)))) 140 | 141 | (deftask with-bikeshed 142 | "This task is backed by 'lein-bikeshed' which is designed to tell you your code is bad, and that you should feel bad 143 | 144 | This task will run all the bikeshed checks within a pod. 145 | 146 | At the moment it takes no arguments, but behold..! it will. ('-m, --max-line-length', etc.)" 147 | ;; [f files FILE #{sym} "the set of files to check."] ;; TODO: convert these to "tmp-dir/file" 148 | [o options OPTIONS edn "bikeshed options EDN map" 149 | t throw-on-errors bool "throw an exception if the check does not pass"] 150 | (let [pod-pool (make-pod-pool (concat pod-deps bikeshed-deps) bootstrap) 151 | tmpdir (core/tmp-dir!)] 152 | (core/with-pre-wrap fileset 153 | (process-results fileset tmpdir 154 | #(bikeshed/check pod-pool fileset options) ;; TODO with args 155 | "bikeshed checks fail" 156 | throw-on-errors options)))) 157 | 158 | (deftask throw-on-errors 159 | "This task provides caller with exception when some of code checkers reports warnings. 160 | 161 | Using this task makes sense when You want to skip later tasks within the pipline as your 162 | 163 | rigorous policy assumes every line of code to be perfect ;-) 164 | 165 | When using this task You decide when to throw an exception. You may want to throw exception after 166 | 167 | particular checker or after all checkers has completed" 168 | [] 169 | (core/with-pre-wrap fileset 170 | (when-let [issues (load-issues fileset)] 171 | (when (seq issues) 172 | (throw (ex-info "Some of code checkers have failed." {:causes issues})))) 173 | fileset)) 174 | -------------------------------------------------------------------------------- /src/tolitius/checker/bikeshed.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.checker.bikeshed 2 | (:require [tolitius.boot.helper :refer :all] 3 | [tolitius.core.check :as ch] 4 | [boot.pod :as pod])) 5 | 6 | (def bikeshed-deps 7 | '[[org.clojure/clojure "1.10.1"] 8 | [lein-bikeshed "0.5.2" :exclusions [org.clojure/tools.cli 9 | org.clojure/tools.namespace]]]) 10 | 11 | (defn to-warning [problems] 12 | (when problems 13 | [(ch/issue :bikeshed :summary (str "Following bikeshed checks failed : " (clojure.string/join ", " problems)) (ch/coords " ? " " ? " " ? ") nil)])) 14 | 15 | (defn check [pod-pool fileset & args] 16 | (let [worker-pod (pod-pool :refresh)] 17 | (pod/with-eval-in worker-pod 18 | (require '[bikeshed.core] 19 | '[tolitius.checker.bikeshed :as checker]) 20 | (let [sources# ~(tmp-dir-paths fileset) 21 | _ (boot.util/dbug (str "bikeshed is about to look at: -- " sources# " --")) 22 | args# (update ~@args :check? #(merge % {})) 23 | problems# (bikeshed.core/bikeshed {:source-paths sources#} args#)] 24 | (if problems# 25 | (boot.util/warn (str "\nWARN: bikeshed found some problems ^^^ \n")) 26 | (boot.util/info "\nlatest report from bikeshed.... [You Rock!]\n")) 27 | {:warnings (or (checker/to-warning problems#) [])})))) 28 | -------------------------------------------------------------------------------- /src/tolitius/checker/eastwood.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.checker.eastwood 2 | (:require [tolitius.boot.helper :refer :all] 3 | [tolitius.core.check :as ch] 4 | [boot.pod :as pod])) 5 | 6 | (def eastwood-deps 7 | '[[jonase/eastwood "0.3.5" :exclusions [org.clojure/clojure]]]) 8 | 9 | (defn eastwood-linting-callback [files handle-issue options] 10 | (fn [{:keys [warn-data kind] :as data}] 11 | (when (= :lint-warning kind) 12 | (let [exclude-linters (:exclude-linters options) 13 | {:keys [file line column linter msg form]} warn-data] 14 | (if-not (some #{linter} exclude-linters) 15 | (let [issue (ch/issue :eastwood linter msg (ch/coords file line column) nil)] 16 | (if-let [warn-contents (load-issue-related-file-part files issue 5)] 17 | (handle-issue (assoc issue :snippet warn-contents)) 18 | (handle-issue issue)))))))) 19 | 20 | (defn check [pod-pool fileset options & args] 21 | (let [worker-pod (pod-pool :refresh) 22 | inputs (fileset->paths fileset) 23 | exclude-linters (:exclude-linters options)] 24 | (pod/with-eval-in worker-pod 25 | (require '[eastwood.lint :as eastwood] 26 | '[eastwood.reporting-callbacks :as reporting] 27 | '[tolitius.checker.eastwood :as checker] 28 | '[tolitius.core.check :as ch]) 29 | (let [sources# #{~@(tmp-dir-paths fileset)} 30 | _ (boot.util/dbug (str "eastwood is about to look at: -- " sources# " --")) 31 | {:keys [some-warnings] :as checks} (eastwood/eastwood {:source-paths sources# 32 | :exclude-linters ~exclude-linters}) 33 | 34 | issues# (atom #{})] 35 | (if some-warnings 36 | (boot.util/warn (str "\nWARN: eastwood found some problems ^^^ \n\n")) 37 | (boot.util/info "\nlatest report from eastwood.... [You Rock!]\n")) 38 | {:warnings (or (vec @issues#) [])})))) 39 | -------------------------------------------------------------------------------- /src/tolitius/checker/kibit.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.checker.kibit 2 | (:require [tolitius.boot.helper :refer :all] 3 | [boot.core :as core] 4 | [tolitius.core.check :as ch] 5 | [boot.pod :as pod])) 6 | 7 | (def kibit-deps 8 | '[[jonase/kibit "0.1.8"] 9 | [org.clojure/tools.cli "0.3.3"]]) 10 | 11 | ;;Kibit does not report file :( - it is a bug. Next version of kibit will support that. 12 | (defn normalise-issue [warning] 13 | (let [{:keys [expr alt line column file]} warning 14 | msg (str "Consider changing [ " (pr-str expr) " ] with [ " (pr-str alt) " ]") 15 | linter "kibit"] 16 | (ch/issue :kibit linter msg (ch/coords file line column) nil))) 17 | 18 | (defn check [pod-pool fileset & args] 19 | (let [worker-pod (pod-pool :refresh) 20 | namespaces (pod/with-eval-in worker-pod 21 | (all-ns* ~@(->> fileset 22 | core/input-dirs 23 | (map (memfn getPath))))) 24 | sources (fileset->paths fileset)] 25 | (pod/with-eval-in worker-pod 26 | (boot.util/dbug (str "kibit is about to look at: -- " '~sources " --")) 27 | (require '[kibit.driver :as kibit] 28 | '[tolitius.checker.kibit :as checker]) 29 | (doseq [ns '~namespaces] (require ns)) 30 | (let [problems# (apply kibit.driver/run '~sources nil '~args)] ;; nil for "rules" which would expand to all-rules, 31 | (if-not (zero? (count problems#)) 32 | (boot.util/warn (str "\nWARN: kibit found some problems: \n\n" {:problems (set problems#)} "\n")) 33 | (boot.util/info "\nlatest report from kibit.... [You Rock!]\n")) 34 | {:warnings (or (mapv checker/normalise-issue (vec problems#)) [])})))) 35 | -------------------------------------------------------------------------------- /src/tolitius/checker/yagni.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.checker.yagni 2 | (:require [boot.pod :as pod] 3 | [tolitius.boot.helper :refer :all] 4 | [tolitius.core.check :as ch] 5 | [clojure.string :as s])) 6 | 7 | (def yagni-deps 8 | '[[venantius/yagni "0.1.7" :exclusions [org.clojure/clojure]]]) 9 | 10 | ;; yagni implementation (07/26/2016) is coupled with a lein specific file 11 | (defonce entry-points-file ".lein-yagni") 12 | 13 | (defn- pp [s] 14 | (s/join "\n" s)) 15 | 16 | (defn- namespace->file [namespace] 17 | (str (clojure.string/replace (clojure.string/replace (str namespace) #"/.*" "") #"\." "/") ".clj")) 18 | 19 | (defn- error-desc [family varname] 20 | (cond 21 | (= family :no-refs) (str "Could not find any reference to Var " varname) 22 | (= family :no-parent-refs) (str "Var " varname " is referenced by unused code"))) 23 | 24 | (defn to-issue [namespace family] 25 | (ch/issue :yagni family (error-desc family (str namespace)) (ch/coords (namespace->file namespace) 0 0) nil)) 26 | 27 | (defn check-graph [find-family g] 28 | (let [{:keys [children parents]} (find-family @g)] 29 | (concat (mapv #(to-issue % :no-parent-refs) (vec children)) 30 | (mapv #(to-issue % :no-refs) (vec parents))))) 31 | 32 | (defn report [{:keys [no-refs no-parent-refs]}] 33 | (when no-refs 34 | (boot.util/warn (str "\nWARN: could not find any references to the following:\n\n" (pp no-refs) "\n"))) 35 | (when no-parent-refs 36 | (boot.util/warn (str "\nWARN: the following have references to them, but their parents do not:\n\n" (pp no-parent-refs) "\n")))) 37 | 38 | (defn create-entry-points [entry-points] 39 | (when entry-points 40 | (->> (interpose "\n" entry-points) 41 | (apply str) 42 | (spit entry-points-file)))) 43 | 44 | (defn check [pod-pool fileset {:keys [entry-points]}] 45 | (let [worker-pod (pod-pool :refresh) 46 | sources (fileset->paths fileset)] 47 | (pod/with-eval-in worker-pod 48 | (boot.util/dbug (str "yagni is about to look at: -- " '~sources " --" 49 | (if '~entry-points 50 | (str "\nwith entry points -- " '~entry-points " --") 51 | "\nwith no entry points"))) 52 | (require '[yagni.core :as yagni] 53 | '[yagni.graph :refer [find-children-and-parents]] 54 | '[tolitius.checker.yagni :refer [check-graph report create-entry-points]]) 55 | (let [graph# (binding [*ns* (the-ns *ns*)] 56 | (create-entry-points '~entry-points) 57 | (yagni/construct-reference-graph '~sources)) 58 | problems# (check-graph find-children-and-parents graph#)] 59 | (if problems# 60 | (boot.util/info "\nlatest report from yagni.... Yagni found some problems!\n") 61 | (boot.util/info "\nlatest report from yagni.... [You Rock!]\n")) 62 | {:warnings (or problems# [])})))) 63 | -------------------------------------------------------------------------------- /src/tolitius/core/check.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.core.check) 2 | 3 | (defn coords 4 | ([file line column] 5 | (coords file line column nil nil)) 6 | ([file line column line-end column-end] 7 | {:file file :line line :column column :line-end line-end :column-end column-end})) 8 | 9 | (defn issue 10 | ([linter message coords] 11 | (issue linter nil message coords nil)) 12 | ([linter message coords snippet] 13 | (issue linter nil message coords snippet)) 14 | ([linter key message coords snippet] 15 | (issue linter nil key message coords snippet)) 16 | ([linter category key message coords snippet] 17 | (issue linter category key message coords :normal snippet)) 18 | ([linter category key message coords severity snippet] 19 | (issue linter category key message coords severity snippet nil)) 20 | ([linter category key message coords severity snippet issue-form] 21 | (issue linter category key message coords severity snippet issue-form nil)) 22 | ([linter category key message coords severity snippet issue-form hint-form] 23 | {:id (str (java.util.UUID/randomUUID)) 24 | :linter-tool linter 25 | :category category 26 | :key key 27 | :severity severity 28 | :message message 29 | :coords coords 30 | :snippet snippet 31 | :issue-form issue-form 32 | :hint-form hint-form})) 33 | -------------------------------------------------------------------------------- /src/tolitius/core/reporting.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.core.reporting) 2 | 3 | (defmulti report (fn [issues options] (:reporter options))) 4 | 5 | (defmulti report-extension (fn [options] (:reporter options))) 6 | -------------------------------------------------------------------------------- /src/tolitius/reporter/html.clj: -------------------------------------------------------------------------------- 1 | (ns tolitius.reporter.html 2 | (:require [tolitius.core.reporting :as r] 3 | [hiccup.core :refer :all])) 4 | 5 | (defn- severity-style [severity] 6 | (cond 7 | (= severity "normal") "table-warning" 8 | (= severity "severe") "table-danger" 9 | :else "")) 10 | 11 | (defn- report-header [] 12 | [:nav {:class "navbar navbar-expand-lg navbar-dark bg-dark"} 13 | [:a {:class "navbar-brand" :href "#"} "Boot-Check Report"] 14 | [:div {:class "collapse navbar-collapse"} 15 | [:ul {:class "navbar-nav mr-auto"} 16 | [:li {:class "nav-item active"} [:a {:class "nav-link"} "Issues"]]] 17 | [:div {:style "color:white"} (str "Report Time : " (java.util.Date.))]]]) 18 | 19 | (defn- with-style [current-line-nr error-line-nr] 20 | (cond-> "white-space:pre-wrap;word-wrap:break-word;" 21 | (= current-line-nr error-line-nr) (str "background-color:red;color:white;") 22 | (not= current-line-nr error-line-nr) (str "background-color:white;color:black;"))) 23 | 24 | (defn- render-line [nr content warning-line] 25 | [:code {:class "row" :style (with-style nr warning-line)} 26 | [:span {:class "pr-2 pl-2" :style "background-color:lightgray;color:gray"} nr] content]) 27 | 28 | (defn- code-snippet [issue] 29 | (let [{:keys [snippet coords]} issue] 30 | (reduce (fn [val nxt] (conj val (render-line (first nxt) (last nxt) (:line coords)))) [:div {:class "grid"}] snippet))) 31 | 32 | (defn- issue-details [issue] 33 | (let [{:keys [linter-tool message key severity coords]} issue] 34 | [:div {:class "card border-warning mb-3" :style "max-width: 40rem;"} 35 | [:div {:class "card-body text-warning"} 36 | [:h5 {:class "card-title"} message] 37 | [:p {:class "card-text"} key]] 38 | [:ul {:class "list-group list-group-flush"} 39 | [:li {:class "list-group-item"} (str (:file coords) " [ " (:line coords) ":" (:column coords) " ] ")] 40 | [:li {:class "list-group-item"} [:span "Severity"] [:b (name severity)]]]])) 41 | 42 | (defn- has-details? [issue] 43 | (not (nil? (:snippet issue)))) 44 | 45 | (defn- issue-table-cell [issue] 46 | (let [{:keys [id linter-tool message key severity coords]} issue] 47 | [:tr {:class (severity-style severity)} 48 | [:td linter-tool] 49 | [:td key] 50 | [:td message] 51 | [:td (:file coords)] 52 | [:td (str "[ " (:line coords) ":" (:column coords) " ]")] 53 | [:td severity] 54 | [:td (when (has-details? issue) [:a {:href "#" :data-toggle "modal" :data-target (str "#" id) } [:span "more"]])]])) 55 | 56 | (defn- insert-rows [aggr issue] 57 | (conj aggr (issue-table-cell issue))) 58 | 59 | (defn- issues-with-snippet [issues] 60 | (filterv has-details? issues)) 61 | 62 | (defn- snippet-modal [issue] 63 | [:div {:class "modal fade bd-example-modal-lg" :id (:id issue) :tabindex -1 :role "dialog" :aria-hidden true} 64 | [:div {:class "modal-dialog modal-lg" :role "document"} 65 | [:div {:class "modal-content pr-3 pl-3"} 66 | [:div {:class "modal-body"} 67 | (code-snippet issue)]]]]) 68 | 69 | (defn- snippets [issues] 70 | (reduce (fn [val nxt] (conj val (snippet-modal nxt))) [:div {:id "snippets-container"}] (issues-with-snippet issues))) 71 | 72 | (defn- build [issues options] 73 | (html 74 | [:html 75 | [:head 76 | [:link {:rel "stylesheet" :href "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"}] 77 | [:link {:rel "stylesheet" :href "https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css"}] 78 | [:script {:src "https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"}] 79 | [:script {:src "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"}] 80 | [:script {:src "https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"}] 81 | [:script {:src "https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"}] 82 | [:script "$(document).ready(function() {$('#issuestable').DataTable();});"]] 83 | [:body 84 | (report-header) 85 | [:h5 {:class "p-3"} "All reported warnings:"] 86 | [:div {:class "container-fluid"} 87 | (snippets issues) 88 | [:div {:id "responsive-wrapper" :class "table-responsive"} 89 | [:table {:id "issuestable" :role "grid" :class "table table-sm table-striped table-hover table-bordered"} 90 | [:thead 91 | [:th "Tool"] 92 | [:th "Type"] 93 | [:th "Message"] 94 | [:th "File"] 95 | [:th "Location"] 96 | [:th "Severity"] 97 | [:th "See Details"]] 98 | (reduce insert-rows [:tbody] issues)]]]]])) 99 | 100 | (defmethod r/report :html [issues options] 101 | (build issues options)) 102 | 103 | (defmethod r/report-extension :html [options] "html") 104 | -------------------------------------------------------------------------------- /test/test/with_eastwood.clj: -------------------------------------------------------------------------------- 1 | (ns test.with-eastwood 2 | (:require [clojure.test :refer [deftest is]])) 3 | 4 | (defn nested-def [] 5 | (def a 42) 6 | a) 7 | 8 | (deftest always-true 9 | (is (= 42 42))) 10 | -------------------------------------------------------------------------------- /test/test/with_kibit.clj: -------------------------------------------------------------------------------- 1 | (ns test.with-kibit) 2 | 3 | (defn when-vs-if [] 4 | (if 42 42 nil)) 5 | 6 | (defn vec-vs-into [] 7 | (into [] 42)) 8 | -------------------------------------------------------------------------------- /test/test/with_yagni.clj: -------------------------------------------------------------------------------- 1 | (ns test.with-yagni 2 | (:gen-class) 3 | (:require [test.with-kibit])) 4 | 5 | (defn func [] (test.with-kibit/when-vs-if)) 6 | 7 | (def notafunc false) 8 | 9 | (defn func-the-second [] notafunc) 10 | 11 | (defn other-func [] (func)) 12 | 13 | (defn -main 14 | "I don't do a whole lot ... yet." 15 | [& args] 16 | (println "Hello, World!")) 17 | --------------------------------------------------------------------------------