This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-02-17
Channels
- # announcements (11)
- # babashka (29)
- # beginners (45)
- # biff (3)
- # cider (5)
- # clj-kondo (55)
- # clojure-austin (2)
- # clojure-europe (6)
- # clojure-norway (24)
- # clojure-uk (1)
- # datalevin (28)
- # fulcro (8)
- # gratitude (1)
- # hyperfiddle (7)
- # keechma (1)
- # membrane (31)
- # other-languages (1)
- # polylith (22)
- # shadow-cljs (12)
I want to lint #C06JZ4X334N defcomponent
, which is very similar to defn
, with the addition that there can be component lifecycle declarations of the form :some-keyword (fn …)
between the name of the component and the argument list. Example from the README:
(defcomponent heading
:on-render (fn [dom-node val old-val])
[data]
[:h2 {:style {:background "#000"}} (:text data)])
I have no idea where to start. 😃it would seem more natural to me if dumdom had written the macro like this:
(defcomponent heading
{:on-render (fn ...)}
[data]
)
using metadata notationI’m currently using a macro that someone wrote that “fixes” this similar to what you suggest:
(ns dumbom.dumdom.macros
(:require [dumdom.core :as dumdom]))
(defn- extract-docstr
[[docstr? & forms]]
(if (string? docstr?)
[docstr? forms]
["" (cons docstr? forms)]))
(defn- extract-opts
([[argvec ?opts & forms]]
(assert (vector? argvec))
(if (map? ?opts)
[?opts (cons argvec forms)]
[{} (list* argvec ?opts forms)])))
(defmacro defcomponent
"A workaround to make default linting resolve `defcomponent` without complaints.
Instead of this:
(defcomponent Widget
\"A Widget\"
:on-mount #(...)
:on-render #(...)
[value constant-value]
(some-child-components))
We can write this:
(defcomponent Widget
[value constant-value]
{:name \"A Widget\"
:on-mount #(...)
:on-render #(...)}
(some-child-components))"
[name & forms]
(let [[docstr forms] (extract-docstr forms)
[options forms] (extract-opts forms)
[argvec & body] forms
options (merge {:name (str (:name (:ns &env)) "/" name)} options)]
`(def ~name ~docstr (dumdom.core/component (fn ~argvec ~@body) ~options))))
And using that instead. I’d like to not have to do this.Well, it's not up to me how people write their macros but the closer you stay to existing syntax, the easier it is to get working with clj-kondo. Maybe you could bring that advice to the library author. Above I gave one solution (def-catch-all). Another is to write a hook.
The catch-all gets rid of the warnings, but it doesn’t “recognize” local bindings, afaict.
that's right, it just tries to do the best it can: see that the name defines a var and ignore all the rest in the body
docs: https://github.com/clj-kondo/clj-kondo/blob/master/doc/hooks.md example configs: https://github.com/clj-kondo/clj-kondo/discussions/1528
Maybe dumdom could support the alternative variant as well, I think that should be doable

if you support it so that people can write: {:lint-as {dumdom/defcomponent clojure.core/defn}}
that would be nice
If you are attending the Conj this year, I will attend any workshop you host, @U04V15CAJ.
Though having my kids glued to when I slayed the Ender Dragon with Clojure was totally awesome, so no regrets here!
So, for this form:
(defcomponent heading
:on-render (fn [dom-node val old-val])
[data]
[:h2 {:style {:background "#000"}} (:text data)])
If I print it at the start of my hook:
My hook returns:
{:node }
I was thinking that this would make kondo lint the defn
form. But maybe I am wrong about that? Interestingly, if I add something between the dumdom options and the argument vector:
(defcomponent heading
:on-render (fn [dom-node val old-val])
'foo
[data]
[:h2 {:style {:background "#000"}} (:text data)])
Then data
is treated as a local binding, but the args in the lambda are not.The hook:
(ns hooks.dumdom
(:require [clj-kondo.hooks-api :as api]))
(defn- extract-docstr
[[docstr? & forms :as maybe-forms]]
(if (api/string-node? docstr?)
[(api/sexpr docstr?) forms]
["no docs" maybe-forms]))
(defn- extract-opts
([forms]
(extract-opts forms {}))
([[k v & forms :as maybe-forms] opts]
(if (api/keyword-node? k)
(extract-opts forms (assoc opts (api/sexpr k) (api/sexpr v)))
[opts maybe-forms])))
(defn defcomponent [{:keys [node]}]
(println "defcomponent\n node:" node)
(let [[name & forms] (rest (:children node))
[docstr forms] (extract-docstr forms)
[options forms] (extract-opts forms)
[argvec & body] forms
new-node (api/list-node
(list*
(api/token-node 'defn)
name
docstr
options
(api/sexpr argvec)
body))]
{:node new-node}))
Try running clj-kondo with the --debug
flag on the command line, it might output some helpful info
clj-kondo --lint - --debug << 'EOF'
(ns dumbom.home
(:require [dumdom.core :refer [defcomponent]]))
(defn heading "no docs" {:on-render (fn [dom-node val old-val])} [data] [:h2 {:style {:background "#000 "}} (:text data)])
(defcomponent heading
:on-render (fn [dom-node val old-val] foo)
[data]
[:h2 {:style {:background "#000"}} (:text data)])
EOF
[clj-kondo] Auto-loading config path: rewrite-clj/rewrite-clj
[clj-kondo] Auto-loading config path: http-kit/http-kit
[clj-kondo] Auto-loading config path: babashka/fs
[clj-kondo] Auto-loading config path: funcool/promesa
[clj-kondo] Linting file: <stdin>
defcomponent
node: <list: (defcomponent heading :on-render (fn [dom-node val old-val] foo) [data] [:h2 {:style {:background "#000"}} (:text data)])>
<stdin>:4:42: warning: unused binding dom-node
<stdin>:4:51: warning: unused binding val
<stdin>:4:55: warning: unused binding old-val
<stdin>:6:1: error: Not a node: no docs
<stdin>:7:19: warning: unused binding dom-node
<stdin>:7:28: warning: unused binding val
<stdin>:7:32: warning: unused binding old-val
<stdin>:7:41: error: Unresolved symbol: foo
<stdin>:8:4: error: Unresolved symbol: data
linting took 61ms, errors: 3, warnings: 6
Not a node: no docs
looks like a clue…
new-node
is not being printed for some reason. It’s printed when I run the hook in the repl…this is probably because Not a node: no docs is something you should first solve. And in the REPL you don't run with --debug
In extract-docstr
you return either a vector of nodes, but the second thing is a vector of a string and a node
Got it working. Please let me know if you see anything stupid being done in the hook code. https://gist.github.com/PEZ/1ac56f00b8e9e622dca2e78c66d4922a
@U9MKYDN4Q, Dumdom could provide this config automatically. Want a PR?
@U04V15CAJ, the auto-config gets imported to the library project (when using Calva+clojure-lsp). Do you think I should add the auto-config to .gitignore? It risks getting a bit confusing with the duplicates…
you can unduplicate this by removing the files in .clj-kondo/cjohansen and then adding this to .clj-kondo/config.edn
:
:config-paths ["../resources/...."]
See the explanation here: https://github.com/clj-kondo/clj-kondo/blob/master/doc/config.md#exporting-and-importing-configuration
To activate the exported configuration in your local project, you can add the following to .clj-kondo/config.edn:
{:config-paths ["../resources/clj-kondo.exports/<your-org>/<your-libname>"]}
Yeah, I read that before, but thought it was if I needed the config for the library (which doesn’t seem necessary). But it makes sense that it will dedup things too. 😃
I guess my real question was: will this cause changes in the PR @U0ETXRFEWor is it ready to merge?
Not getting it to stop downloading the auto-config, though… I copied the path using VS Code to make sure I get it right. :thinking_face:
yes, it will always import the config. that doesn't make sense for this lib so in this lib you can add .clj-kondo/cjohansen/dumdom
to .gitignore I guess