Fork me on GitHub
Stefan T22:01:57

I know there are many existing topic related to custom macros, but I’m having a hard time finding a solution to this specific problem. As we know, the defrecord macro generates some constructor functions like -> and map->. Suppose I have a custom macro like defrecord+ which generates custom constructor functions. What are my options for configuring clj-kondo to handle this? Ideally I’d like to avoid having to add a configuration entry for every custom record definition created through my defrecord+.

(defrecord+ MyRecord [id data])

; unresolved var
(make->MyRecord {:id 1 :data "something"})



{:lint-as {your.ns/defrecord+ clojure.core/defrecord}}

Stefan T22:01:52

In this example, is your.ns the namespace that defines the defrecord+ macro or the namespace that is using it?

Stefan T23:01:52

I currently have this in my configuration under :lint-as as suggested, but that doesn’t seem to work.

Stefan T23:01:52

(ns my-ns
  (:require [my-custom-record :refer [defrecord+]]))

; No lint issues here
(defrecord+ MyRecord [id data])

(ns my-other-ns
  (:require [my-ns :as mns]))

; unresolved var
(mns/make->MyRecord {:id id :data data})


Can you show your entire config? Make sure you put :lint-ason the top level

Stefan T23:01:40

Yeah just to be clear, I’m not getting lint issues on the usage of defrecord+ its from the custom function it generates.


make->MyRecordis not defined by defrecord, that is map->MyRecord

Stefan T23:01:12

That’s what I’m saying, defrecord+ defines non-standard constructor functions


ah ok. your option here is to write a custom config macro or hook. See


Something which just expands into

(do (declare make->MyRecord) (defrecord MyRecord [] ...)
or so will do

Stefan T23:01:20

Ah ok, so I can set the :analyze-call for the defrecord+ macro, and I won’t need to configure :analyze-call for each of the make->X functions?

Stefan T23:01:55

Excellent. That clears things up. Thanks!


there is :analyze-call but also :macroexpandwhich will probably be easier to start with, you can just provide a normal macro there, but it gives less precise warnings / locations

Stefan T23:01:47

Gotcha, I’ll give it a try.

Stefan T23:01:32

This question might be specific to clojure lsp or vscode, but can you recommend a good workflow for developing and testing these hooks?


Currently, use clj-kondo on the command line and insert printlns in the code for debugging, and then run it on examples

Stefan T23:02:54

Yeah I had to smack myself when I realized I was working the really really hard way 🙂 using the command line was the way to go here! Hooks are working quite well for me now, thank you!

Stefan T23:02:04

Though I ran into this issue that puzzled me. I thought it would be a no brainer to use :lint-as but that was not working, I had to use a custom hook which seems like it would do the same thing. Any idea here?

(defmacro with-tmp-file* [[sym f] & body] ...)

;; Usage like:
(io-utils/with-tmp-file* [tmp-file "asset"] ...)
Tried adding :lint-as
{:lint-as {* cojure.core/let}}
Did not work. The hook works though:
(defn with-tmp-file* [{:keys [node]}]
  (let [new-node (api/list-node
                   (list* (api/token-node 'let)
                          (rest (:children node))))]
    {:node new-node}))


@UQE9SS4DP a typo: cojure.core/let


should be clojure.core/let

Stefan T18:02:20

😢 I need to catch up on sleep

Stefan T18:02:49

:rolling_on_the_floor_laughing: thanks