Giter VIP home page Giter VIP logo

clj-kondo's Introduction

Clojars Project Financial Contributors on Open Collective CircleCI Build status cljdoc badge project chat twitter

A static analyzer and linter for Clojure code that sparks joy.

Thanks a lot for clj-kondo. It is like a companion for me. It has made clojure fun again.

@geraldodev on Clojurians Slack

Rationale

Clj-kondo performs static analysis on Clojure, ClojureScript and EDN, without the need of a running REPL. It informs you about potential errors while you are typing.

Features

Clj-kondo detects:

  • inline def expressions
  • redundant do and let wrappings
  • arity errors:
    • within the same namespace and across namespaces
    • of static Java method calls
    • of local let and letfn binding calls
    • of recursive calls (including recur)
    • conflicting arities in overloaded functions
  • unused private vars
  • private and deprecated var usage
  • required but unused namespaces
  • unsorted required namespaces
  • referred but unused vars
  • duplicate requires
  • unused function arguments and let bindings
  • marked as unused, but used arguments and let bindings (optional)
  • unused imports
  • redefined vars
  • unresolved symbols, vars and namespaces
  • misplaced docstrings
  • duplicate map keys and set elements
  • duplicates and quoting in case test constants
  • missing map keys
  • invalid number of forms in binding vectors
  • missing assertions in clojure.test/deftest
  • alias consistency
  • type checking
  • Datalog syntax checking
  • format string argument mismatches
  • shadowed vars
  • 2 argument usage of reduce (optional)

before your form hits the REPL.

It suggests several style guide recommendations, such as:

It has support for syntax of commonly used macros like clojure.core.async/alt!!, schema.core/defn and potemkin/import-vars.

It detects common errors in deps.edn and bb.edn

It provides analysis data so you build your own custom linters.

View all available linters here.

This linter is:

  • compatible with .clj, .cljs, .cljc and .edn files
  • build tool and editor agnostic
  • a static code analyzer
  • compiled to native code using GraalVM

Try clj-kondo at the interactive playground.

Watch the talk:

Clj-kondo at ClojuTRE 2019

Support ❤️

You can support this project via Github Sponsors, OpenCollective, Ko-fi or indirectly via Clojurists Together.

Top sponsors

Usage

Command line

Lint from stdin:

$ echo '(def x (def x 1))' | clj-kondo --lint -
<stdin>:1:8: warning: inline def

Lint a file:

$ echo '(def x (def x 1))' > /tmp/foo.clj
$ clj-kondo --lint /tmp/foo.clj
/tmp/foo.clj:1:8: warning: inline def

Lint a directory:

$ clj-kondo --lint src
src/clj_kondo/test.cljs:7:1: warning: redundant do
src/clj_kondo/calls.clj:291:3: error: Wrong number of args (1) passed to clj-kondo.calls/analyze-calls

Lint a project classpath:

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

Project setup

To detect lint errors across namespaces in your project, a cache is needed. To let clj-kondo know where to create one, make a .clj-kondo directory in the root of your project, meaning on the same level as your project.clj, deps.edn or build.boot. A cache will be created inside of it when you run clj-kondo. Before linting inside your editor, it is recommended to lint the entire classpath to teach clj-kondo about all the libraries you are using, including Clojure and/or ClojureScript itself:

$ clj-kondo --lint "<classpath>" --dependencies --parallel --copy-configs

The --dependencies flag indicates that clj-kondo is used to analyze sources to populate the cache. When enabled, clj-kondo will suppress warnings and skips over already linted .jar files for performance.

The --parallel option will use multiple threads to lint your sources, going through them faster.

The --copy-configs flag will search and copy configurations from dependencies into the .clj-kondo directory, while linting (see config.md).

Build tool specific ways to get a classpath:

  • lein classpath
  • boot with-cp -w -f -
  • clojure -Spath
  • npx shadow-cljs classpath

So for lein the entire command would be:

$ clj-kondo --lint "$(lein classpath)" --dependencies --parallel --copy-configs

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

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

Since clj-kondo now knows about your version of ClojureScript via the cache, it detects that the number of arguments you passed to select-keys is invalid. Each time you edit a file, the cache is incrementally updated, so clj-kondo is informed about new functions you just wrote.

If you want to use a different directory to read and write the cache, use the --cache-dir option. To disable the cache even if you have a .clj-kondo directory, use --cache false.

Exit codes

Exit codes can be controlled by the --fail-level <level> option. The default fail level is warning which returns exit codes as follows:

  • 0: no errors or warnings were found
  • 2: one or more warnings were found
  • 3: one or more errors were found

If --fail-level error is supplied, warnings do not lead to a non-zero exit code:

  • 0: no errors were found
  • 0: one or more warnings were found
  • 3: one or more errors were found

All exit codes other than 0, 2 and 3 indicate an error because of a bug in clj-kondo or some other unexpected error beyond the control of clj-kondo.

Companies using clj-kondo

Macros

As clj-kondo is a static analyzer is does not need a runtime (JVM, browser, Node.js, etc.). It doesn't execute your code. As such it can be a faster alternative to linters that do use a runtime, like eastwood. This approach comes with the limitation that clj-kondo cannot execute your macros as macros can use arbitrary features from a runtime. Clj-kondo has support for clojure core macros and some popular libraries from the community. Macros that are not supported out of the box can be supported using configuration. One of the ways to configure macros is to write hooks for them (also see this blogpost). For many libraries there is already a configuration available that you can import. Also check out clj-kondo configs which contains configurations for third party libraries.

Babashka pod

Clj-kondo can be invoked as a babashka pod.

#!/usr/bin/env bb
(ns script
  (:require [babashka.pods :as pods]))

(pods/load-pod "clj-kondo")
(require '[pod.borkdude.clj-kondo :as clj-kondo])

(clj-kondo/merge-configs
 '{:linters {:unresolved-symbol {:exclude [(foo1.bar)]}}}
 '{:linters {:unresolved-symbol {:exclude [(foo2.bar)]}}})
;;=> {:linters {:unresolved-symbol {:exclude [(foo1.bar) (foo2.bar)]}}}

(-> (clj-kondo/run! {:lint ["src"]})
    :summary)
;;=> {:error 0, :warning 0, :info 0, :type :summary, :duration 779}

Podcasts

Articles

Thanks to:

License

Copyright © 2019 - 2023 Michiel Borkent

Distributed under the EPL License, same as Clojure. See LICENSE.

The directory inlined contains source from tools.reader which is licensed under the EPL license.

The directory parser contains modified source from rewrite-clj which is licensed under the MIT license.

clj-kondo's People

Contributors

abogoyavlensky avatar benedekfazekas avatar borkdude avatar cosineblast avatar dancek avatar delaguardo avatar dpassen avatar ericdallo avatar heliosmaster avatar imrekoszo avatar jysandy avatar laurio avatar lispyclouds avatar lread avatar mainej avatar marcomorain avatar mk avatar mknoszlig avatar mrkam2 avatar noahtheduke avatar robert-stuttaford avatar severeoverfl0w avatar snoe avatar sogaiu avatar sumbach avatar timothypratley avatar tomdl89 avatar vemv avatar volrath avatar yuhan0 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

clj-kondo's Issues

Optimize linting single file with project cache

Currently the entire cache (an EDN file) is read and written each time clj-kondo is run.
We can optimize this by splitting the cache into files per namespace.

Reads:
When making calls to ns1, ns2 ..., we only need to read ns1.edn, ns2.edn.
Writes:
When detecting new function definitions for ns1 we only need to write ns1.edn.

Running without graalvm causes error

❯ clj -Sdeps '{:deps {clj-kondo {:git/url "https://github.com/borkdude/clj-kondo" :sha "79c67cb9d3dbe0727e6c1e93066c16da36206f1a"}}}' -m clj-kondo.main --lint src
Exception in thread "main" Syntax error compiling at (main.clj:12:24).
        at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3707)
        at clojure.lang.Compiler$DefExpr.eval(Compiler.java:457)
        at clojure.lang.Compiler.eval(Compiler.java:7181)
        at clojure.lang.Compiler.load(Compiler.java:7635)
        at clojure.lang.RT.loadResourceScript(RT.java:381)
        at clojure.lang.RT.loadResourceScript(RT.java:372)
        at clojure.lang.RT.load(RT.java:463)
        at clojure.lang.RT.load(RT.java:428)
        at clojure.core$load$fn__6824.invoke(core.clj:6126)
        at clojure.core$load.invokeStatic(core.clj:6125)
        at clojure.core$load.doInvoke(core.clj:6109)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invokeStatic(core.clj:5908)
        at clojure.core$load_one.invoke(core.clj:5903)
        at clojure.core$load_lib$fn__6765.invoke(core.clj:5948)
        at clojure.core$load_lib.invokeStatic(core.clj:5947)
        at clojure.core$load_lib.doInvoke(core.clj:5928)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invokeStatic(core.clj:667)
        at clojure.core$load_libs.invokeStatic(core.clj:5985)
        at clojure.core$load_libs.doInvoke(core.clj:5969)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invokeStatic(core.clj:667)
        at clojure.core$require.invokeStatic(core.clj:6007)
        at clojure.main$main_opt.invokeStatic(main.clj:491)
        at clojure.main$main_opt.invoke(main.clj:487)
        at clojure.main$main.invokeStatic(main.clj:598)
        at clojure.main$main.doInvoke(main.clj:561)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:705)
        at clojure.main.main(main.java:37)
Caused by: java.io.FileNotFoundException: VERSION (No such file or directory)
        at java.io.FileInputStream.open0(Native Method)
        at java.io.FileInputStream.open(FileInputStream.java:195)
        at java.io.FileInputStream.<init>(FileInputStream.java:138)
        at clojure.java.io$fn__11466.invokeStatic(io.clj:229)
        at clojure.java.io$fn__11466.invoke(io.clj:229)
        at clojure.java.io$fn__11379$G__11372__11386.invoke(io.clj:69)
        at clojure.java.io$fn__11478.invokeStatic(io.clj:258)
        at clojure.java.io$fn__11478.invoke(io.clj:254)
        at clojure.java.io$fn__11379$G__11372__11386.invoke(io.clj:69)
        at clojure.java.io$fn__11440.invokeStatic(io.clj:165)
        at clojure.java.io$fn__11440.invoke(io.clj:165)
        at clojure.java.io$fn__11392$G__11368__11399.invoke(io.clj:69)
        at clojure.java.io$reader.invokeStatic(io.clj:102)
        at clojure.java.io$reader.doInvoke(io.clj:86)
        at clojure.lang.RestFn.invoke(RestFn.java:410)
        at clojure.lang.AFn.applyToHelper(AFn.java:154)
        at clojure.lang.RestFn.applyTo(RestFn.java:132)
        at clojure.core$apply.invokeStatic(core.clj:667)
        at clojure.core$slurp.invokeStatic(core.clj:6942)
        at clojure.core$slurp.doInvoke(core.clj:6942)
        at clojure.lang.RestFn.invoke(RestFn.java:410)
        at clojure.lang.AFn.applyToHelper(AFn.java:154)
        at clojure.lang.RestFn.applyTo(RestFn.java:132)
        at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3702)
        ... 30 more

Support arity checking for common Java static methods

We can do this by stubbing out static class methods as Clojure functions:

$ clj-kondo --lint -
(ns java.lang.Thread)
(defn sleep ([ms]) ([ms nanos]))
(ns user (:require [java.lang.Thread :as Thread]))
(Thread/sleep 1 2 3)
<stdin>:4:1: error: Wrong number of args (3) passed to java.lang.Thread/sleep
linting took 99658ms, errors: 1, warnings: 0

These stub files we can put in a stubs directory (don't put this dir on the classpath).
Then we analyze this and write to a cache. These cache files will then be used as built-ins.

We can add an implicit require like the example above to every analyzed namespace.

Maybe we can parse javadocs like https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html
and scan for static methods, collect the arities and emit transit directly, bypassing the linter.

Quick POC with e.g.: https://github.com/RaidAndFade/javadoc-json-doclet

javadoc -doclet com.raidandfade.JsonDoclet.Main -docletpath builds/json-jdoc.jar ~/git/jdk/src/java.base/share/classes/java/lang/Thread.java

Example of using doclet with clojure:
https://github.com/oakes/play-clj/tree/3b7b7544e04ced125407d4ba4f0f0368e8f9caed/doclet

See the javadoc branch:

clj -Sdeps "{:deps {tools.jar {:local/root \"$JAVA_HOME/lib/tools.jar\"}}}"
(import 'com.sun.tools.javadoc.Main)
(compile 'clj-kondo.Doclet)
(Main/execute (into-array ["clojure.lang" "-classpath" "/Users/Borkdude/git/clojure/src/jvm" "-doclet" "clj_kondo.Doclet"]))
alexmiller [5:32 PM]
 that stuff is dead

borkdude [5:32 PM]
 is there something newer I should use?
alexmiller [5:33 PM]
 yes - javax.tools.DocumentationTool
I don't know the coords for it, but you should just be using it as normal Maven artifact
borkdude [5:34 PM]
 that seems to work out of the box:
```$ clj
Clojure 1.10.0
user=> (import 'javax.tools.DocumentationTool)
javax.tools.DocumentationTool```

Also see the migration part in: https://docs.oracle.com/en/java/javase/11/docs/api/jdk.javadoc/jdk/javadoc/doclet/package-summary.html

Support in-ns

Currently we don't support in-ns: we just ignore it.

We should only handle top level in-ns expression and ignore those in function bodies.

Right now when we handle a namespace clj-kondo thinks it sees the entire thing. So next time we see a namespace, it replaces the entire old one in the cache. This handles deleting functions well. This should still work when implementing in-ns, so we should write a test for it.

Experiment with clojure.spec for parsing defn forms

E.g.:

(s/conform :clojure.core.specs.alpha/defn-args
             '[foo
               ([x y z] (+ x y z))])

;;=>

{:fn-name foo,
 :fn-tail
 [:arity-n
  {:bodies
   [{:params {:params [[:local-symbol x] [:local-symbol y] [:local-symbol z]]},
     :body [:body [(+ x y z)]]}]}]}

Config

See this config file

clj -A:clj-kondo --lint $(clj -Spath) --config '{:output {:remove ["^clojure" "^cljs" "^rewrite_clj"]}}'
clj -A:clj-kondo --lint $(clj -Spath) --config '{:linters {:cond-without-else {:level :off}}}'

Might change to:

{;; ;; *** lint contents inside comment sections, set to truthy to skip them
 ;; :skip-comments false

 ;; ;; *** linter level can be tweaked by setting :level to :error, :warn or :info (or any other keyword)
 ;; ;; *** all linters are enabled by default, but can be turned off by setting :level to :off.
 ;; :incorrect-arity {:level :error}
 ;; :private-call {:level :error}
 ;; :inline-def {:level :warning}
 ;; :redundant-do {:level :warning}
 ;; :redundant-let {:level :warning}
 ;; :cond-without-else {:level :warning}
 ;; :missing-test-assertion {:level :warning}

 ;; ;; *** set to truthy to print progress while linting
 ;; :progress false


 ;; ;; *** output can be filtered and removed by regex on filename. empty options leave the output untouched.
 ;; :include ["^src" "^test"]
 ;; :exclude ["^cljs/core"]

 ;; ;; *** the output pattern can be altered using a template:
 ;; :pattern "{{file}}:{{row}}:{{col}}: {{level}}: {{message}}"
 }

Reader conditional support for defns and calls

E.g.:

#?(:cljs
   (defn macroexpand [env form]
     (loop [form form
            form' (ana/macroexpand-1 env form)]
       (if-not (identical? form form')
         (recur form' (ana/macroexpand-1 env form'))
         form'))))

(defn- pll-mod-body [env var-sym body]
  (letfn [(inner [form]
                 (if (seq? form)
                   (let [form #?(:clj  (macroexpand form)
                                 :cljs (macroexpand env form))]
                     (condp = (first form)
                       'loop* form
                       'recur (concat `(recur (inc ~var-sym)) (rest form))
                       (walk/walk inner identity form)))
                   form))]
    (walk/walk inner identity body)))

gives

cljs/pprint.cljc:93:40: error: Wrong number of args (1) passed to macroexpand

clj-kondo chokes on string namespaces in require

CLJS projects can have string namespaces. E.g.:

(ns myapp
  (:require [reagent.core :as r :refer [atom]]
            ["react-native" :as rn :refer [AppRegistry]]
            ["react-native-navigation" :as rnn]
            [env]))

Currently clj-kondo chokes on processing theses file. Moreover, it doesn't process the rest of the directory when one of the files is not readable. Both issues should be fixed.

Make calls order-aware of defns

On line 342 of clojure.spec.alpha.clj a macro is defined with the name def, but earlier calls already get resolved as using this macro.

Write test for caching

When only linting clj(s) it should not read and write the cache for the other language.

Suggest for modifiers

(for [i (range 10)]
    (let [j (* i 10)]
      j))

=>

(for [i (range 10)
        :let [j (* i 10)]]
    j)

Better local error handling

Instead of rejecting a whole file, we should report an error only on parts that could not be processed well, like namespace declarations.

clj-kondo creates cache directory in cwd if .clj-kondo doesn't exist

clj-kondo should print a warning if the --cache option exists, but isn't specified. the behavior is that it searches for the nearest .clj-kondo directory, but if this directory doesn't exist it now makes a .cache directory in the current working directory. Instead it should print a warning that the config directory could not be found.

Suggest using str/join

When detecting something like (str "foo" "/" "bar" "/" "baz") the linter might suggest using (str/join "/" ["foo" "bar" "baz"]).

Null pointer exception with nested namespaced maps

When invoking clj-kondo on some code I got a null pointer exception. I have reduced it to a minimal test case

(ns ck.core
  (:require [ck.it :as it])
  (:gen-class))

(defn -main
  [& args]
  (println #::it {:a #::it {}}))

I get the error shown below. I've tried various combinations and it seems the nested use of auto-resolved namespaced keywords triggers it.

Probably related, if I use the current namespace, i.e. #:: {:a #:: {}}, I get "[line 7, col 16] A single colon is not a valid keyword."

  • Debian GNU/Linux 9.8 (stretch) x64
  • Clojure 1.10.0
  • openjdk 1.8.0_181
$ lein clj-kondo src/ck/core.clj
Exception in thread "main" Syntax error compiling at (/tmp/form-init2114187965721477842.clj:1:73).
	at clojure.lang.Compiler.load(Compiler.java:7647)
	at clojure.lang.Compiler.loadFile(Compiler.java:7573)
	at clojure.main$load_script.invokeStatic(main.clj:452)
	at clojure.main$init_opt.invokeStatic(main.clj:454)
	at clojure.main$init_opt.invoke(main.clj:454)
	at clojure.main$initialize.invokeStatic(main.clj:485)
	at clojure.main$null_opt.invokeStatic(main.clj:519)
	at clojure.main$null_opt.invoke(main.clj:516)
	at clojure.main$main.invokeStatic(main.clj:598)
	at clojure.main$main.doInvoke(main.clj:561)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.lang.Var.applyTo(Var.java:705)
	at clojure.main.main(main.java:37)
Caused by: java.lang.NullPointerException
	at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
	at clojure.lang.Namespace.find(Namespace.java:188)
	at clojure.core$find_ns.invokeStatic(core.clj:4130)
	at clojure.core$the_ns.invokeStatic(core.clj:4160)
	at clojure.core$ns_name.invokeStatic(core.clj:4164)
	at clojure.core$ns_name.invoke(core.clj:4164)
	at rewrite_clj.node.seq.NamespacedMapNode.sexpr(seq.clj:57)
	at rewrite_clj.node.protocols$eval231$fn__275$G__237__277.invoke(protocols.clj:9)
	at rewrite_clj.node.protocols$eval231$fn__275$G__236__280.invoke(protocols.clj:9)
	at clojure.core$map$fn__5851.invoke(core.clj:2755)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.Cons.next(Cons.java:39)
	at clojure.lang.RT.boundedLength(RT.java:1788)
	at clojure.lang.RestFn.applyTo(RestFn.java:130)
	at clojure.core$apply.invokeStatic(core.clj:665)
	at clojure.core$apply.invoke(core.clj:660)
	at rewrite_clj.node.seq$map_node$fn__2119.invoke(seq.clj:110)
	at rewrite_clj.node.seq.SeqNode.sexpr(seq.clj:16)
	at rewrite_clj.node.protocols$eval231$fn__275$G__237__277.invoke(protocols.clj:9)
	at rewrite_clj.node.protocols$eval231$fn__275$G__236__280.invoke(protocols.clj:9)
	at clojure.core$map$fn__5851.invoke(core.clj:2753)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.RT.seq(RT.java:531)
	at clojure.lang.RT.countFrom(RT.java:646)
	at clojure.lang.RT.count(RT.java:639)
	at rewrite_clj.node.seq$assert_namespaced_map_children.invokeStatic(seq.clj:40)
	at rewrite_clj.node.seq$assert_namespaced_map_children.invoke(seq.clj:37)
	at rewrite_clj.node.seq$namespaced_map_node.invokeStatic(seq.clj:115)
	at rewrite_clj.node.seq$namespaced_map_node.invoke(seq.clj:112)
	at rewrite_clj.parser.core$eval2727$fn__2728.invoke(core.clj:123)
	at clojure.lang.MultiFn.invoke(MultiFn.java:229)
	at rewrite_clj.reader$read_with_meta.invokeStatic(reader.clj:132)
	at rewrite_clj.reader$read_with_meta.invoke(reader.clj:128)
	at rewrite_clj.parser.core$parse_next.invokeStatic(core.clj:35)
	at rewrite_clj.parser.core$parse_next.invoke(core.clj:33)
	at rewrite_clj.parser.core$parse_delim$fn__2683.invoke(core.clj:43)
	at rewrite_clj.reader$read_repeatedly$fn__1399.invoke(reader.clj:141)
	at clojure.core$repeatedly$fn__6448.invoke(core.clj:5149)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.RT.seq(RT.java:531)
	at clojure.core$seq__5387.invokeStatic(core.clj:137)
	at clojure.core$take_while$fn__5902.invoke(core.clj:2904)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.Cons.next(Cons.java:39)
	at clojure.lang.RT.next(RT.java:709)
	at clojure.core$next__5371.invokeStatic(core.clj:64)
	at clojure.core$dorun.invokeStatic(core.clj:3142)
	at clojure.core$doall.invokeStatic(core.clj:3148)
	at clojure.core$doall.invoke(core.clj:3148)
	at rewrite_clj.reader$read_repeatedly.invokeStatic(reader.clj:143)
	at rewrite_clj.reader$read_repeatedly.invoke(reader.clj:137)
	at rewrite_clj.parser.core$parse_delim.invokeStatic(core.clj:44)
	at rewrite_clj.parser.core$parse_delim.invoke(core.clj:39)
	at rewrite_clj.parser.core$eval2751$fn__2752.invoke(core.clj:172)
	at clojure.lang.MultiFn.invoke(MultiFn.java:229)
	at rewrite_clj.reader$read_with_meta.invokeStatic(reader.clj:132)
	at rewrite_clj.reader$read_with_meta.invoke(reader.clj:128)
	at rewrite_clj.parser.core$parse_next.invokeStatic(core.clj:35)
	at rewrite_clj.parser.core$parse_next.invoke(core.clj:33)
	at rewrite_clj.parser.core$parse_delim$fn__2683.invoke(core.clj:43)
	at rewrite_clj.reader$read_repeatedly$fn__1399.invoke(reader.clj:141)
	at clojure.core$repeatedly$fn__6448.invoke(core.clj:5149)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.RT.seq(RT.java:531)
	at clojure.core$seq__5387.invokeStatic(core.clj:137)
	at clojure.core$take_while$fn__5902.invoke(core.clj:2904)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.Cons.next(Cons.java:39)
	at clojure.lang.RT.next(RT.java:709)
	at clojure.core$next__5371.invokeStatic(core.clj:64)
	at clojure.core$dorun.invokeStatic(core.clj:3142)
	at clojure.core$doall.invokeStatic(core.clj:3148)
	at clojure.core$doall.invoke(core.clj:3148)
	at rewrite_clj.reader$read_repeatedly.invokeStatic(reader.clj:143)
	at rewrite_clj.reader$read_repeatedly.invoke(reader.clj:137)
	at rewrite_clj.parser.core$parse_delim.invokeStatic(core.clj:44)
	at rewrite_clj.parser.core$parse_delim.invoke(core.clj:39)
	at rewrite_clj.parser.core$eval2751$fn__2752.invoke(core.clj:172)
	at clojure.lang.MultiFn.invoke(MultiFn.java:229)
	at rewrite_clj.reader$read_with_meta.invokeStatic(reader.clj:132)
	at rewrite_clj.reader$read_with_meta.invoke(reader.clj:128)
	at rewrite_clj.parser.core$parse_next.invokeStatic(core.clj:35)
	at rewrite_clj.parser.core$parse_next.invoke(core.clj:33)
	at rewrite_clj.parser$parse.invokeStatic(parser.clj:13)
	at rewrite_clj.parser$parse.invoke(parser.clj:10)
	at rewrite_clj.parser$parse_all$fn__2768.invoke(parser.clj:18)
	at clojure.core$repeatedly$fn__6448.invoke(core.clj:5149)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.RT.seq(RT.java:531)
	at clojure.core$seq__5387.invokeStatic(core.clj:137)
	at clojure.core$take_while$fn__5902.invoke(core.clj:2904)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.Cons.next(Cons.java:39)
	at clojure.lang.RT.next(RT.java:709)
	at clojure.core$next__5371.invokeStatic(core.clj:64)
	at clojure.core$dorun.invokeStatic(core.clj:3142)
	at clojure.core$doall.invokeStatic(core.clj:3148)
	at clojure.core$doall.invoke(core.clj:3148)
	at rewrite_clj.parser$parse_all.invokeStatic(parser.clj:20)
	at rewrite_clj.parser$parse_all.invoke(parser.clj:15)
	at rewrite_clj.parser$parse_string_all.invokeStatic(parser.clj:35)
	at rewrite_clj.parser$parse_string_all.invoke(parser.clj:32)
	at clj_kondo.core$process_input.invokeStatic(core.clj:95)
	at clj_kondo.core$process_input.invoke(core.clj:87)
	at clj_kondo.core$process_file.invokeStatic(core.clj:105)
	at clj_kondo.core$process_file.invoke(core.clj:102)
	at clojure.core$map$fn__5851.invoke(core.clj:2755)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.RT.seq(RT.java:531)
	at clojure.core$seq__5387.invokeStatic(core.clj:137)
	at clojure.core$apply.invokeStatic(core.clj:660)
	at clojure.core$mapcat.invokeStatic(core.clj:2783)
	at clojure.core$mapcat.doInvoke(core.clj:2783)
	at clojure.lang.RestFn.invoke(RestFn.java:423)
	at clj_kondo.main$_main.invokeStatic(main.clj:24)
	at clj_kondo.main$_main.doInvoke(main.clj:18)
	at clojure.lang.RestFn.invoke(RestFn.java:423)
	at clojure.lang.Var.invoke(Var.java:388)
	at user$eval140.invokeStatic(form-init2114187965721477842.clj:1)
	at user$eval140.invoke(form-init2114187965721477842.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7176)
	at clojure.lang.Compiler.eval(Compiler.java:7166)
	at clojure.lang.Compiler.load(Compiler.java:7635)
	... 12 more

clj-kondo fails with `No maching field found: getMessage for class java.lang.NullPointerException` when initializing cache

I've just installed clj-kondo via homebrew on my Mac OS X (10.14.3) and tried to run it on a closed-source project (fairly non-trivial with lots of dependencies) but cache initialization fails after a while with the following error:

$ clj-kondo --version
clj-kondo v2019.04.07-alpha

$ clj-kondo --lint $(lein classpath) --cache
...
Exception in thread "main" java.lang.IllegalArgumentException: No matching field found: getMessage for class java.lang.NullPointerException
        at clojure.lang.Reflector.getInstanceField(Reflector.java:397)
        at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:440)
        at clojure.stacktrace$print_throwable.invokeStatic(stacktrace.clj:43)
        at clojure.stacktrace$print_stack_trace.invokeStatic(stacktrace.clj:55)
        at clojure.stacktrace$print_stack_trace.invoke(stacktrace.clj:48)
        at clojure.stacktrace$print_stack_trace.invokeStatic(stacktrace.clj:53)
        at clojure.stacktrace$print_stack_trace.invoke(stacktrace.clj:48)
        at clj_kondo.main$_main$fn__4224.invoke(main.clj:269)
        at clj_kondo.main$_main.invokeStatic(main.clj:267)
        at clj_kondo.main$_main.doInvoke(main.clj:265)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clj_kondo.main.main(Unknown Source)

I tried the same on my clojure-experiments public project and it fails again with the same error

I've tried both Java 8 and Java 11 => the same result.

I've got a bunch of extra dependencies in my ~/.lein/profiles.clj so I thought they may affect it too.
However, I ran this using only base lein profile and the result is again the same:

clj-kondo --lint $(lein with-profile base classpath) --cache

WARNING!!! version ranges found for:
[net.cgrand/sjacket "0.1.1"] -> [org.clojure/clojure "[1.3.0,)"]
Consider using [net.cgrand/sjacket "0.1.1" :exclusions [org.clojure/clojure]].
[net.cgrand/sjacket "0.1.1"] -> [net.cgrand/regex "1.1.0"] -> [org.clojure/clojure "[1.2.0,)"]
Consider using [net.cgrand/sjacket "0.1.1" :exclusions [org.clojure/clojure]].
[net.cgrand/sjacket "0.1.1"] -> [net.cgrand/parsley "0.9.2"] -> [net.cgrand/regex "1.1.0"] -> [org.clojure/clojure "[1.2.0,)"]
Consider using [net.cgrand/sjacket "0.1.1" :exclusions [org.clojure/clojure]].

Exception in thread "main" java.lang.IllegalArgumentException: No matching field found: getMessage for class java.lang.NullPointerException
        at clojure.lang.Reflector.getInstanceField(Reflector.java:397)
        at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:440)
        at clojure.stacktrace$print_throwable.invokeStatic(stacktrace.clj:43)
        at clojure.stacktrace$print_stack_trace.invokeStatic(stacktrace.clj:55)
        at clojure.stacktrace$print_stack_trace.invoke(stacktrace.clj:48)
        at clojure.stacktrace$print_stack_trace.invokeStatic(stacktrace.clj:53)
        at clojure.stacktrace$print_stack_trace.invoke(stacktrace.clj:48)
        at clj_kondo.main$_main$fn__4224.invoke(main.clj:269)
        at clj_kondo.main$_main.invokeStatic(main.clj:267)
        at clj_kondo.main$_main.doInvoke(main.clj:265)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clj_kondo.main.main(Unknown Source)

`clj-kondo update` command

Inspired by the circleci command line tool:

clj-kondo update
clj-kondo update --unstable
clj-kondo update --check
clj-kondo update --check --unstable

spec key typos

E.g.:

(s/fdef foo/foo :args (s/cat :x (s/keys :req-un [::bar]))))

(foo/foo {:baz 1})

info: did you mean bar?

minor improvements

  • make it clear in the README that clj-kondo is a static code analyzer
  • use no-doc on the macroexpand namespace
  • add screenshot of working IntelliJ setup

Improve help printing

The --files option should read --lint.
Make it clear that the .clj-kondo directory is not the caching directory, but a caching directory will be created inside of it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.