clj-kondo

yuhan 2026-01-31T12:46:50.588399Z

Hey I've recently gotten into the habit of 'fiddle' files that sit right next to the source .clj files as REPL scratch pads, like

src/app/impl/webserver.clj
src/app/impl/webserver.fiddle
I believe this convention was popularized by Calva (which has a couple of built-in support features: https://calva.io/fiddle-files/) But as noted at the bottom of that page, it doesn't have great linter support, because clj-kondo can't pick up the var declarations and throws unresolved symbol / ns-alias errors all over the place. Eg. the contents of the fiddle file might look something like:
(in-ns 'app.impl.webserver)

(def $server (start! 8080))
(stop! $server)

; no need for @#'tricks to invoke private fns
(def >ctx (get-ctx @$server)) 

(validate (::auth/state >ctx)) ;=> true

(-> (slurp (io/resource "log.txt"))
  (str/split-lines)
  count)
;=> 123

;; random explorations, temporary defs, etc
Wondering if there could be a way for clj-kondo to better support such a workflow?

yuhan 2026-01-31T12:48:33.864619Z

I can sorta understand why the analyzer doesn't handle in-ns forms in general, (lack of determinism? and non-idiomatic), but maybe there could be a config option to treat some files specially based on their extensions? In effect treating them as if they were just transparently 'tacked on' to the end of the main file, like a sort of gigantic RCF

borkdude 2026-01-31T13:01:07.008379Z

Why not just create a fiddles/my_fiddle.clj file with (:require [namespace :refer :all])

yuhan 2026-01-31T13:14:54.266789Z

Mostly DX reasons, it's a much simpler mental model to have it be literally "in the same namespace" - eg. you have access to the same private vars, ns aliases etc, and there's zero friction to copy-pasting subexpressions back and forth

yuhan 2026-01-31T13:16:42.400999Z

I think of it as basically a persistent REPL session where you'd in-ns into the namespace and have all those things at hand, or extended RCF forms which I'd rather not clutter up the main file with

borkdude 2026-01-31T13:16:48.603989Z

Why don't you add a (comment) at the end of the namespace and make this your fiddle workspace

borkdude 2026-01-31T13:17:12.377269Z

that's basically what they are for

yuhan 2026-01-31T13:18:56.805519Z

again, mostly ease of use / friction reasons - it's nicer to work with top-level forms when they're actually top level, and I don't have to worry about syntax errors etc that might prevent the file from loading, and it's gitignored and excluded from uberjars etc by default

yuhan 2026-01-31T13:20:58.153829Z

I've used the RCF workflow as you described for years, and this has genuinely been an improvement for my particular style of dev (might not be the same for everyone)

yuhan 2026-01-31T13:25:21.170659Z

(here's how I described it to Claude)

**Properties:**
- Gitignored, not on classpath (due to `.fiddle` extension), excluded from uberjars.
- Shared namespace = zero friction for cut-paste, preserves requires, aliases, access to private vars etc. for free
- Need not load cleanly - syntax errors, incomplete forms are fine
- Top-level forms (unlike RCFs in main files)
	- Exception: wrap dangerous side effects in `(comment)` for safety

**Typical contents:**
- Exploratory snippets with `$` and `>` prefixed vars, used to distinguish temporary REPL bindings from production code
- Side-effectful orchestration commands
- Legacy/alternative implementations kept for reference
- Graduated test cases (before formalizing to unit tests)

**Evolution pattern:**
1. Start with RCFs in main file (informal sanity checks)
2. Move to fiddle when cluttered or tests get involved
3. Eventually formalize to `*-test` namespace with proper `deftest
`

borkdude 2026-01-31T13:25:47.611499Z

the difficulty is that clj-kondo works with a 1-to-1 mapping for namespaces and cache files. edit one namespace, the cache file overwrites. but perhaps we can do something like this:

(ns babashka.impl.repl-fiddle)

(in-ns 'babashka.impl.repl)
where we basically treat in-ns as a refer-all including private vars. However, that would not work if you used the in-ns ns as an extra "production" extension (like happens with clojure.pprint)

borkdude 2026-01-31T13:26:30.070879Z

I'm open to contributions to improve this

yuhan 2026-01-31T13:27:30.490919Z

yes that's not the intention here re. the clojure.pprint layout - I'd never want the fiddle contents to affect linting of the main file, it's should be a strictly one-way flow

borkdude 2026-01-31T13:28:41.513079Z

well, that is how it would work if we supported the clojure.pprint layout and this is actually how clojure itself works, so that would be the consequence of supporting this

borkdude 2026-01-31T13:29:17.018899Z

you could also stop using private vars and use .impl namespaces

borkdude 2026-01-31T13:29:20.922339Z

and then use refer all

yuhan 2026-01-31T13:32:20.356609Z

a bunch of these uses have also been familiarizing myself in legacy codebases where I don't have the ability to restructure the codebase

borkdude 2026-01-31T13:36:18.235459Z

two things so far. • There seems an ambiguity in supporting in-ns: you don't want to "add" definitions to the main namespace in clj-kondo's analysis when you are using it from elsewhere, but this is how in-ns works (see clojure.pprint). What people want is not always how Clojure works and clj-kondo tries to analyze in the way Clojure works. • If you extract tests from these experiments, then you might as well start with a fiddle "test" namespace instead. I'm open to contributions but "what we want" should be resolved before we even think about implementing anything

borkdude 2026-01-31T13:36:41.094039Z

Another thing: for other dialects like CLJS in-ns isn't even possible - how do you explore that code? your pattern doesn't work there

yuhan 2026-01-31T13:39:58.786029Z

Yeah, I had the feeling that supporting in-ns in general like how clojure.core / pprint is defined would be a much more complex undertaking, you'd have to analyze load forms to figure out what order to interleave the files etc., Which is why I thought of the workaround of using the '.fiddle' ext as a signal - the semantics could be something really dumb like 'take the contents of the main file and splice them at the start before analysis'? Not sure if that makes any sense with how the underlying impl works And yeah, I had the feeling that supporting in-ns like how clojure.core / pprint is defined would be a much more complex undertaking, you'd have to figure out the

yuhan 2026-01-31T13:43:59.361419Z

Probably something that won't be enabled in the default config, and you would specify the mapping yourself eg. {"*.fiddle" "$1.clj"} for flexibility - I'm not familiar enough with clojurescript to know what the semantics would need to involve there

yuhan 2026-01-31T13:46:22.477009Z

(just a rough sketch obviously, wrote a regex before remembering that they weren't valid edn - could also be inline configs or #_ discarded forms at the top of the file)

yuhan 2026-01-31T13:51:36.187879Z

Again not claiming that this is a universally applicable or good workflow, just one that I've found to work well for me, (and other folk? enough to warrant editor support in Calva)