Skip to content

Commit

Permalink
Reader conditional support (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
borkdude committed Apr 4, 2019
1 parent 76ebcb0 commit a6ee3e3
Show file tree
Hide file tree
Showing 30 changed files with 823 additions and 447 deletions.
16 changes: 4 additions & 12 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,13 @@ jobs:
command: |
clojure -A:test
- run:
name: Run as local root dependency
name: Run as tools.deps dependency
command: |
mkdir -p /tmp/proj
cd /tmp/proj
rm -rf *
clojure -Sdeps '{:deps {clj-kondo {:local/root "/home/circleci/repo"}}}' \
-m clj-kondo.main --lint /home/circleci/repo
.circleci/script/tools.deps
- run:
name: Run as git dependency
name: Run as lein command
command: |
mkdir -p /tmp/proj
cd /tmp/proj
rm -rf *
clojure -Sdeps "{:deps {clj-kondo {:git/url \"https://github.com/borkdude/clj-kondo\" :sha \"$CIRCLE_SHA1\"}}}" \
-m clj-kondo.main --lint /home/circleci/repo
.circleci/script/lein
- save_cache:
paths:
- ~/.m2
Expand Down
9 changes: 9 additions & 0 deletions .circleci/script/lein
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

err=0
trap '(( err |= $? ))' ERR
trap 'exit $err' SIGINT SIGTERM

lein clj-kondo --lint "$(lein classpath)"

exit "$err"
10 changes: 8 additions & 2 deletions .circleci/script/performance
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#!/usr/bin/env bash

rm performance.txt
err=0
trap '(( err |= $? ))' ERR
trap 'exit $err' SIGINT SIGTERM

rm -rf performance.txt
echo -e "==== Build initial cache" | tee -a performance.txt
cp="$(clojure -Spath)"
cp="$(clojure -R:cljs -Spath)"
(time ./clj-kondo --lint "$cp" --cache) 2>&1 | tee -a performance.txt

echo -e "\n==== Lint a single file (emulate in-editor usage)" | tee -a performance.txt
Expand All @@ -11,3 +15,5 @@ echo -e "\n==== Lint a single file (emulate in-editor usage)" | tee -a performan
count=$(find . -name "*.clj*" -type f | wc -l | tr -d ' ')
echo -e "\n==== Launch clj-kondo for each file in project ($count)" | tee -a performance.txt
(time find src -name "*.clj*" -type f -exec ./clj-kondo --lint {} --cache \; ) 2>&1 | tee -a performance.txt

exit "$err"
21 changes: 21 additions & 0 deletions .circleci/script/tools.deps
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash

err=0
trap '(( err |= $? ))' ERR
trap 'exit $err' SIGINT SIGTERM

# Run as local root dependency
rm -rf /tmp/proj
mkdir -p /tmp/proj
cd /tmp/proj
clojure -Sdeps '{:deps {clj-kondo {:local/root "/home/circleci/repo"}}}' \
-m clj-kondo.main --lint /home/circleci/repo/src /home/circleci/repo/test

# Run as git dependency
rm -rf /tmp/proj
mkdir -p /tmp/proj
cd /tmp/proj
clojure -Sdeps "{:deps {clj-kondo {:git/url \"https://github.com/borkdude/clj-kondo\" :sha \"$CIRCLE_SHA1\"}}}" \
-m clj-kondo.main --lint /home/circleci/repo/src /home/circleci/repo/test

exit "$err"
2 changes: 2 additions & 0 deletions .clj-kondo/config.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{:ignore-comments? false
:debug? false}
121 changes: 59 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ A minimal and opinionated linter for Clojure code that sparks joy.

## Rationale

You use [inline
defs](https://blog.michielborkent.nl/2017/05/25/inline-def-debugging/) for
debugging but you would like to get rid of them before making your code
You don't mind the occasional [inline
def](https://blog.michielborkent.nl/2017/05/25/inline-def-debugging/) for
debugging, but you would like to get rid of them before making your code
public. Also, unnecessary `do` and `let` nestings don't really add any value to
your life. Let clj-kondo help you tidy your code.

Expand All @@ -20,26 +20,43 @@ your life. Let clj-kondo help you tidy your code.
* inline def warnings
* obsolete do warnings
* obsolete let warnings
* basic arity errors
* arity errors across namespaces
* private function call errors

<img src="screenshots/wrong-arity.png">

This linter is:

* compatible with .clj, .cljs and .cljc files
* build tool and editor agnostic

## Status

WIP, hacky, alpha, but already useful. None of the code is meant to be exposed as a public API,
except the command line interface.
Under active development, but already useful. None of the code is meant to be exposed as a
public API, except the command line interface.

For new features I'd like to focus on things that
[joker](https://github.com/candid82/joker) doesn't support yet, so I recommend
enabling that one as well.

## Installation

Pre-built binaries are available for linux and MacOS on the [releases](https://github.com/borkdude/clj-kondo/releases) page.
### MacOS:

brew install borkdude/brew/clj-kondo

### Linux

Install [Linuxbrew](http://linuxbrew.sh/). Then run:

brew install borkdude/brew/clj-kondo

For running without GraalVM, look [here](#running-without-graalvm).
### Manual install

Pre-built binaries are available for linux and MacOS on the
[releases](https://github.com/borkdude/clj-kondo/releases) page.

### Building from source

To build a binary from source, download [GraalVM](https://github.com/oracle/graal/releases) and set the
`GRAALVM_HOME` variable. E.g.:
Expand All @@ -54,6 +71,8 @@ Place the binary somewhere on your path.

## Usage

### Command line

Lint from stdin:

``` shellsession
Expand All @@ -75,107 +94,85 @@ Lint a directory:
$ clj-kondo --lint src
src/clj_kondo/test.cljs:7:1: warning: obsolete do
src/clj_kondo/vars.clj:291:3: error: Wrong number of args (1) passed to clj-kondo.vars/analyze-arities
...
```

Lint a project classpath:

``` shellsession
$ clj-kondo --lint $(clj -Spath)
$ clj-kondo --lint $(lein classpath)
$ clj-kondo --lint $(boot with-cp -w -f -)
```

It is recommended to save the analysis results to a cache. This gives a better
experience when using `clj-kondo` in an editor. To do this, make a `.clj-kondo`
directory in the root of your project and use the `--cache` option:
### Project setup

clj-kondo --cache --lint $(clj -Spath)
Make a `.clj-kondo` directory in the root of your project. This directory will
be automatically detected by `clj-kondo` and a cache will be created inside of
it.

Next time you will lint a single namespace (e.g. using [editor
integration](#editor-integration)), the cache can be leveraged to detect more
errors:
To initialize the cache, lint the entire classpath generated by e.g. `lein`:

``` shellsession
$ clj-kondo --lint $(lein classpath) --cache
```

For `boot` you can use `boot with-cp -w -f -`. For `tools.deps` you can use `clj -Spath`.

Now you are ready to lint single files using [editor
integration](#editor-integration). A simulation of what happens when you edit a
file in your editor:

``` shellsession
$ echo '(select-keys)' | clj-kondo --lang cljs --cache --lint -
<stdin>:1:1: error: Wrong number of args (0) passed to cljs.core/select-keys
```

### Running without GraalVM
Since the cache knows about your version of ClojureScript, it detects that the
number of arguments you pass to `select-keys` is invalid. This works for all
libraries and your own namespaces, not just Clojure. While editing files, the
cache is incrementally updated.

Running with GraalVM is recommended for better startup time, but you can run this linter with a normal JVM as well.
### Running with the JVM

Using the binary is recommended for better startup time, but you can run this
linter with as a regular Clojure program on the JVM as well.

#### leiningen

You can add `clj-kondo` to `~/.lein/profiles.clj` to make it available as a `lein` command:

``` clojure
{:user {:dependencies [[clj-kondo "0.0.1-SNAPSHOT"]]
:aliases {"clj-kondo" ["run" "-m" "clj-kondo.main" "--lint"]}
{:user {:dependencies [[clj-kondo "RELEASE"]]
:aliases {"clj-kondo" ["run" "-m" "clj-kondo.main"]}
```

``` shellsession
$ lein clj-kondo src
$ lein clj-kondo --lint src
```

#### tools.deps.alpha

Run `clj-kondo` as an ad-hoc command line dependency:

``` shellsession
$ clj -Sdeps '{:deps {clj-kondo {:git/url "https://github.com/borkdude/clj-kondo" :sha "<master/latest-sha>"}}}' -m clj-kondo.main --lint src
$ clj -Sdeps '{:deps {clj-kondo {:mvn/version "RELEASE"}}}' -m clj-kondo.main --lint src
```

where `<master/latest-sha>` is the SHA of the latest commit on the master
branch.

Or add it as an alias to `~/.clojure/deps.edn`:

``` clojure
{:aliases
{:clj-kondo
{:extra-deps {clj-kondo {:git/url "https://github.com/borkdude/clj-kondo" :sha "<master/latest-sha>"}}
:main-opts ["-m" "clj-kondo.main" "--lint"]}}}
{:extra-deps {clj-kondo {:mvn/version "RELEASE"}}
:main-opts ["-m" "clj-kondo.main"]}}}
```

``` shellsession
$ clj -A:clj-kondo src
$ clj -A:clj-kondo --lint src
```

## Editor integration

You can integrate with Emacs [`flycheck`](https://www.flycheck.org/en/latest/) as follows:

``` emacs-lisp
(flycheck-define-checker clj-kondo-clj
"See https://github.com/borkdude/clj-kondo"
:command ("clj-kondo" "--cache" "--lang" "clj" "--lint" "-")
:standard-input t
:error-patterns
((error line-start "<stdin>:" line ":" column ": " (0+ not-newline) (or "error: " "Exception: ") (message) line-end)
(warning line-start "<stdin>:" line ":" column ": " (0+ not-newline) "warning: " (message) line-end))
:modes (clojure-mode clojurec-mode)
:predicate (lambda () (not (string= "edn" (file-name-extension (buffer-file-name)))))
;; use this when you also use the joker linter, recommended!
:next-checkers ((error . clojure-joker) (warning . clojure-joker)))

(flycheck-define-checker clj-kondo-cljs
"See https://github.com/borkdude/clj-kondo"
:command ("clj-kondo" "--cache" "--lang" "cljs" "--lint" "-")
:standard-input t
:error-patterns
((error line-start "<stdin>:" line ":" column ": " (0+ not-newline) (or "error: " "Exception: ") (message) line-end)
(warning line-start "<stdin>:" line ":" column ": " (0+ not-newline) "warning: " (message) line-end))
:modes (clojurescript-mode)
:predicate (lambda () (not (string= "edn" (file-name-extension (buffer-file-name)))))
;; use this when you also use the joker linter, recommended!
:next-checkers ((error . clojurescript-joker) (warning . clojurescript-joker)))

(add-to-list 'flycheck-checkers 'clj-kondo-clj)
(add-to-list 'flycheck-checkers 'clj-kondo-cljs)
```

This code was adapted from [flycheck-joker](https://github.com/candid82/flycheck-joker).
For integrating with Emacs, see
[flycheck-clj-kondo](https://github.com/borkdude/flycheck-clj-kondo).

## Tests

Expand Down
21 changes: 21 additions & 0 deletions corpus/cljc/test_cljc.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
(ns corpus.cljc.test-cljc)

#?(:clj (defn foo [x y]
x)
:cljs (defn foo [x]
x))

;; valid calls on lines 9 and 10:
#?(:clj (foo 1 2)
:cljs (foo 1))

;; invalid calls on lines 13 and 14:
#?(:clj (foo 1)
:cljs (foo 1 2))

;; bar is function that is callable from both CLJ and CLJS:
(defn bar [x]
x)

(bar 1)
(bar 1 2 3)
5 changes: 5 additions & 0 deletions corpus/cljc/test_cljc.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(ns corpus.cljc.test-cljc
(:require [corpus.cljc.test-cljc :refer [foo]]))

(foo 1) ;; correct
(foo 1 2) ;; incorrect
5 changes: 5 additions & 0 deletions corpus/cljc/test_cljc_from_clj.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(ns corpus.cljc.test-cljc-from-clj
(:require [corpus.cljc.test-cljc :refer [bar]]))

;; why you no find this one?
(bar 1 2 3)
8 changes: 8 additions & 0 deletions corpus/cljc/test_cljs.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(ns corpus.cljc.test-cljs
(:require [corpus.cljc.test-cljc :refer [foo bar]]))

(foo 1) ;; correct
(foo 1 2) ;; incorrect
(bar 1 2) ;; incorrect

(defn baz [])
12 changes: 12 additions & 0 deletions corpus/exclude_clojure.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(ns clojure.core)

(defn get [x y] x)

(ns corpus.exclude-clojure1
(:refer-clojure :exclude [get]))

(get 1 2 3 4)

(ns corpus.exclude-clojure2)

(get 1 2 3 4)
13 changes: 13 additions & 0 deletions corpus/inline_def.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
(ns inline-def)

(defn foo []
(def x 1))

(defn- foo []
(def x 1))

(def foo (def x 1))

(deftest foo (def x 1))

(defmacro foo [] (def x 1))
11 changes: 11 additions & 0 deletions corpus/invalid_arity/calls.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(ns corpus.invalid-arity.calls
(:require [corpus.invalid-arity.defs
:as x :refer [public-fixed
public-varargs
public-multi-arity]]))

(public-fixed 1)
(x/public-fixed 1)
(corpus.invalid-arity.defs/public-fixed 1)
(public-multi-arity 1 2 3)
(public-varargs 1)
11 changes: 11 additions & 0 deletions corpus/invalid_arity/defs.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(ns corpus.invalid-arity.defs)

(defn public-fixed [x y z] x)
(defn public-multi-arity ([x] (public-multi-arity x false)) ([x y] x))
(defn public-varargs [x y & zs] x)

(public-fixed 1)
(public-multi-arity 1)
(public-multi-arity 1 2)
(public-multi-arity 1 2 3)
(public-varargs 1)
9 changes: 9 additions & 0 deletions corpus/invalid_arity/order.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(ns corpus.invalid-arity.order)

;; call to def special form with docstring
(def x "the number one" 1)
(defmacro def [k spec-form])
;; valid call to macro
(def ::foo int?)
;; invalid call to macro
(def ::foo int? string?)

0 comments on commit a6ee3e3

Please sign in to comment.