clj-kondo

https://github.com/clj-kondo/clj-kondo
Cnly 2020-08-06T13:02:14.290900Z

Quick question, does clj-kondo give warnings about unused public functions?

borkdude 2020-08-06T13:03:29.291400Z

@cnly No. But there is a tool for this based on clj-kondo: https://github.com/borkdude/carve

Cnly 2020-08-06T13:04:19.291900Z

:thumbsup: Thanks!

Karol Wójcik 2020-08-06T16:56:22.293Z

@borkdude Is there a tutorial or docs how to test & write custom hooks? I would love to tweak rum.core/defcs in free time

borkdude 2020-08-06T17:01:13.293900Z

@karol.wojcik This is the most recent one: https://github.com/borkdude/clj-kondo/blob/master/doc/config.md#hooks

Karol Wójcik 2020-08-07T07:38:11.297500Z

Finally got it working

(defn defcs
  [{:keys [node]}]
  (let [args (rest (:children node))
        component-name (first args)
        args (next args)
        body (loop [args* args
                    mixins []]
               (if (seq args*)
                 (let [a (first args*)]
                   (if (vector? (api/sexpr a))
                     (cons a (concat mixins (rest args*)))
                     (recur (rest args*)
                            (conj mixins a))))
                 args))
        partial-state (api/list-node [(api/token-node 'partial)
                                    (api/list-node (list* (api/token-node 'fn) body))
                                    (api/token-node 'state)])
        new-node (api/list-node
                  [(api/token-node 'let)
                   (api/vector-node
                    [(api/token-node 'state) (api/token-node {})])
                   (with-meta (api/list-node [(api/token-node 'def)
                                              component-name
                                              partial-state])
                     (meta node))])]
    {:node new-node}))

Karol Wójcik 2020-08-07T07:38:21.297700Z

Thank you for help @borkdude you are golden!

borkdude 2020-08-07T07:41:01.297900Z

Cool! What is the partial state for?

Karol Wójcik 2020-08-07T07:43:09.298100Z

It's for making sure that state is provided

borkdude 2020-08-07T07:43:40.298300Z

I think this generates something like:

(let [state {}] (def my-component (partial (fn [...] ...) state)))
Why not:
(defn my-component [args] (let [state {}] body)
?

Karol Wójcik 2020-08-07T07:44:14.298600Z

Is there a difference?

borkdude 2020-08-07T07:44:40.298800Z

Syntactically yes, clj-kondo can see the argument count this way for example?

Karol Wójcik 2020-08-07T07:45:13.299Z

So in partial it's not seen?

borkdude 2020-08-07T07:45:15.299200Z

You might want to do:

(defn my-component [args] (let [state {}] state ... body ...)
to make state being used, so it won't be reported.

borkdude 2020-08-07T07:45:24.299400Z

No

Karol Wójcik 2020-08-07T07:45:35.299600Z

Hmm....

Karol Wójcik 2020-08-07T07:46:52.299800Z

Frankly I want the state to be reported

borkdude 2020-08-07T07:47:02.300Z

ah

borkdude 2020-08-07T07:47:07.300200Z

that's ok then, don't use it

Karol Wójcik 2020-08-07T07:47:34.300400Z

Ahh wait.

borkdude 2020-08-07T07:47:39.300600Z

so if you don't use state, then you're going to use def-something-else in rum right?

Karol Wójcik 2020-08-07T07:47:43.300800Z

So I can just ignore the first arg?

borkdude 2020-08-07T07:48:06.301Z

why would you want to ignore the first arg?

Karol Wójcik 2020-08-07T07:49:13.301200Z

I think that we are talking past each other 😄

borkdude 2020-08-07T07:49:40.301400Z

I think so too. Maybe it's better to talk in examples. Paste an example of Rum usage and the desired sexpr for clj-kondo

Karol Wójcik 2020-08-07T07:57:21.301700Z

@borkdude My goal is to make sure that both not used state is reported and function that takes like 2 (except state) args then when I provide only one argument then I want clj-kondo to report that not all function parameters are passed. Let the demo.cljs be defined like so:

(rum/defcs SomeComponent <
  {:did-mount (fn [state] state)}
  [state input another]
  (let [x "Hello"]
    nil))

(rum/defc SomeComponent1 <
  {:did-mount (fn [state] state)}
  [input]
  input)


(SomeComponent "hello")
After running clj-kondo I got the following things reported:
demo.cljs:9:4: warning: unused binding state
demo.cljs:9:10: warning: unused binding input
demo.cljs:9:16: warning: unused binding another
demo.cljs:10:9: warning: unused binding x
linting took 17ms, errors: 0, warnings: 4
That's perfectly fine. What I need more is just the warning that not all parameters to function has been passed. My question is how I can have that last warning working? What I understand is that I should not use 'partial, but how should I construct sexpr?

borkdude 2020-08-07T08:33:15.302Z

@karol.wojcik I think more like (defn my-component [args] (let [state {}] body), so more like how it was originally, but with the extra let around the body

borkdude 2020-08-07T10:01:19.302200Z

(defsc foo [state x] body1 body2) => (defn foo [x] (let [state {}] body1 body2)

borkdude 2020-08-07T10:02:23.302400Z

So you call it as (f 1) and clj-kondo will think this is correct, since the generated form only receives one arg (or n-1 args in general) and state is still recognized as a valid binding.

borkdude 2020-08-07T10:03:12.302700Z

and (f 1 2) should trigger an arity warning

Karol Wójcik 2020-08-07T10:05:02.302900Z

Thanks.. Will try to do it in free time

borkdude 2020-08-07T10:06:49.303100Z

:thumbsup:

Karol Wójcik 2020-08-09T09:33:51.307100Z

@borkdude Ok almost got it. The only issue is that I'm receiving unsupported binding form input, another warning:

(ns hooks.rum.defc
  (:require
   [clj-kondo.hooks-api :as api]))

(defn defcs
  [{:keys [node]}]
  (let [args (rest (:children node))
        component-name (first args)
        args (next args)
        [fn-args body] (loop [args* args
                              mixins []]
                         (if (seq args*)
                           (let [arg (first args*)]
                             (if (vector? (api/sexpr arg))
                               (let [fn-args (api/sexpr arg)
                                     fn-args-without-state (vec (rest fn-args))]
                                 [(with-meta (api/vector-node fn-args-without-state) (meta arg))
                                  (concat mixins (rest args*))])
                               (recur (rest args*)
                                      (conj mixins arg))))
                           [nil nil]))
        new-node (when fn-args
                   (with-meta (api/list-node [(api/token-node 'defn)
                                              component-name
                                              fn-args
                                              (api/list-node
                                               (list*
                                                (api/token-node 'let)
                                                (api/vector-node
                                                 [(api/token-node 'state) (api/token-node {})])
                                                body))])
                     (meta node)))]
    {:node new-node}))
demo.cljs
(ns demo
  (:require
   [rum.core :as rum]))

(rum/defcs SomeComponent <
  {:did-mount (fn [state] state)}
  [state input another]
  (let [x "Hello"]
    nil))

(rum/defc SomeComponent1 <
  {:did-mount (fn [state] state)}
  [input]
  input)

(SomeComponent "hello")
Any ideas how to fix it?"

borkdude 2020-08-09T09:37:02.307500Z

Can you print the sexpr using (println (api/sexpr new-node)) and show what it makes?

Karol Wójcik 2020-08-09T09:39:19.307800Z

(defn SomeComponent [input another] (let [state {}] < {:did-mount (fn [state] state)} (let [x Hello] nil)))

borkdude 2020-08-09T09:40:48.308Z

Can you post the raw output without editing it?

borkdude 2020-08-09T09:41:36.308200Z

Maybe use prn instead of println to preserve the string

Karol Wójcik 2020-08-09T09:43:31.308400Z

(defn SomeComponent [input another] (let [state {}] < {:did-mount (fn [state] state)} (let [x "Hello"] nil)))

borkdude 2020-08-09T09:44:24.308600Z

Btw, I did get the correct linting with your code

borkdude 2020-08-09T09:44:54.308800Z

borkdude 2020-08-09T09:45:19.309300Z

borkdude 2020-08-09T09:45:56.309800Z

Are you use your config.edn is edited properly?

borkdude 2020-08-09T09:46:12.310Z

I now have this:

{:hooks {:analyze-call {rum.core/defc hooks.rum.defc/defc
                        rum.core/defcs hooks.rum.defc/defcs}}
 :lint-as {;; rum.core/defcs rum.core/defc
           rum.core/defcc rum.core/defc}}

borkdude 2020-08-09T09:46:48.310200Z

So, all looking OK here

Karol Wójcik 2020-08-09T09:48:23.310400Z

Config:

{:hooks {:analyze-call {rum.core/defc hooks.rum.defc/defc
                        rum.core/defcs hooks.rum.defc/defcs}}
 :lint-as {rum.core/defcc rum.core/defc
           view.core/FOR clojure.core/for
           view.core/KEEP clojure.core/keep}
 :linters {:missing-else-branch {:level :off}}}

borkdude 2020-08-09T09:48:28.310600Z

I'm afk for a few hours now.

Karol Wójcik 2020-08-09T09:49:13.310800Z

Maybe wrong version of clj-kondo: clj-kondo v2020.07.29

borkdude 2020-08-09T09:49:50.311Z

that's the latest

borkdude 2020-08-09T09:50:06.311200Z

afk

Karol Wójcik 2020-08-09T09:51:02.311400Z

Ok 🙂 I'm getting that errors only if I run clj-kondo --lint demo.cljs

borkdude 2020-08-09T09:51:28.311600Z

Then maybe in that case it doesn't pick up on your config

borkdude 2020-08-09T10:17:12.312700Z

You should run it from the directory that has .clj-kondo in it

Karol Wójcik 2020-08-09T10:18:21.312900Z

I'm doing it 🙂

borkdude 2020-08-09T14:53:04.313100Z

I'm back now. So, I'm getting good results with your code. What I've done is add it in libraries/rum/.clj-kondo/hooks/rum/defc.clj and added the entry in config.edn. Then I made the screenshots

Karol Wójcik 2020-08-09T16:02:39.313300Z

@borkdude can you please use command clj-kondo --lint demo.cljs and check whether no errors are reported?

borkdude 2020-08-09T16:11:36.313500Z

Now you're sounding like me, I say this all the time to people. Yes, I'll try it after dinner :)

👍 1
❤️ 1
Karol Wójcik 2020-08-09T16:44:17.313700Z

haha 😄 Thank you very much! I'm just wrapping my head around why in flycheck buffer those errors are not visible whereas in terminal I can spot them. I think those are internal errors of clj-kondo.

robert-stuttaford 2020-08-09T16:54:26.314200Z

all i have to add at this point is: 👏👏👏👏👏👏👏👏👏👏

borkdude 2020-08-09T17:14:02.314400Z

@karol.wojcik Works correctly here: src/example.cljs:25:9: warning: unused binding x src/example.cljs:28:1: error: demo/SomeComponent is called with 1 arg but expects 2

borkdude 2020-08-09T17:14:59.314600Z

When I put that code in src/example.cljs that is

borkdude 2020-08-09T17:15:09.314800Z

When linted separately, I do see different errors... hmm

borkdude 2020-08-09T17:15:40.315Z

Ah I see:

src/example.cljs::: error: unsupported binding form x
src/example.cljs::: error: unsupported binding form input
src/example.cljs::: error: unsupported binding form another

borkdude 2020-08-09T17:15:53.315200Z

I'll start debugging, brb

borkdude 2020-08-09T17:45:12.315400Z

OK fixed it. The mistake that was in your hook code is that you inserted sexprs back into the node instead of proper nodes. I took the liberty to just start from scratch and adapt the existing defc one, in this commit: https://github.com/borkdude/clj-kondo/commit/9f004622d0c69a6baf93bb63243306a7098f0941

❤️ 1
borkdude 2020-08-09T17:45:39.315600Z

If that code isn't sufficient yet, feel free to make another PR

borkdude 2020-08-09T17:47:40.315800Z

Also I renamed libraries to examples to fix the illusion that clj-kondo will pick up on those automatically, it won't, you have to copy the code yourself

borkdude 2020-08-09T17:51:53.316300Z

one other detail: when introducing a let, you can get warnings about a nested let. you can prevent this by using let*, this should probably go into the docs

borkdude 2020-08-09T17:53:08.316500Z

I'm pretty happy that this can be fixed in user-land code, so I don't have to publish a new release for each tweak :)

❤️ 1
Karol Wójcik 2020-08-09T17:58:25.316700Z

Thank you very much @borkdude !

borkdude 2020-08-06T17:03:14.295200Z

@karol.wojcik Note: I edited the link, posted an old one earlier

Karol Wójcik 2020-08-06T17:27:07.295300Z

Thank you very much! Why there is :node in keys instead of node?

Karol Wójcik 2020-08-06T17:31:48.295500Z

@borkdude Btw I'm trying to find the exact source of clj-kondo.hooks-api. This is what I got so far:

(defn defcs
  [{:keys [node]}]
  (let [args (rest (:children node))
        component-name (first args)
        args (next args)
        body
        (loop [args* args
               mixins []]
          (if (seq args*)
            (let [a (first args*)]
              (if (vector? (api/sexpr a))
                (do
                  (println a)
                  (println (api/vector-node (vec (rest (api/sexpr a)))))
                  (cons (api/vector-node (vec (rest (api/sexpr a)))) (concat mixins (rest args*))))
                (recur (rest args*)
                       (conj mixins a))))
            args))
        new-node (with-meta
                   (api/list-node (list* (api/token-node 'defn) component-name body))
                   (meta node))]
    ;; (println (api/token-node 'defn))
    ;; (println new-node)
    {:node new-node}))
But this throws unsupported binding form input

borkdude 2020-08-06T18:04:03.296300Z

:node or node are both supported, doesn't matter

👍 1
Karol Wójcik 2020-08-06T20:11:56.296600Z

@borkdude I think that I should ignore state somehow, but dunno how to do it. Can you please give me a hint? For instance simply ignoring the first argument results in input not being known.

borkdude 2020-08-06T20:12:42.296800Z

Yes. You should probably wrap the body in an artificial let expression that has a binding state, like (let [state nil] ... body ...)

borkdude 2020-08-06T20:13:00.297Z

Take a look at the hook for slingshot, which also does something similar

borkdude 2020-08-06T20:14:04.297200Z

@martinklepsch @robert-stuttaford Any ideas?