i've got a question about the new hooks API. i'm trying to write a hook for defstate
macro from https://github.com/tolitius/mount lib. this macro supports adding certain https://github.com/tolitius/mount#on-reload. i would like to lint this metadata node as well, but i cant figure out how to access it through the hooks API. it seems not be parsed into rewrite-clj node. example:
(ns example
(:require [mount.core :as mount]))
(mount/defstate
foo
^{:on-reload :noop}
:start (do (prn "start") {:bar "buzz"}))
and in hooks ns:
(ns hooks.defstate
(:require [clj-kondo.hooks-api :as api]))
(defn defstate [arg]
(prn arg))
clj-kondo lint produces:
{:node <list: (mount/defstatefoo:start(do(prn"start"){:bar"buzz"}))>}
is this intended behavior? is it possible to access {:on-reload :noop}
map?@epransky The metadata node is accessible by fetching :meta
on the node
I've deviated from vanilla rewrite-clj here to make parsing easier and not check for metadata node everywhere
thanks. if i understand correctly i should expect (:meta (:node arg))
to return {:on-reload :noop}
?
@epransky In the form (defn ^{:cool true} foo [])
the metadata node for {:cool true}
is part of the node for the symbol foo
just like in normal clojure where you expect the metadata to be attached
however, your defstate example looks a bit weird, keywords can't have metadata in Clojure
But maybe that's what you are trying to lint. The metadata should probably go onto the symbol
yes you are right. mistake by me. double-checking
Try: (doseq [c (:children (:node arg))] (prn c '-> (:meta c)))
or something similar
ah, found it. after fixing the mistake to:
(mount/defstate
^{:on-reload :noop}
foo
:start (do (prn "start") {:bar "buzz"}))
(:meta (second (:children node)))
thank you!@epransky Added some docs for it here now: https://github.com/borkdude/clj-kondo/blob/master/doc/config.md#clojure-code-as-rewrite-clj-nodes (cc @lee)
thanks. appreciate the help
Nice!
@borkdude just to follow up on this, i thought about it and IMO we should be able to lint the metadata forms. like what if im trying to warn a user that the metadata declaration is not in the correct position, such as the mistake i initially made? i cant see a way for me to achieve this functionality if i need to rely on correct placement of the form. wdyt?
well, you can check for :meta
on the symbol node and if it's not there, emit a warning?
what if meta is optional?
then lint accordingly?
is your question: what logic should I use for my problem, or is it a matter of clj-kondo not enabling you somehow to do it?
but the issue is that there is a form being passed which should not be there and the user might think they've configured some metadata when they havent. it seems that this is what linting is for. i actually think my mistake above illustrates perfectly:
(mount/defstate
foo
^{:on-reload :noop}
:start (do (prn "start") {:bar "buzz"}))
the metadata here is an optional functionality of the defstate
API. i accidentally put it after the symbol instead of before. shouldnt linting help me out here to notify me that i cant put metadata on a keyword, as you pointed out?yeah, so you can check for :meta
on the keyword and then emit a warning right?
yeah. its possible. just seems a bit roundabout instead of just parsing the meta as a node of its own, but i guess its workable
what do you mean with "instead of just parsing the meta as a node of its own"? I'm not sure if I understand
im not sure what im thinking about is possible or logical, but when i started using the hooks API, i expected to the meta form to be a node in the children
list of the macro. instead, what's necessary is to check each child node to see if it has meta and extract it. just seems like a lot of extra work than if the meta was first class in this situation
Ah that. Well, what rewrite-clj has by default is even more annoying: if you have ^:foo []
then the structure is the metadata node is the parent of the vector node. It's now structured this way because if it wasn't you'd constantly be checking if a node is metadata and if so, go down into the children for the thing you're actually looking for.
e.g. when clj-kondo analysis (defn foo [])
you expect the second thing to be a symbol, but when users write (^:foo defn ^:bar foo [])
then clj-kondo can still expect the name of the function to be the second child.
So that's the reason it is like it is now and that's unlikely going to change
i see. i agree clj-kondo
is better. but i guess my issue is just that intuitively i see a certain number of forms in a list and i expect the children of the parent node to have the same number of forms. but i get the thought and as long as its documented i think ppl will learn to work with it
the way clj-kondo structures it also matches how it works in clojure itself: the second child is the symbol and if you want metadata from that, you call meta
on it
ya, I do like clj-kondo’s rewrite-clj metadata node scheme better than rewrite-clj’s.
yeah i get the thought. thanks for explaining
You can put meta after foo
but without ^