Quick question, does clj-kondo give warnings about unused public functions?
@cnly No. But there is a tool for this based on clj-kondo: https://github.com/borkdude/carve
:thumbsup: Thanks!
@borkdude Is there a tutorial or docs how to test & write custom hooks? I would love to tweak rum.core/defcs in free time
@karol.wojcik This is the most recent one: https://github.com/borkdude/clj-kondo/blob/master/doc/config.md#hooks
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}))
Thank you for help @borkdude you are golden!
Cool! What is the partial state for?
It's for making sure that state is provided
I think this generates something like:
(let [state {}] (def my-component (partial (fn [...] ...) state)))
Why not:
(defn my-component [args] (let [state {}] body)
?Is there a difference?
Syntactically yes, clj-kondo can see the argument count this way for example?
So in partial it's not seen?
You might want to do:
(defn my-component [args] (let [state {}] state ... body ...)
to make state being used, so it won't be reported.No
Hmm....
Frankly I want the state to be reported
ah
that's ok then, don't use it
Ahh wait.
so if you don't use state, then you're going to use def-something-else in rum right?
So I can just ignore the first arg?
why would you want to ignore the first arg?
I think that we are talking past each other 😄
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
@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?@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
(defsc foo [state x] body1 body2)
=> (defn foo [x] (let [state {}] body1 body2)
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.
and (f 1 2)
should trigger an arity warning
Thanks.. Will try to do it in free time
:thumbsup:
@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?"Can you print the sexpr using (println (api/sexpr new-node))
and show what it makes?
(defn SomeComponent [input another] (let [state {}] < {:did-mount (fn [state] state)} (let [x Hello] nil)))
Can you post the raw output without editing it?
Maybe use prn
instead of println
to preserve the string
(defn SomeComponent [input another] (let [state {}] < {:did-mount (fn [state] state)} (let [x "Hello"] nil)))
Btw, I did get the correct linting with your code
Are you use your config.edn is edited properly?
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}}
So, all looking OK here
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}}}
I'm afk for a few hours now.
Maybe wrong version of clj-kondo: clj-kondo v2020.07.29
that's the latest
afk
Ok 🙂 I'm getting that errors only if I run clj-kondo --lint demo.cljs
Then maybe in that case it doesn't pick up on your config
You should run it from the directory that has .clj-kondo in it
I'm doing it 🙂
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
@borkdude can you please use command clj-kondo --lint demo.cljs and check whether no errors are reported?
Now you're sounding like me, I say this all the time to people. Yes, I'll try it after dinner :)
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.
all i have to add at this point is: 👏👏👏👏👏👏👏👏👏👏
@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
When I put that code in src/example.cljs that is
When linted separately, I do see different errors... hmm
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
I'll start debugging, brb
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
If that code isn't sufficient yet, feel free to make another PR
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
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
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 :)
Thank you very much @borkdude !
@karol.wojcik Note: I edited the link, posted an old one earlier
Thank you very much! Why there is :node in keys instead of node?
@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
:node
or node
are both supported, doesn't matter
@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.
Yes. You should probably wrap the body in an artificial let expression that has a binding state
, like (let [state nil] ... body ...)
Take a look at the hook for slingshot, which also does something similar
@martinklepsch @robert-stuttaford Any ideas?