Fork me on GitHub
#clj-kondo
<
2020-07-11
>
ep10:07:44

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?

borkdude11:07:45

@epransky The metadata node is accessible by fetching :meta on the node

borkdude11:07:39

I've deviated from vanilla rewrite-clj here to make parsing easier and not check for metadata node everywhere

ep11:07:54

thanks. if i understand correctly i should expect (:meta (:node arg)) to return {:on-reload :noop} ?

borkdude11:07:02

@epransky In the form (defn ^{:cool true} foo []) the metadata node for {:cool true} is part of the node for the symbol foo

borkdude11:07:31

just like in normal clojure where you expect the metadata to be attached

borkdude11:07:17

however, your defstate example looks a bit weird, keywords can't have metadata in Clojure

borkdude11:07:57

But maybe that's what you are trying to lint. The metadata should probably go onto the symbol

ep11:07:57

yes you are right. mistake by me. double-checking

borkdude11:07:36

Try: (doseq [c (:children (:node arg))] (prn c '-> (:meta c))) or something similar

ep11:07:51

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!

serioga19:07:01

You can put meta after foo but without ^

ep11:07:24

thanks. appreciate the help

ep17:07:56

@U04V15CAJ 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?

borkdude17:07:23

well, you can check for :meta on the symbol node and if it's not there, emit a warning?

ep17:07:41

what if meta is optional?

borkdude17:07:00

then lint accordingly?

borkdude17:07:01

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?

ep17:07:24

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?

borkdude17:07:05

yeah, so you can check for :meta on the keyword and then emit a warning right?

ep17:07:47

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

borkdude17:07:48

what do you mean with "instead of just parsing the meta as a node of its own"? I'm not sure if I understand

ep17:07:21

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

borkdude17:07:13

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.

borkdude17:07:03

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.

borkdude17:07:42

So that's the reason it is like it is now and that's unlikely going to change

ep17:07:10

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

borkdude17:07:55

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

lread17:07:29

ya, I do like clj-kondo’s rewrite-clj metadata node scheme better than rewrite-clj’s.

ep17:07:49

yeah i get the thought. thanks for explaining