clj-kondo

https://github.com/clj-kondo/clj-kondo
ep 2020-07-11T10:00:44.159100Z

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?

borkdude 2020-07-11T11:24:45.160Z

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

borkdude 2020-07-11T11:27:39.160500Z

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

ep 2020-07-11T11:28:54.161600Z

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

borkdude 2020-07-11T11:31:02.162700Z

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

borkdude 2020-07-11T11:31:31.163100Z

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

borkdude 2020-07-11T11:32:17.163600Z

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

borkdude 2020-07-11T11:35:57.164200Z

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

ep 2020-07-11T11:36:57.164500Z

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

borkdude 2020-07-11T11:38:36.165200Z

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

ep 2020-07-11T11:39:51.165700Z

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!

borkdude 2020-07-11T11:53:45.166600Z

@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)

ep 2020-07-11T11:55:24.166900Z

thanks. appreciate the help

lread 2020-07-11T12:07:19.167800Z

Nice!

ep 2020-07-11T17:00:56.168800Z

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

borkdude 2020-07-11T17:05:23.169Z

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

ep 2020-07-11T17:05:41.169300Z

what if meta is optional?

borkdude 2020-07-11T17:06:00.169500Z

then lint accordingly?

borkdude 2020-07-11T17:09:01.169700Z

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?

ep 2020-07-11T17:09:24.169900Z

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?

borkdude 2020-07-11T17:10:05.170100Z

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

ep 2020-07-11T17:16:47.170300Z

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

borkdude 2020-07-11T17:17:48.170500Z

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

ep 2020-07-11T17:23:21.170700Z

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

borkdude 2020-07-11T17:26:13.171Z

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.

borkdude 2020-07-11T17:27:03.171200Z

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.

borkdude 2020-07-11T17:29:42.171400Z

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

ep 2020-07-11T17:31:10.171600Z

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

borkdude 2020-07-11T17:31:55.171800Z

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

lread 2020-07-11T17:37:29.172Z

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

ep 2020-07-11T17:49:49.172200Z

yeah i get the thought. thanks for explaining

serioga 2020-07-11T19:57:01.172400Z

You can put meta after foo but without ^