clj-kondo 2025-12-01

hello, I may have found an edge case here. let me know if this is a known issue. This come from my actual code, but I tried to simplify it into this made up example of loop -> if -> clojure.core/match -> recur. here, clj-kondo will lint the first recur form as error Recur can only be used in tail position. but if the recur form is in another form like do or let, it doesn't lint as error file.clj

(require '[clojure.core.match :refer [match]])

(loop [[target & remaining] [{:hey "a"} {:hi "b"} {:hello "c"} {:hi "d"}]]
  (if (> (count remaining) 0)
    (match [target]
      [{:hey _}]
      (recur remaining) ;; <-- lint error here

      [{:hi _}]
      (do
        (println "hi" target)
        (recur remaining))

      :else (println "end"))
    (println "end")))
clj-kondo
clj-kondo --lint .\file.clj
.\file.clj:7:7: error: Recur can only be used in tail position.
linting took 708ms, errors: 1, warnings: 0
if it is inside when or directly inside loop however, no lint-error is reported. lastly, this is a working clojure code, or at least this is working babashka code
bb .\file.clj
hi {:hi b}
end

seems like a bug. feel free to report an issue

šŸ‘šŸ¼ 1

you can add a #_:clj-kondo/ignore to silence the warning

Thank you, I will post the issue. this isn't a big issue since I don't write the recur with just one form like that. Just happen write my code halfway and noticed this inconsistency.

great, thanks for explaining the urgency :)

I don’t think reader conditionals should be marked as illegal just because it is not a .cljc extension on the file.

This file uses the extension .fiddle and is a first class citizen in Calva. 😃

that's how clojure/clojurescript work - you can disable that linter if you want

I don't even know why clj-kondo is activated for .fiddle files. Maybe this is something you control in Calva. In that case you can maybe also control that it should be linted as cljc

Calva is configured to treat fiddle files as Clojure-ish file. It gets all the service as any Clojure-ish files get, clojure-lsp, linting, etc. The Clojure repl doesn’t complain about the reader conditional. I don’t think the Clojure repl even knows what file extension the file has. It reads and evaluates. So does the ClojureScript repl.

; clj  hello-world.core 
#?(:cljs (js/console.log "cljc from js")
   :clj (.println System/out "cljc from jvm")
   :bb (.println System/out "cljc from bb"))
cljc from jvm

nil

; cljs  hello-world.core 
#?(:cljs (js/console.log "cljc from js")
   :clj (.println System/out "cljc from jvm")
   :bb (.println System/out "cljc from bb"))
cljc from js

nil

The REPL is accepting of reader conditionals, but when code is loaded from a file, it's not accepting

$ clj-kondo --lint /tmp/foo.fiddle
/tmp/foo.fiddle:1:1: error: Reader conditionals are only allowed in .cljc files
linting took 47ms, errors: 1, warnings: 0
$ clj-kondo --lint /tmp/foo.fiddle --lang cljc
linting took 13ms, errors: 0, warnings: 0

you can make clj-kondo believe this is a cljc file in that way.

Ah, thanks. You’re right, loading a file the repl complains. So then I am again happy that kondo has this warning. 😃

You are absolutely right!

> you can make clj-kondo believe this is a cljc file in that way Calva treats anything that isn’t explicitly targeted for a particular repl a bit like cljc. So that’s partly where I’m coming from. But if Clojure doesn’t want to play along, there’s not much Calva can do about it. I wonder what the reason could be to close that map….

depends on how you evaluate stuff. you can bind a dynamic var to allow reader conditionals. Again, don't know anything about Calva's integration with this

Good to know! Calva’s integration is super basic bare nrepl client. There’s nothing fancy going on at the Calva level at least. Enough moving parts in the stack below. 😃