Fork me on GitHub
#clj-kondo
<
2021-11-05
>
kirill.salykin11:11:32

hi Can clj-kondo lint on unused fns? (eg with zero refs)

borkdude11:11:51

@kirill.salykin you can do this in two ways: ā€¢ Use https://github.com/borkdude/carve as a tool to get a report for unused functions ā€¢ Use #lsp in your editor to see how many references there are for a function (in emacs lsp it's called lens mode)

ericdallo11:11:47

Actually you don't need lens enabled, clojure-lsp will return a diagnostic saying that function is unused public var

kirill.salykin12:11:09

I was more in running it on CI already have it in my emacs šŸ™‚

kirill.salykin12:11:43

carve seems exactly what i needed, thanks!

ericdallo12:11:40

for CI you can check this as well: https://clojure-lsp.io/api/#ci

kirill.salykin12:11:02

wow, it is brilliant! thanks a lot

welcome-to-the-world-of-tomorrow 1
ag20:11:16

So the question I mistakenly posted in #babashka @borkdude you say, use macroexpand hook instead of analyze-call, but I can't figure out, say for example, how do I write a hook for compojure.api things? e.g.:

(context "/api" []
      :tags ["api"]

      (GET "/plus" []
        :return {:result Long}
        :query-params [x :- Long, y :- Long]
        :summary "adds two numbers together"
        (ok {:result (+ x y)}))

      (POST "/echo" []
        :return Pizza
        :body [pizza Pizza]
        :summary "echoes a Pizza"
        (ok pizza)))))
How do I get pizza and x, and y to "be resolved"?

borkdude20:11:16

There are various ways to deal with this. From easy + less precise to hard/more work + more precise errors: 1. Suppress unresolved symbols in compojure.api/context or compojure.api/GET etc, using the configuration. https://github.com/clj-kondo/clj-kondo/blob/master/doc/linters.md#unresolved-symbol

borkdude20:11:46

2. :lint-as can be used if there is a function/macro that behaves syntactically the same (but that doesn't seem to be applicable here)

borkdude20:11:36

3. use :macroexpand hook to write a macro to transform the code into simpler code that clj-kondo can understand. it does not have to transform into code that "works", just into expressions that clj-kondo will lint and make sense for linting. The macro does not have to correspond to the real macro.

borkdude20:11:15

4. use :analyze-call hook. Similar to 3. but this gives you more precise locations for errors.

borkdude20:11:53

So let's say if we shoot for 3, can you come up with a macro that receives the above expression and turns it into something that doesn't have special syntax?

borkdude20:11:30

you can write macros separately for context, GET, POST, etc

borkdude20:11:46

let's focus only on GET for now

ag20:11:37

I've tried 1 and 2 and it didn't work perfectly for me, because then things used within the POST block would not be recognized as used and kondo flags them as unused references. macro I tried, but couldn't figure it out (forgot exact reasons) thought I'm gonna need analyze-call for that, but that turned out to be even more tangled

ag20:11:31

I'll give a try to :macroexpand hook again

borkdude20:11:49

I think 1 should work without any false positives though

borkdude20:11:39

(ns foo
  {:clj-kondo/config '{:linters {:unresolved-symbol {:exclude [(compojure.api/GET)]}}}}
  (:require [compojure.api :refer [GET]]))

(GET "/plus" []
     :return {:result Long}
     :query-params [x :- Long, y :- Long]
     :summary "adds two numbers together"
     (ok {:result (+ x y)}))

borkdude20:11:43

This works without errors

borkdude20:11:16

I don't know the exact namespace for GET, this is just an example

borkdude20:11:55

Also when you type:

(let [foo 3]
  (GET "/plus" []
       :return {:result Long}
       :query-params [x :- Long, y :- Long]
       :summary "adds two numbers together"
       (ok {:result (+ x y foo)})))
then foo isn't marked as unused

borkdude20:11:02

with that config

borkdude20:11:08

are you using the :refer :all like in their README? this could cause problems as clj-kondo has to do guesswork where stuff is coming from

ag20:11:22

Right. okay. I'll keep playing with this. Sorry for being fuzzy on that. I'm space out a bit, because my last attempt was a couple of weeks ago. And cleaning up things with kondo in a huge project is not "a real work", but I'm doing it as a "side-project"

borkdude20:11:44

sure, no problem

borkdude20:11:19

Just come back with one isolated small piece of code where 1 doesn't work for you and I'm confident we can fix it

ag20:11:11

Alright! I guess I was overly excited and wanted to learn how to make custom hooks with :analyze-call, even though might be an overkill for this task šŸ™‚

borkdude20:11:49

we can also do that. I think 3 is easier to get started with

borkdude20:11:14

@U0G75ARHC I think this could be a good start for a macro for GET :

(defmacro GET [path args & {:as opts}]
  [path args (update opts :query-params
                     (fn [params]
                       (list 'fn (vec (remove  #{':-} params)))))])

borkdude20:11:53

(macroexpand '(GET "/plus" []
                   :return {:result Long}
                   :query-params [x :- Long, y :- Long]
                   :summary "adds two numbers together"))

borkdude20:11:59

["/plus" [] {:return {:result Long}, :query-params (fn [x Long y Long]), :summary "adds two numbers together"}]

borkdude20:11:37

it's not perfect yet, but I hope you see the idea

borkdude20:11:07

so that last expression is something which clj-kondo can understand

borkdude20:11:59

you'll have to wrap the body of the GET into the function and then it will also understand the usages within the body

ag21:11:27

Thank you. I'll try this. Really appreciate your help. I think this gives me enough food for thought. I'm sure I'll have to come back again and ask some more questions.

ag22:11:37

Okay, now I remember why I wanted analyze-call.

ag22:11:04

So, I have something like this:

(context "/:id" []
  :query-params [{wait_for :- s/Bool}]
  :path-params [id :- s/Str]
  (PATCH "/" []
    ;; ... id an wait_for are used somewhere here
Using :unresolved-symbol {:exclude would then ignore the actual errors within. So for example I rename id to foo - instead of telling me that foo is unused, it would just ignore that entire block. If I use :hooks :macroexpand, then (even if I do it right) if there's something wrong, it will show the errors all the way at the beginning of the block. These blocks can get too long, it will be annoying to try to find a misplaced var(s).

borkdude22:11:02

Yes, 1) this is a trade-off, you will ignore all false positives, but won't get additional errors. 2) yes, this is also a trade-off of :macroexpand. if you want precise locations you should make this an :analyze-callhook. The implementation is similar to your macro but is based on nodes rather than s-expressions.

borkdude22:11:43

so it's gradually more work but also getting better feedback

borkdude22:11:56

afk now, will respond tomorrow

1