Fork me on GitHub
#clj-kondo
<
2023-03-07
>
amithgeorge12:03:57

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&amp;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?

borkdude12:03:52

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]}}}}

amithgeorge12:03:44

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.

borkdude12:03:12

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

borkdude12:03:13

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

borkdude12:03:27

and in that namespace, clojure.core.async hasn't been required yet, so clj-kondo complains about it.

borkdude12:03:07

another way to suppress this from the macro expansion side is:

(vary-meta ... assoc :clj-kondo/ignore [:unresolved-namespace])

borkdude12:03:16

but this will suppress all such problems within the macro usage

amithgeorge12:03:45

Sample usage

(testing "some side effecty code"
  (test-utils/with-fx-ch [effects]
    (testing "scenario X"
      (execute-the-subject)
      (email-sent? effects))))

borkdude12:03:45

I'd just add the config

borkdude12:03:32

it seems you could also use {:lint-as {your.ns/your-macro clojure.core/fn} perhaps

borkdude12:03:13

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

amithgeorge12:03:07

Yeah. I tested it locally and it works!

amithgeorge12:03:40

I wouldn't have seen that equivalence with fn . Thank you!

๐Ÿ‘ 2
Benjamin12:03:57

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

borkdude12:03:23

Can you give the complete form?

borkdude12:03:01

does it have problems with the first or the second?

Benjamin12:03:07

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 symbol

borkdude12:03:34

yeah, that's not syntax that defn has so it won't work. why not use var metadata instead of a custom macro?

borkdude12:03:11

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

borkdude12:03:17

(defn foo {:hello :there} [])

๐Ÿ˜… 2
Benjamin12:03:56

I guess we were to macro happy in the code base I am working rn

Benjamin12:03:19

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

borkdude12:03:38

the other option, other than writing a hook is to use {:lint-as {your.ns/your-macro clj-kondo.lint-as/def-catch-all}

imre14:03:21

Does the :macroexpand hook support the .clj_kondo extension?

borkdude14:03:34

it should yes

borkdude14:03:00

do you have a reason to believe it doesn't?

imre14:03:08

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

borkdude14:03:25

That's probably just from before

borkdude14:03:44

PR welcome to change it

imre14:03:46

are macroexpansion definitions distributed the same way with libs as other config/hooks?

imre14:03:31

> PR welcome to change it Will do once I confirm it works

Braden Shepherdson14:03:27

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 ^:exported and called from JS code. second, this macro generates 3 public, ^:exported 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 defns 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?

borkdude14:03:29

@UCY0GT0QM The clojure-lsp/* linters are implemented by #lsp - it might help asking questions over there

Braden Shepherdson14:03:31

oh I suppose those :exclude-when-defined-by etc. options are a clojure-lsp custom thing. I'll poke around in its implementation.

ericdallo14:03:54

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

Braden Shepherdson15:03:52

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 [] ...)

ericdallo15:03:13

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 :/

Braden Shepherdson15:03:06

ah, I see. so that sounds like my resolution is to use hooks instead of :macroexpand and set that rule accordingly.

ericdallo15:03:17

Yeah, makes sense, feel free to create a issue where we can discuss the implementation at clojure-lsp

ericdallo15:03:27

yes, as a workaround that may work

Braden Shepherdson15:03:54

well, that's separate from the ^:export thing. actually now that I think about it, that already works. there's a separate ^:exported function here with no warning about that. if I un-`^:export` it, I get an unused warning.

2
ericdallo15:03:13

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

Braden Shepherdson15:03:16

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)

ericdallo15:03:18

oh, indeed we already have support for export did one year ago

ericdallo15:03:18

yeah, probably should have more work to support export-as

Braden Shepherdson15:03:24

let me try setting :export true on the dummy defns in my :macroexpand

borkdude15:03:04

(defn foo []
  {:export true} ...)
might help

borkdude15:03:21

althought from the linked PR I don't see how metadata is picked up

Braden Shepherdson15:03:54

okay, metadata after the args doesn't work (not sure why) but ~(vary-meta sym assoc :export true) on the defns in the :macroexpand does work.

๐Ÿ‘ 2
Braden Shepherdson15:03:28

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.

ericdallo15:03:53

agreed, TBH I didn't pay attention that :exclude-when-defined-by could be used by macros, worth mentioning that

Braden Shepherdson15:03:33

I can file an issue with clojure-lsp's docs if you like.

ericdallo16:03:13

thanks, I mentioned in the last one how it should be fixed, LMK if you wanna work on that

Braden Shepherdson17:03:38

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.

๐Ÿ‘ 2