clj-kondo

https://github.com/clj-kondo/clj-kondo
dominicm 2021-01-19T11:56:19.041500Z

I noticed that kondo doesn't warn when the same arity is repeated. Pretty minor as Clojure catches it.

(defn foo
  ([x] x)
  ([x] (inc x)))

borkdude 2021-01-19T11:56:36.041700Z

issue welcome!

lread 2021-01-19T18:18:12.042400Z

Hiya! Has anyone ever asked to warn on usage of prefix lists in namespaces? https://stuartsierra.com/2016/clojure-how-to-ns.html#prefix-lists

borkdude 2021-01-19T18:18:33.042700Z

@lee I think this will be part of the next release

lread 2021-01-19T18:18:54.043Z

Awesome!

borkdude 2021-01-19T18:19:06.043200Z

(already on master)

borkdude 2021-01-19T18:19:16.043600Z

oh sorry, you mean, warn on usage of these things at all?

lread 2021-01-19T18:19:23.043900Z

yeah

borkdude 2021-01-19T18:19:30.044100Z

there are some lint warnings about invalid prefix lists

borkdude 2021-01-19T18:19:35.044300Z

recently added

borkdude 2021-01-19T18:19:39.044600Z

but not the one you asked for

lread 2021-01-19T18:19:51.044900Z

like follow Stuart’s advice and avoid using them at all

borkdude 2021-01-19T18:25:06.045200Z

yeah, feel free to add an issue. I think not using them is preferable

lread 2021-01-19T18:25:30.045500Z

ok, thanks, will do

2021-01-19T18:40:09.046300Z

I'd caution against clj-kondo becoming too much like CheckStyle

2021-01-19T18:41:10.047300Z

Catching mistakes and bugs are good, no one will ever complain. Forcing your opinions on a particular style are bad, and people will start to hold grudges and discontent against you πŸ˜›

lread 2021-01-19T18:41:29.047700Z

Yeah but they can be off by default.

2021-01-19T18:41:49.048500Z

For style concerns, focus on automated-styling tools instead. Like a tool that automatically rewrites the ns declaration to your agreed team style

lread 2021-01-19T18:41:49.048600Z

We already have some style linters.

lread 2021-01-19T18:42:45.049400Z

like: missing else branch on if

borkdude 2021-01-19T18:43:44.051Z

@didibus I don't agree. Clj-kondo checks many style things and almost all of them are disabled by default if they are controversial. This is one of the core things of clj-kondo and adding more makes sense, even if not all people use them.

2021-01-19T18:44:12.051700Z

I wouldn't call that a style linter. That can actually be a bug, and I've had it be a bug before where that warning helped me catch the fact my parenthesis were wrong so I was missing an else branch.

2021-01-19T18:44:58.052300Z

If they are disabled by default its a lot better. That said, I would be worried of seeing a thing where like the default lein template drops a pre-configured clj-kondo config into a project with some forced def "style" linters turned on, etc.

2021-01-19T18:45:37.052900Z

I kinda just hope in my dreams that Clojure doesn't devolve to Java's shenanigans. And that for styling we'd focus efforts on re-formatting tools.

2021-01-19T18:47:15.053600Z

But hey, I have CheckStyle PTSD, this is just my 2 cents.

2021-01-19T18:49:43.055700Z

Like a linter is defined as: > lint, or a linter, is a static code analysis tool used to flag programming errors, bugs, stylistic errors, and suspicious constructs And I think this is all good. The "stylistic error" part I guess is maybe the more controversial, but ya if those are off and can be customized I'll be happy. > Checkstyle is a static code analysis tool used in software development for checking if Java source code is compliant with specified coding rules This is bad bad in my opinion πŸ˜›

borkdude 2021-01-19T18:50:09.056600Z

I get your point, don't worry. Most if not all of the opinionated things going into clj-kondo since a while has been disabled by default.

1
2021-01-19T18:50:25.056900Z

Ya, I've been very happy with the choices you've made and have full confidence.

2021-01-19T18:52:00.059300Z

Apology, I think my comment came off as more negative, I wanted to emphasize on the alternative more so than on shutting down the use of clj-kondo for style. Like a team can have their own conventions and I have no issue with that. But what is even more awesome than having something tell you you didn't do things the way the team wants them, is a tool that fixes it automatically for you.

lread 2021-01-19T18:52:08.059400Z

You’ll just be annoyed if submitting PRs to my projects because I’ll have all the linters on. (just teasing)

😭 1
βœ… 1
borkdude 2021-01-19T18:52:58.059900Z

One tool that fixes it automatically for you is clojure-lsp.

borkdude 2021-01-19T18:53:11.060300Z

It's using clj-kondo under the hood and will fix it using your editor integrations.

borkdude 2021-01-19T18:53:33.060900Z

And there are other tools like carve which also do this.

2021-01-19T18:53:58.061500Z

Ya, I think clj-kondo would be a great underlying lib to leverage for that

borkdude 2021-01-19T18:54:09.061800Z

The focus of clj-kondo is code analysis and linting (including style), but it won't provide rewriting

borkdude 2021-01-19T18:54:18.062100Z

Just the output should be enough to write tools on top of this

2021-01-19T18:54:31.062300Z

Totally make sense.

2021-01-19T18:54:53.062600Z

The custom analysis seem to be a good start for that.

borkdude 2021-01-19T18:55:09.063300Z

Thanks for test-driving the :unresolved-var linter.

2021-01-19T18:56:08.064500Z

That said, there is one difference. If the linter is: warn on use of "bad style", it is not letting you choose your preferred style, it is making an opinionated choice on what style is good and bad. Like what if I prefer all my ns to be written the other way? (ns foo (:require [a.b c d e g]))

lread 2021-01-19T18:57:37.065300Z

As I write out the issue, I’m remembering that prefix lists are invalid in cljs. I’ll make a note in the issue.

2021-01-19T18:58:37.066200Z

Ah, now that's a good observation πŸ˜› See now you could almost argue it should be a warn by default, since its now in the category of possible error

lread 2021-01-19T18:59:20.066500Z

I knew I’d win you over. :simple_smile:

2021-01-19T18:59:27.066700Z

πŸ˜›

2021-01-19T19:01:42.067700Z

Ya, I guess its just nuanced. @borkdude has had great instinct I think to put the line in the right place. CheckStyle on the other hand has not.

2021-01-19T19:03:08.069300Z

Like the missing-docstring linter being applied only to public var. Like that's a pretty good way to handle it. Where as in CheckStyle this is one of my most hated linters, but that's because it forces everything to have a docstring. So you'll see stupid things like:

// The customer id
private String customerId;

2021-01-19T19:06:33.072900Z

Also depends how you use it too. CheckStyle is slow to run, and can't be integrated in your editor (well it can but not in a way you get feedback as you type). So people always set it up as a build step or a PR approval step. Pretty frustrating to have it fail when you're ready to push because your private customerId field doesn't have a doc-string. But clj-kondo gives you instant feedback, so I think that helps a lot with frustration. And you could easily set it up to only fail on error, so warns can be a recommendation, like "Probably have a doc-string here, unless the doc-string is a copy/paste of the var name with The added in front πŸ˜›"

lread 2021-01-19T19:06:49.073Z

https://github.com/clj-kondo/clj-kondo/issues/1137, @didibus I was even so cheeky as to include your require example above as my code snippit.

πŸ‘ 1
lread 2021-01-19T19:10:20.075Z

@didibus I sympathize with your PTSD from silly CheckStyle rules. I vaguely remember them too, but the memory for me is fading, and the healing is almost complete.

2021-01-19T19:17:31.077Z

Ya, CheckStyle has some stupid rules, especially when combined with Java, makes it really annoying. In Clojure, sometimes "style" enforcement is more like: Clojure is so loose, that you can't even remember what "style" is a bug and which one is not. Like using :else in cond. That linter is nice, not because it forces me to use :else and not :default, but because I actually never remember if I'm supposed to use some specific keyword or not.

2021-01-19T19:19:20.078600Z

And then you do get a lot of weird stylistic errors, like not knowing that I could easily think this makes sense:

(cond
  (pred? a)
  (foo a)
  :else
  (bar a)
  :default
  (baz a))

2021-01-19T19:21:28.079400Z

By the way, hum, is that a linter? Would be a good one to have: Unreachable cond branch. Or something like that

borkdude 2021-01-19T19:23:56.079900Z

@didibus

$ clj-kondo --lint /tmp/foo.clj
/tmp/foo.clj:2:4: error: Unresolved symbol: pred?
/tmp/foo.clj:2:10: error: Unresolved symbol: a
/tmp/foo.clj:3:4: error: Unresolved symbol: foo
/tmp/foo.clj:5:4: error: Unresolved symbol: bar
/tmp/foo.clj:6:3: warning: unreachable code
/tmp/foo.clj:6:3: warning: use :else as the catch-all test expression in cond
/tmp/foo.clj:7:4: error: Unresolved symbol: baz
linting took 130ms, errors: 5, warnings: 2

2021-01-19T19:24:16.080100Z

> warning: unreachable code Nice!

2021-01-19T19:26:19.080500Z

I'm super excited for the unresolved var linter. Thanks so much for putting the work on it by the way!

2021-01-19T19:27:06.081300Z

A number1 complaint I see from people is always: Refactoring in Clojure is hard cause you can't tell what you broke. And so that linter I think is a huge step to help with that.

borkdude 2021-01-19T19:32:16.081800Z

A few small items to fix and hopefully a release tomorrow or in the weekend... 🀞

πŸ™Œ 2
borkdude 2021-01-19T19:34:19.082400Z

one important thing: it should probably support a config to exclude certain vars or entire namespaces

borkdude 2021-01-19T19:34:34.082800Z

e.g. namespaces that have a lot of auto-generated vars using macros

2021-01-19T19:35:33.083800Z

Yes, I was also wondering, would it work with hooks? Like if I have a hook for a macro that returns:

(do (defn foo ...)
   (def bar ...))
Will clj-kondo then know that this namespace contains a foo and a bar var?

borkdude 2021-01-19T19:35:53.084Z

yes

borkdude 2021-01-19T19:36:42.084600Z

at least, it should

2021-01-19T19:37:18.085500Z

Cool, ya that's great then. I think most macros that generate vars do return a form close to that. So hopefully as more macros provide a hook, the number of vars/namespaces you need to exclude would get smaller.

borkdude 2021-01-19T19:38:02.086100Z

there is also lint-as and clj-kondo.lint-as/def-catch-all, so in many cases no hook needed

2021-01-19T19:38:20.086400Z

Oh ya, that's true

2021-01-19T19:38:25.086700Z

Even better

borkdude 2021-01-19T19:38:51.087300Z

but it still takes effort to configure of course. I was thinking we could also have a runtime clj-kondo plugin that scans your project and emits cache info that can then be used by static analysis

borkdude 2021-01-19T19:39:14.087600Z

so you would at least not get false positives for unresolved vars

2021-01-19T19:41:16.088500Z

Hum... So like it would learn as the code is running? You could have a nRepl middleware maybe. And a way to set it up in your test-runner.

2021-01-19T19:42:27.089300Z

Though there's a disadvantage to that as well. Which is the same with the REPL, its possible that you defed something that you later deleted from the code.

borkdude 2021-01-19T19:42:48.089600Z

yeah, I think you would run it only occassionally from the command line as a JVM CLI tool

borkdude 2021-01-19T19:43:01.090Z

to update the un-analyzable vars

2021-01-19T19:43:47.090600Z

I think if all it did was like load everything and let macros macro-expand. That would make sense.

borkdude 2021-01-19T19:44:07.091100Z

yes, it would only load namespaces, then all-ns, ns-publics, and emit

2021-01-19T19:45:01.092100Z

Ya that would be like automatic hooks. And it only wouldn't work if you're doing side effect, but in practice it would also work, cause you'd know that when running that, just like running your tests, you need to make sure you have the right environment setup.

2021-01-19T19:45:42.092300Z

Its a good idea

borkdude 2021-01-19T19:46:12.093Z

a bit similar: https://github.com/clj-kondo/inspector

2021-01-19T19:46:37.093700Z

I guess it only leaves this:

(ns foo)

(defn foo []
  (def f (fn [a b] (+ a b))))

(ns other (:require [foo :as foo]))

(foo/f 1 2)
As the edge case, where you would still need to use exclusion.

2021-01-19T19:48:38.093800Z

Oh that's nice.

2021-01-19T19:49:21.094Z

Why does it need to be at runtime? When you call emit-types it walks the spec registry?

borkdude 2021-01-19T19:49:31.094200Z

yes

2021-01-19T19:50:51.094400Z

Ya, that's neat. I guess then this say runtime-analyze tool could do both: load namespaces, then all-ns, ns-publics, and emit cache and emit types.

borkdude 2021-01-19T19:52:11.094900Z

Why is that an edge case? This could be seen at runtime as well?

2021-01-19T19:52:30.095Z

For specs though, in theory (and I get that its more work), can't you figure out what the specs are from the require and walking through the namespaces for calls to s/def and s/fdef ?

2021-01-19T19:52:43.095200Z

Like statically

2021-01-19T19:53:18.095800Z

Well, in theory, every run of the program is not guaranteed to have the same defined vars

borkdude 2021-01-19T19:53:20.095900Z

Specs are quite difficult to analyze accurately statically as they can have many indirections, although clj-kondo could do a best-effort attempt

2021-01-19T19:54:04.096100Z

Hum, ya that's true. It does sound tricky since they can refer to others with namespace keys and all.

borkdude 2021-01-19T19:54:15.096500Z

you mean, if you conditionally define vars based on the weather?

borkdude 2021-01-19T19:54:50.096600Z

It can be done, but a lot of work so that's why I went with the runtime approach, just as an experiment

borkdude 2021-01-19T19:54:57.096900Z

Malli does a similar thing

borkdude 2021-01-19T19:55:20.097600Z

Spec still being alpha, I don't want to build this into any tools yet

2021-01-19T19:56:21.098900Z

Hum... actually I think you are correct, I don't think this is even possible, cause the symbol passed to def has to be static.

2021-01-19T19:57:14.099600Z

Like you can't do:

(defn foo [username]
  (def username {}))

borkdude 2021-01-19T19:58:21.100700Z

you can use intern to do this dynamically

2021-01-19T19:58:49.101900Z

I guess you could with a macro:

(defmacro create-user [username]
  `(def ~(symbol username) {}))
And then ya, I mean you'd need to exclude the linter on this namespace, cause you're getting dynamic usernames from the user at runtime and creating vars with them.

borkdude 2021-01-19T19:58:54.102100Z

(if (< 20 degrees-fahrenheit) (intern 'foo.bar 'baz 1) (intern 'foo.baz 'bar 2))

πŸ‘ 1
borkdude 2021-01-19T19:59:29.102500Z

even with the macro, the input to the macro is static

2021-01-19T19:59:48.102900Z

Ya makes sense. I feel spec 2 is actively being worked on as well so.

borkdude 2021-01-19T19:59:52.103100Z

I guess you could also use (eval ...)

borkdude 2021-01-19T20:00:25.103900Z

so barring intern and eval, most of the var names can be statically known, and surely at runtime

2021-01-19T20:00:28.104100Z

Oh ya, you're right. Ok, so only with intern could someone do this? Or is it just not possible?

2021-01-19T20:00:53.104600Z

Ya, I mean, if you're doing eval or intern, you know what you are doing, and won't mind excluding your namespace I think.

borkdude 2021-01-19T20:01:09.104800Z

or excluding only certain vars

2021-01-19T20:02:04.106300Z

Well, I mean, if you're doing a dynamic var def like I'm describing, you don't know what the vars will be because they are based on external input.

borkdude 2021-01-19T20:02:35.106900Z

hugsql is an example that comes to mind. but I know a nice trick for that: Have a dedicated foo.domain.hugsql namespace which is only dedicated to turning sql into vars, without any other fns. This namespace doesn't end up in your cache (because it has no statically analyzable vars) so it won't give you any errors in the unresolved-var linter.

borkdude 2021-01-19T20:03:08.107200Z

I discovered this today at work ;)

2021-01-19T20:03:11.107400Z

Ok ya, that is a good example

2021-01-19T20:03:54.108Z

If you had that runtime caching tool, it would be able to learn the vars generated from hugsql right?

borkdude 2021-01-19T20:04:04.108300Z

yes

2021-01-19T20:05:05.109500Z

I think even a hook for hugsql could work here. If the hook is willing to do a "side-effect". In theory, the hugsql hook could be smart enough to say: "Warn can't find sql files". And if it can find them, to return the defs from it

2021-01-19T20:05:46.110100Z

Unless people often put their SQL definition in a DB or something like that, but I don't think so. They're generally a resource I think

2021-01-19T20:06:25.110700Z

Anyway, ya its a pretty good idea to have a runtime caching system. Seems it could cover a lot of those edge cases.

2021-01-19T20:07:56.111400Z

And still, I feel for all these edge cases, its not that big a deal to exclude the namespace or the vars from the unused-var linter.

borkdude 2021-01-19T20:11:05.112Z

yeah, it's kind of a last resort that should justify itself with enough frustration and so far the frustration hasn't been big enough yet ;)