This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-03-07
Channels
- # announcements (20)
- # babashka (25)
- # beginners (48)
- # biff (26)
- # calva (5)
- # cider (3)
- # clara (7)
- # clerk (7)
- # clj-kondo (61)
- # cljdoc (3)
- # clojure (6)
- # clojure-austin (1)
- # clojure-conj (8)
- # clojure-europe (58)
- # clojure-nl (1)
- # clojure-norway (4)
- # clojure-poland (1)
- # clojure-uk (9)
- # cursive (2)
- # emacs (11)
- # fulcro (8)
- # graphql (14)
- # gratitude (6)
- # humbleui (10)
- # hyperfiddle (17)
- # integrant (15)
- # introduce-yourself (1)
- # leiningen (5)
- # malli (13)
- # meander (21)
- # nbb (11)
- # off-topic (15)
- # pedestal (15)
- # polylith (15)
- # quil (28)
- # rdf (2)
- # reitit (3)
- # releases (6)
- # sci (21)
- # shadow-cljs (38)
- # spacemacs (3)
- # xtdb (6)
Folks, since last week I have had issues with clojure-lsp not working correctly. Reading through the #lsp channel, I found a thread that described my issue (`clj-kondo.lint-as/def-catch-all`). The https://clojurians.slack.com/archives/CPABC1H61/p1677599963893079?thread_ts=1677515210.671249&cid=CPABC1H61 was for the user to fix the clj-kondo lint config. Reading through the clj-kondo docs, I think the macroexpand
https://github.com/clj-kondo/clj-kondo/blob/master/doc/hooks.md#macroexpand is what I need to use.
My macro in app code
(ns app.test-utils
(:require [clojure.core.async :as async] ;; the alias is used by other non macro code in this ns
[app.effects-dispatcher :as effects-dispatcher]
)
(defmacro with-fx-ch
[[nom] & body]
`(let [~nom (clojure.core.async/chan 100)]
(with-redefs [app.effects-dispatcher/dispatch
(fn [[k# v#]] (clojure.core.async/put! ~nom {k# v#}))]
~@body)))
The same macro pasted into config at the path ./clj-kondo/app/test_utils.clj
(ns app.test-utils)
(defmacro with-fx-ch
[[nom] & body]
`(let [~nom (clojure.core.async/chan 100)]
(with-redefs [app.effects-dispatcher/dispatch
(fn [[k# v#]] (clojure.core.async/put! ~nom {k# v#}))]
~@body)))
And the hook in config.edn
{:hooks {:macroexpand {app.test-utils/with-fx-ch app.test-utils/with-fx-ch}}}
This fixes the error with clojure-lsp. However, now clk-kondo is showing the warning "Unresolved namespace clojure.core.async. Are you missing a require?"
. Why is it complaining about this?It's complaining about this because clj-kondo doesn't see a require of the namespace clojure.core.async
before the expansion of this macro.
You can fix this by providing another bit of config:
{:config-in-call {app.test-utils/with-fx-ch {:linters {:unresolved-namespace {:exclude [clojure.core.async]}}}}
Is it unable to see the require in the config macro or the prod macro? the prod macro ns has the require, though the macro itself is using fully qualified fns.
It would have been easier if your macro followed a syntax of an existing macro, so you can use :lint-as
.
Can you give an example of how this macro is called in practise? It might be similar to cljs.test/async
If you have a namespace like this:
(ns foo (:require [app.test-utils]))
(app.test-utils/with-fx-ch ...)
it will expand into:
(ns foo (:require [app.test-utils]))
(let [.. (clojure.core.async/chan 100)])
and in that namespace, clojure.core.async
hasn't been required yet, so clj-kondo complains about it.
another way to suppress this from the macro expansion side is:
(vary-meta ... assoc :clj-kondo/ignore [:unresolved-namespace])
Sample usage
(testing "some side effecty code"
(test-utils/with-fx-ch [effects]
(testing "scenario X"
(execute-the-subject)
(email-sent? effects))))
Seems to work fine:
(ns app.test-utils
{:clj-kondo/config '{:lint-as {app.test-utils/with-fx-ch clojure.core/fn}}}
(:require [clojure.test :refer [testing]]))
(defmacro with-fx-ch
[[nom] & body]
`(let [~nom (clojure.core.async/chan 100)]
(with-redefs [app.effects-dispatcher/dispatch
(fn [[k# v#]] (clojure.core.async/put! ~nom {k# v#}))]
~@body)))
(testing "some side effecty code"
(with-fx-ch [effects]
(testing "scenario X"
(prn effects))))
Yeah. I tested it locally and it works!
Can I configure a macro that is like defn but has an optional arg in front of the symbol?
defn-foo {:foo :bar} handler
defn-foo handler
both result in hander being defined. Right now I have :lint-as clojure.core/defn but I think it doesn't handle the second form
problems with the first form I mean. When I have more args in front.
(defn-handler {:permissions :read-application} retrieve-foo
[{{:keys [application-id]} :path-params :as req}
{:foo :foo}])
retrieve-foo does not end up being counted as symbolyeah, that's not syntax that defn has so it won't work. why not use var metadata instead of a custom macro?
or you can support the map after the name in your custom macro, then it would work since that's where normally the metadata goes
its a good lesson, if you make a defn macro, add extra args to where the body usually goes I guess. In the spot that :pre
etc goes I suppose
the other option, other than writing a hook is to use {:lint-as {your.ns/your-macro clj-kondo.lint-as/def-catch-all}
Not at the moment, just reading up on the feature itself and saw that the examples use .clj
as the file extension for the expansion files
are macroexpansion definitions distributed the same way with libs as other config/hooks?
I'm having trouble with :clojure-lsp/unused-public-var
with an admittedly rather complicated, multi-level def
macro. there's two issues with the warnings: first, it can only track usage in Clojure code, and some of these are ^:export
ed and called from JS code. second, this macro generates 3 public, ^:export
ed defs for a list of inputs, and it's not an error to only use a subset of what it generates (even if all the uses were in Clojure and it could find them).
I had thought configuring it with :linters {:clojure-lsp/unused-public-var {:exclude-when-defined-by ...}}
would work, but it doesn't seem to.
the relevant macros are here https://github.com/metabase/metabase/blob/master/src/metabase/domain_entities/malli.clj#L88 . see the usage examples in the docstrings, it takes a list of symbol [:path :in :map]
pairs and generates a getter, setter, and some JS<->CLJS conversion helpers for each.
I've tried putting the outer macro and all the inner macros in the :exclude-when-defined-by
without success.
there's a :macroexpand
kondo rule for this as well, replacing it with three dummy defn
s for each row of input, if that changes anything.
can I tweak the macroexpand code, or use a hook instead, and suppress this warning on the output somehow?
@UCY0GT0QM The clojure-lsp/* linters are implemented by #lsp - it might help asking questions over there
oh I suppose those :exclude-when-defined-by
etc. options are a clojure-lsp custom thing. I'll poke around in its implementation.
@UCY0GT0QM https://clojure-lsp.io/settings/#clojure-lspunused-public-varare the docs detailing all possible settings for that linter, but I'm wondering if we should exclude vars defined with ^:export
meta, WDYT?
I think that makes sense. ^:export
is indicating that it's used out of scope, so even aside from the macro I wouldn't want an unused-public-var warning on (defn ^:export some-fn-called-from-js [] ...)
the issue with defined-by
for macros is that relies on user defining :defined-by
in clj-kondo hooks like https://github.com/nubank/state-flow/blob/master/resources/clj-kondo.exports/nubank/state-flow/nubank/state_flow.clj#L35 :/
ah, I see. so that sounds like my resolution is to use hooks instead of :macroexpand
and set that rule accordingly.
Yeah, makes sense, feel free to create a issue where we can discuss the implementation at clojure-lsp
well, that's separate from the ^:export
thing. actually now that I think about it, that already works. there's a separate ^:export
ed function here with no warning about that. if I un-`^:export` it, I get an unused warning.
at nubank we have custom vars with a specific meta as well, so maybe we should allow user define metas to exclude, but if exports
is a cljs/clj thing/standard, clojure-lsp should support OOTB
shadow-cljs recently added a non-standard ^{:export-as "jsFunctionName"}
option too, that should probably have the same semantics as ^:export
(`^:export` is CLJS standard, not just shadow-cljs)
let me try setting :export true
on the dummy defns in my :macroexpand
okay, metadata after the args doesn't work (not sure why) but ~(vary-meta sym assoc :export true)
on the defn
s in the :macroexpand
does work.
I think the only resulting action here should be documenting that the :exclude-when-defined-by
only works if the :defined-by
is set in the hook for that macro. I imagine plenty of libraries with defn
-ish macros have that built in, but if you're building something of your own then it's weird that the option doesn't work.
agreed, TBH I didn't pay attention that :exclude-when-defined-by
could be used by macros, worth mentioning that
I can file an issue with clojure-lsp
's docs if you like.
I lied there's also the :export-as
thing.
https://github.com/clojure-lsp/clojure-lsp/issues/1511
https://github.com/clojure-lsp/clojure-lsp/issues/1512
thanks, I mentioned in the last one how it should be fixed, LMK if you wanna work on that
I haven't actually tripped over it yet, but probably will later this week or next week. I'll fix it when I do, or when I've got some idle time.