Finding transitive var usages with clj-kondo

Today fellow Clojurian Søren Knudsen asked the following question on Slack:

Say I'd like an overview of which fns in my Clojurescript app don't have :x metadata and aren't children of functions that have :x. I'd love this overview as data.

Anyone know a relevant analysis tool for this purpose?

Let's represent this problem in code form. Read it from bottom to top.

(defn grandchild [] ; no :x, but reachable via child: ignore
  :leaf)

(defn child [] ; no :x, called by ^:x grandparent: ignore
  (grandchild))

(defn ^:x grandparent [] ;; has :x metadata, ignore
  (child))

(defn standalone [] ;; has no :x metadata and not reachable from anything with :x metadata, include
  :other)

It turns out that clj-kondo analysis data is well suited to solve this problem. In this blog post, let's write a babashka script, that uses the clj-kondo pod. This bit of setup lets you do that. Of course you could also use clj-kondo as a regular JVM dependency, but we're going for ease here, since it's just a tiny script at this point.

#!/usr/bin/env bb
(require '[babashka.pods :as pods]
         '[clojure.set :as set])

(pods/load-pod 'clj-kondo/clj-kondo "2025.06.05")
(require '[pod.borkdude.clj-kondo :as clj-kondo])

Clj-kondo lets you find var-definitions and var-usages. Clj-kondo can also include var metadata. The arguments to clj-kondo's run! API function then should look like this:

(def analysis
  (-> (clj-kondo/run! {:lint ["src"]
                       :config {:analysis {:var-definitions {:meta [:x]}
                                           :var-usages true}}})
      :analysis))

To illustrate how it works, we'll introduce a multi-namespace project:

;; src/app/core.cljs
(ns app.core
  (:require [app.util :as util]))

(defn ^:x grandparent []
  (util/child))

(defn standalone []
  :other)

;; a top level var usage, not inside any var definition:
(util/child)
;; src/app/util.cljs
(ns app.util)

(defn grandchild []
  :leaf)

(defn child []
  (grandchild))

To illustrate what a var usage looks like in clj-kondo's analysis data, let's look at the usage in app.core of util/child:

{:from app.core
 :from-var grandparent
 :to app.util
 :name child
 ...}

The :from key describes from which namespace the reference was used. The :from-var key describes in which var definition the var was used, and this is the key ingredient of tracking transitive var usages. The :to + :name keys describe which var was used.

Vars

In clj-kondo's analysis you can request metadata from vars with :meta [:x] (or all metadata with true). To distinguish all project vars from those that have :x metadata we can do the following:

(defn fq [ns name] (symbol (str ns) (str name)))

(def defs (:var-definitions analysis))
(def project-vars (set (map #(fq (:ns %) (:name %)) defs)))
(def with-x (set (keep #(when (-> % :meta :x) (fq (:ns %) (:name %))) defs)))

Here project-vars is a set of symbols of all the project vars and with-x are only those that have :x metadata.

Building the call graph

Now we're ready to build the call graph that lets us solve our problem. In the following we're making a map that looks like: caller -> callees, but we limit callees only to project vars since we're not interested in vars like cljs.core/assoc, reagent.core/atom etc.

(def graph
  (reduce (fn [g {:keys [from from-var to name]}]
            (let [callee (fq to name)]
              (if (and from-var (contains? project-vars callee))
                (update g (fq from from-var) (fnil conj #{}) callee)
                g)))
          {}
          (:var-usages analysis)))

The from-var condition leaves out any top level var usages. The (contains? project-vars callee) takes care of filtering only on project vars. After running this, we'll end up with a graph (map) that looks like:

{app.core/grandparent #{app.util/child}
 app.util/child       #{app.util/grandchild}}

So app.core/grandparent calls app.util/child and app.util/child calls app.util/grandchild.

Transitive reachability

Next we write a function to find out what vars are reachable from a set of vars starts.

(defn reachable [starts]
  (loop [seen #{}
         todo (set starts)]
    (if (empty? todo)
      seen
      (let [seen (into seen todo)
            used-vars (set (mapcat graph todo))
            unvisited (set/difference used-vars seen)]
        (recur seen unvisited)))))

(def children (set/difference (reachable with-x) with-x))

(prn {:graph graph
      :with-x with-x
      :children-of-x children
      :without-x (set/difference project-vars with-x children)})

The reachable function just calculates the transitive closure of the graph, given a set of starting nodes (vars). The children var is the set of reachable vars without the starting points (the vars with :x metadata).

{:graph {app.core/grandparent #{app.util/child}
         app.util/child #{app.util/grandchild}}
 :with-x #{app.core/grandparent}
 :children-of-x #{app.util/child app.util/grandchild}
 :without-x #{app.core/standalone}}

So the answer we were looking for is #{app.core/standalone}. This function is neither a transitive child of any function with :x metadata, nor does it have any :x metadata itself.

The code

Here's the full script once again.

#!/usr/bin/env bb
(require '[babashka.pods :as pods]
         '[clojure.set :as set])

(pods/load-pod 'clj-kondo/clj-kondo "2025.06.05")
(require '[pod.borkdude.clj-kondo :as clj-kondo])

(def analysis
  (-> (clj-kondo/run! {:lint ["src"]
                       :config {:analysis {:var-definitions {:meta [:x]}
                                           :var-usages true}}})
      :analysis))

(defn fq [ns name] (symbol (str ns) (str name)))

(def defs (:var-definitions analysis))
(def project-vars (set (map #(fq (:ns %) (:name %)) defs)))
(def with-x (set (keep #(when (-> % :meta :x) (fq (:ns %) (:name %))) defs)))

;; caller -> callees, project vars only
(def graph
  (reduce (fn [g {:keys [from from-var to name]}]
            (let [callee (fq to name)]
              (if (and from-var (contains? project-vars callee))
                (update g (fq from from-var) (fnil conj #{}) callee)
                g)))
          {}
          (:var-usages analysis)))

(defn reachable [starts]
  (loop [seen #{} todo (set starts)]
    (if (empty? todo)
      seen
      (let [seen (into seen todo)
            used-vars (set (mapcat graph todo))
            unvisited (set/difference used-vars seen)]
        (recur seen unvisited)))))

(def children (set/difference (reachable with-x) with-x))

(prn {:graph graph
      :with-x with-x
      :children-of-x children
      :without-x (set/difference project-vars with-x children)})

Conclusion

I hope you learned how useful clj-kondo analysis data can be for tracking relations between vars and that you can use this data in casual babashka scripts as well!

Published: 2026-06-10

Tagged: clojure clj-kondo babashka

OSS updates March and April 2026

In this post I'll give updates about open source I worked on during March and April 2026.

To see previous OSS updates, go here.

Sponsors

I'd like to thank all the sponsors and contributors that make this work possible. Without you, the below projects would not be as mature or wouldn't exist or be maintained at all! So a sincere thank you to everyone who contributes to the sustainability of these projects.

gratitude

Current top tier sponsors:

Open the details section for more info about sponsoring.

Sponsor info

If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work in the following ways. Thank you!

Updates

Babashka conf and Dutch Clojure Days 2026

Babashka Conf 2026 is happening on May 8th in the OBA Oosterdok library in Amsterdam! David Nolen, primary maintainer of ClojureScript, will be our keynote speaker. We're excited to have Nubank, Exoscale, Bob, Flexiana and Itonomi as sponsors. Nubank and Exoscale are hiring. Wendy Randolph will be our event host. For the schedule and other info, see babashka.org/conf. Join the babashka-conf Slack channel on Clojurians Slack for last minute communication. The day after babashka conf, Dutch Clojure Days 2026 will be happening, so you can enjoy a whole weekend of Clojure in Amsterdam. Hope to see many of you there!

Projects

In the last two months I spent significant time organizing babashka conf, but made progress in several projects as well.

My upstream work to enable async/await in ClojureScript was merged in the beginning of March. The implementation mirrors squint. Thanks David for reviewing and merging. Also deftest now supports an ^:async annotation so you can use async/await and don't need to mess around with the cljs.test/async macro anymore:

I'll be presenting this work at the Dutch Clojure Days.

Rebel-readline is now bb compatible. The work involved mainly exposing more JLine stuff and making sure rebel-readline didn't hit any internal JLine APIs. One step to drive this to completion was to make a dependency, compliment, bb compatible. Thanks both to Bruce and Alexander for the cooperation.

Squint now supports cljs.test and multimethods! clojure-mode was ported to use the new cljs.test.

On the cream front, I put in effort to make the binary smaller and have been keeping up with the new GraalVM EA releases. I've been posting bug reports to the crema maintainer. Currently there's still an unfixed bug around core.async that I have trouble reproducing in pure Java. I also added lots of library tests to CI so I can ensure stability in the long run. For now it remains experimental, but the direction is promising.

A performance PR to weavejester/dependency speeds up depend, depends? and topo-sort significantly, so clerk notebooks render faster.

The cljfmt library, also by @weavejester, now fully runs from source in babashka. The Java diff library that wasn't bb-compatible was replaced with text-diff, but only for the babashka path. The JVM build of cljfmt still uses the original Java diff library, with a possible switch later once text-diff has matured.

Several SCI fixes were made to improve Clojure compatibility between babashka and Clojure. E.g. records can now support extending to IFn which was a blocker for some Clojure libs that tried to run in bb so far.

Clj-kondo 2026.04.15 got a few new linters thanks to @jramosg for stewarding most of these. It also has better out of the box potemkin support, and @alexander-yakushev contributed a wave of performance improvements.

Updates per project below. Bullets are highlights; see each project's CHANGELOG.md for the full list.

  • babashka: native, fast starting Clojure interpreter for scripting.

    • Released 1.12.216, 1.12.217 and 1.12.218
    • Support rebel-readline as external REPL provider:
      • Add proxy support for Completer, Highlighter, ParsedLine, Writer, Reader
      • Add clojure.repl/special-doc and clojure.repl/set-break-handler!
      • Add clojure.main/repl-read
      • Add org.jline.reader.Buffer to class allowlist
    • Add clojure.java.javadoc namespace with javadoc available in REPL #1933
    • Fix (doc var), (doc set!) and other special forms #1932
    • Support (source inc) and (source babashka.fs/exists?) for built-in vars #1935
    • Support BABASHKA_REPL_HISTORY env var for configurable REPL history location #1930
    • Fix deftype and defrecord inside non-top-level forms (e.g. let, testing) #1936
    • #1948: add java.util.HexFormat interop support
    • #1403: fix uberscript warnings with :as-alias
    • #1955: support -version as an alias for --version
    • #1954: add clojure.lang.EdnReader$ReaderException
    • #1951: fix --prepare flag skipping next token
    • #1967: expose clojure.data.xml.tree/{flatten-elements,event-tree}, clojure.data.xml.event record constructors, and clojure.data.xml.jvm.parse/string-source
    • #1969: include java.net.Proxy and java.net.Proxy$Type Java classes (@jeeger)
    • #1939: disable JLine backslash escaping/shell history commands (@bobisageek)
    • Performance improvements for math operations and for calling functions on locals
    • Add many new classes to reflection config: java.lang.reflect.Constructor, java.lang.reflect.Executable, java.util.stream.Collectors, java.util.Comparator (for reify), and more
    • Bump JLine to 4.0.12, cheshire to 6.2.0, nextjournal.markdown to 0.7.255, edamame to 1.5.39, data.xml to 0.2.0-alpha11, jsoup to 1.22.2, rewrite-clj to 1.2.54, tools.cli to 1.4.256, transit-clj to 1.1.357, fs to 0.5.32
    • Full changelog
  • SCI: Configurable Clojure/Script interpreter suitable for scripting

    • Fix recur with 20+ args in loop (#1035)
    • Check recur arity, throw when it doesn't match (#1034)
    • Support IFn on defrecord, deftype and reify (#808, #1036)
    • Validate single binding pair in let-like conditional macros (#1037)
    • Normalize SCI types in hierarchy lookups (#1033)
    • Expose IPrintWithWriter as protocol (#1032)
    • Optimize tight loops: fused binding nodes + specialized inlined calls (#1031)
    • Support special form documentation in doc macro
    • Include SCI types in ns-map
    • Full changelog
  • clj-kondo: static analyzer and linter for Clojure code that sparks joy.

    • Released 2026.04.15
    • #2788: NEW linter: :not-nil? which suggests (some? x) instead of (not (nil? x)), and similar patterns with when-not and if-not (default level: :off)
    • #2520: NEW linter: :protocol-method-arity-mismatch which warns when a protocol method is implemented with an arity that doesn't match any arity declared in the protocol (@jramosg)
    • #2520: NEW linter: :missing-protocol-method-arity (off by default) which warns when a protocol method is implemented but not all declared arities are covered
    • #2768: NEW linter: :redundant-declare which warns when declare is used after a var is already defined (@jramosg)
    • #1878: support potemkin's import-fn, import-macro, and import-def
    • #2498: support new potemkin import-vars :refer and :rename syntax
    • Performance optimizations across many linting paths (@alexander-yakushev) and hook-fn lookup caching to avoid repeated SCI evaluation
    • Add type support for pmap and future-related functions (future, future-call, future-done?, future-cancel, future-cancelled?) (@jramosg)
    • #2762: fix false positive: throw with string in CLJS no longer warns about type mismatch (@jramosg)
    • #2770: linter-specific ignores now correctly respect the specified linters
    • #2773: align executable path for images to be /bin/clj-kondo (@harryzcy)
    • #2621: load imports from symlinked config dir (@walterl)
    • #2798: report correct filename and error details when StackOverflowError occurs during analysis
    • Full changelog
  • cream: Clojure + GraalVM Crema native binary

    • Followed each GraalVM EA release: EA21 shrunk the binary to ~175MB, EA22 brought a virtual-thread fix, EA23 fixed the forkjoin segfault, EA24 finally allowed re-enabling clojure.core.async-test
    • Added smoke tests for httpkit, nextjournal/markdown, clj-yaml, core.async ioc-macros
    • Updated 10M loop benchmark numbers for EA21
    • Added Windows test status notes (still some failures on EA22)
  • squint: CLJS syntax to JS compiler

    • Released 0.10.186, 0.11.187, 0.11.188 and 0.11.189
    • Add multimethod support: defmulti, defmethod, get-method, methods, remove-method, remove-all-methods, prefer-method, prefers, plus hierarchy ops isa?, derive, underive, make-hierarchy, parents, ancestors, descendants (#806)
    • cljs.test/report is now a multimethod, extensible via defmethod. test-var now fires :begin-test-var / :end-test-var events.
    • Accept plain await in async functions, in anticipation of CLJS next. The legacy js-await and js/await forms continue to work as aliases for now.
    • Add built-in cljs.test / clojure.test support: deftest, is, testing, are, use-fixtures, async, run-tests
    • Fix with-meta now preserves callability when applied to a function
    • #783: auto-load macros from .cljc files via :require (no need for :require-macros); resolve qualified symbols from macro expansions
    • #784: resolve transitive macro deps and auto-import runtime deps from macro expansion
    • #809: add squint.compiler/compile* and squint.compiler/transpile* which accept either a string or a sequence of pre-parsed forms, skipping the forms -> string -> forms roundtrip for SSR use cases
    • #810: shorthand classes in #html / #jsx were erased when an attrs map was present without a :class key
    • Full changelog
  • cherry: Experimental ClojureScript to ES6 module compiler

    • Accept plain await as a special form, in anticipation of CLJS next
    • Multiple :require-macros clauses with :refer now properly accumulate instead of overwriting each other
    • Add cherry.test with clojure.test-compatible testing API: deftest, is, testing, are, use-fixtures, async, run-tests. Macros are compiler built-ins (shared with squint), so no :require-macros plumbing is needed in user code.
  • nbb: Scripting in Clojure on Node.js using SCI

    • Released 1.4.207
    • #408: support IFn on defrecord and reify
    • Fix REPL and nREPL not awaiting promesa thenables (e.g. p/then results)
  • fs: file system utility library for Clojure

    • Released 0.5.32 and 0.5.33
    • #232: add touch fn (@lread & @borkdude)
    • #197: docstring review: consistent arg naming, improved docstrings, added Coercions and Returns / Argument Naming Conventions sections to README (@lread)
    • #231: get/set attribute functions were never following links. They now respect the :nofollow-links option (@lread)
    • #254: fix split-ext and extension on dotfiles with parent dirs (e.g. foo/.gitignore)
    • #202: gzip & gunzip now honor dest dir when specified (@lread)
    • #215: document effect of umask on created files and directories (@lread)
    • #182: enable soft & hard link tests on Windows (@lread)
    • #242: test: add JDK 26 to CI test matrix (@lread)
  • clerk: Moldable Live Programming for Clojure

    • Improve analysis performance by bumping weavejester/dependency (#808)
    • Bump SCI to v0.12.51 (#793), enables async/await in viewer functions
    • Improve presentation performance (#803)
    • Remove bb-specific code for array-map data structure (#805)
    • Preserve TOC opts (#806)
    • Remove redundant declare of present+reset! (#809)
    • Fix build-graph crash on non-Clojure source files (#810)
  • edamame: configurable EDN and Clojure parser with location metadata and more

    • Released 1.5.38 and 1.5.39
    • parse-ns-form: include :use and :require-macros in :requires
    • Check if object is iobj before attaching metadata #141 #142
  • Nextjournal Markdown: A cross-platform Clojure/Script parser for Markdown

    • Released 0.7.225
    • Add option :disable-footnotes true to disable parsing footnotes #67
  • quickdoc: Quick and minimal API doc generation for Clojure

    • Released 0.2.6
    • #42: fix var name not recognized in docstring when preceded by multiline backtick expression
    • #52: fix formatting of function signature when :or destructuring uses namespaced keyword fallback value
    • Dedent indented docstrings before rendering #53
  • grasp: Grep Clojure code using clojure.spec regexes

    • Released 0.2.5
    • Bump SCI to 0.12.51, Clojure to 1.12.4
    • Upgrade CI to GraalVM 25; move Windows CI from Appveyor to GitHub Actions
    • Fix bug in native which dropped all match results (@bsless)
    • Fix circular reference in grasp.impl
  • babashka.nrepl: The nREPL server from babashka as a library

    • Lock output stream in send to prevent interleaved bencode frames from concurrent writes
    • info and lookup op refinements: lookup carries nested info map whereas info is a flatmap
  • pod-babashka-instaparse: instaparse from babashka

    • Expose add-line-and-column-info-to-metadata
    • Drop macOS Intel builds, now building for macOS aarch64 only
    • Migrate Windows CI from Appveyor to GitHub Actions
    • Upgrade CI to GraalVM 25
    • Add --features=clj_easy.graal_build_time.InitClojureClasses to native-image
  • instaparse-bb: Use instaparse from babashka

    • Released 0.0.7
    • Bump pod to 0.0.7
    • Add add-line-and-column-info-to-metadata and get-failure
    • Fix opts passing in parser (e.g. :output-format :enlive)
    • Support java.net.URL for grammars
  • babashka-sql-pods: babashka pods for SQL databases

    • Released 0.1.5 and 0.1.6
    • #74: add DB2 support (@janezj)
    • #72: handle concurrent requests (@katangafor)
    • Upgrade to Oracle GraalVM 25.0.2; bump next.jdbc, cheshire (Jackson 2.12 -> 2.20), PostgreSQL, MSSQL, HSQLDB, MySQL Connector/J drivers
    • Remove DuckDB support
    • #51: macOS binaries are now aarch64 only
  • http-client: HTTP client built on java.net.http

    • Replace defunct httpstat.us examples with httpbin.org in tests
  • neil: A CLI to add common aliases and features to deps.edn-based projects

    • Fix README instructions for dev installation (@teodorlu)
  • deps.clj: a faithful port of the clojure CLI bash script to Clojure

    • Released 1.12.4.1618
    • #145: support for installing in FreeBSD and Windows bash environments including MINGW64, MSYS_NT and Cygwin (@ikappaki)
    • Catch up with Clojure CLI 1.12.4.1618

Contributions to third party projects:

  • ClojureScript:
    • CLJS-3470: added async/await support (merged!)
    • CLJS-3476: added async deftest support (merged!)
  • weavejester/dependency: improve performance of depend, depends?, and topo-sort
  • weavejester/cljfmt: #404 babashka compatibility via new text-diff lib (merged!)
  • Engelberg/instaparse: submitted #242 for babashka compatibility. Required :bb reader conditionals to replace the AutoFlattenSeq deftype with plain vectors plus metadata markers, swap the Segment deftype for a reify-based CharSequence, and add a CI test runner. Open, awaiting review.

Other projects

These are (some of the) other projects I'm involved with but little to no activity happened in the past two months.

Click for more details

Published: 2026-05-04

Tagged: clojure oss updates

OSS updates January and February 2026

In this post I'll give updates about open source I worked on during January and February 2026.

To see previous OSS updates, go here.

Sponsors

I'd like to thank all the sponsors and contributors that make this work possible. Without you, the below projects would not be as mature or wouldn't exist or be maintained at all! So a sincere thank you to everyone who contributes to the sustainability of these projects.

gratitude

Current top tier sponsors:

Open the details section for more info about sponsoring.

Sponsor info

If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work in the following ways. Thank you!

Updates

Babashka conf and Dutch Clojure Days 2026

Babashka Conf 2026 is happening on May 8th in the OBA Oosterdok library in Amsterdam! David Nolen, primary maintainer of ClojureScript, will be our keynote speaker! We're excited to have Nubank, Exoscale, Bob and Itonomi as sponsors. Wendy Randolph will be our event host / MC / speaker liaison :-). The CfP is now closed. More information here. Get your ticket via Meetup.com (there is a waiting list, but more places may become available). The day after babashka conf, Dutch Clojure Days 2026 will be happening, so you can enjoy a whole weekend of Clojure in Amsterdam. Hope to see many of you there!

Projects

I spent a lot of time making SCI's deftype, case, and macroexpand-1 match JVM Clojure more closely. As a result, libraries like riddley, cloverage, specter, editscript, and compliment now work in babashka.

After seeing charm.clj, a terminal UI library, I decided to incorporate JLine3 into babashka so people can build terminal UIs. Since I had JLine anyway, I also gave babashka's console REPL a major upgrade with multi-line editing, tab completion, ghost text, and persistent history. A next goal is to run rebel-readline + nREPL from source in babashka, but that's still work in progress (e.g. the compliment PR is still pending).

I've been working on async/await support for ClojureScript (CLJS-3470), inspired by how squint handles it. I also implemented it in SCI (scittle, nbb etc. use SCI as a library), though the approach there is different since SCI is an interpreter.

Last but not least, I started cream, an experimental native binary that runs full JVM Clojure with fast startup using GraalVM's Crema. Unlike babashka, it supports runtime bytecode generation (definterface, deftype, gen-class). It currently depends on a fork of Clojure and GraalVM EA, so it's not production-ready yet.

Here are updates about the projects/libraries I've worked on in the last two months in detail.

  • NEW: cream: Clojure + GraalVM Crema native binary

    • A native binary that runs full JVM Clojure with fast startup, using GraalVM's Crema (RuntimeClassLoading) to enable runtime eval, require, and library loading
    • Unlike babashka, supports definterface, deftype, gen-class, and other constructs that generate JVM bytecode at runtime
    • Can run .java source files directly, as a fast alternative to JBang
    • Cross-platform: Linux, macOS, Windows
  • babashka: native, fast starting Clojure interpreter for scripting.

    • Released 1.12.214 and 1.12.215
    • #1909: add JLine3 for TUI support
    • Console REPL (bb repl) improvements: multi-line editing, tab completion, ghost text, eldoc, doc-at-point (C-x C-d), persistent history
    • Support deftype with map interfaces (e.g. IPersistentMap, ILookup, Associative). Libraries like core.cache and linked now work in babashka.
    • Compatibility with riddley, cloverage, editscript, charm.clj
    • #1299: add new babashka.terminal namespace that exposes tty?
    • Add keyword completions to nREPL and console REPL
    • deftype supports Object + hashCode
    • #1923: support reify with java.time.temporal.TemporalQuery
    • Fix reify with methods returning int/short/byte/float
    • Full changelog
  • SCI: Configurable Clojure/Script interpreter suitable for scripting

    • Released 0.12.51
    • deftype now macroexpands to deftype*, matching JVM Clojure, enabling code walkers like riddley
    • case now macroexpands to JVM-compatible case* format, enabling tools like riddley and cloverage
    • Support async/await in ClojureScript. See docs.
    • Support functional interface adaptation for instance targets
    • Infer type tags from let binding values to binding names
    • defrecord now expands to deftype* (like Clojure), with factory fns emitted directly in the macro expansion
    • macroexpand-1 now accepts an optional env map as first argument
    • Add proxy-super, proxy-call-with-super, update-proxy and proxy-mappings
    • Support #564: this-as in ClojureScript
    • Store current analysis context during macro invocation, enabling tools like riddley to access outer locals
    • Full changelog
  • clj-kondo: static analyzer and linter for Clojure code that sparks joy.
    @jramosg, @tomdl89 and @hugod have been on fire with contributions this period. Six new linters!

    • Released 2026.01.12 and 2026.01.19
    • #2735: NEW linter: :duplicate-refer which warns on duplicate entries in :refer of :require (@jramosg)
    • #2734: NEW linter: :aliased-referred-var, which warns when a var is both referred and accessed via an alias in the same namespace (@jramosg)
    • #2745: NEW linter: :is-message-not-string which warns when clojure.test/is receives a non-string message argument (@jramosg)
    • #2712: NEW linter: :redundant-format to warn when format strings contain no format specifiers (@jramosg)
    • #2709: NEW linter: :redundant-primitive-coercion to warn when primitive coercion functions are applied to expressions already of that type (@hugod)
    • Add new types array, class, inst and type checking support for related functions (@jramosg)
    • Add type checking support for clojure.test functions and macros (@jramosg)
    • #2340: Extend :condition-always-true linter to check first argument of clojure.test/is (@jramosg)
    • #2729: Check for arity mismatch for bound vectors, sets & maps, not just literals (@tomdl89)
    • #2768: NEW linter: :redundant-declare which warns when declare is used after a var is already defined (@jramosg)
    • Add type support for pmap and future-related functions (@jramosg)
    • Upgrade to GraalVM 25
    • Full changelog
  • squint: CLJS syntax to JS compiler @tonsky and @willcohen contributed several improvements this period.

    • Add squint.math, also available as clojure.math namespace
    • #779: Added compare-and-swap!, swap-vals! and reset-vals! (@tonsky)
    • #788: Fixed compilation of dotimes with _ binding (@tonsky)
    • #790: Fixed shuffle not working on lazy sequences (@tonsky)
    • Multiple :require-macros with :refer now accumulate instead of overwriting (@willcohen)
    • Fix emitting negative zero value (-0.0)
    • Fix #792: prn js/undefined as nil
    • Fix #793: fix yield* IIFE
    • Full changelog
  • scittle: Execute Clojure(Script) directly from browser script tags via SCI

    • Support async/await. See docs.
    • Implement js/import not using eval
    • Support this-as
    • nREPL: print #<Promise value> when a promise is evaluated
  • nbb: Scripting in Clojure on Node.js using SCI

    • Support async/await. See docs for syntax.
    • Print promise result value in REPL/nREPL: (js/Promise.resolve 1) ;;=> #<Promise 1>
  • fs - File system utility library for Clojure

    • Released 0.5.31
    • #212: Introduce :keep true option in with-temp-dir
    • #188 copy-tree now throws if src or dest is a symbolic link when not following links (@lread)
    • #201 gzip now accepts source-file Path (@lread)
    • #207 review and update glob and match docstrings (@lread)
  • clerk: Moldable Live Programming for Clojure

    • Fix browse when using random port by passing 0, fixes #801
    • bb now supports editscript
  • neil: A CLI to add common aliases and features to deps.edn-based projects.

    • #258: neil test now exits with non-zero exit code when tests fail
  • cherry: Experimental ClojureScript to ES6 module compiler

    • Multiple :require-macros clauses with :refer now properly accumulate instead of overwriting each other

Contributions to third party projects:

  • ClojureScript:
    • Working on async/await support (CLJS-3470). I also implemented this in SCI, scittle, and nbb.
    • CLJS-3471: fix printing of negative zero
    • CLJS-3472: str on var that is set! returns empty string
  • editscript: Added babashka support, deps.edn for git dep usage, fixed CLJS tests
  • riddley: Added babashka compatibility, clj-kondo config
  • cloverage: Added babashka compatibility, migrated tools.cli from deprecated cli/cli to cli/parse-opts, bumped riddley
  • specter: Added babashka compatibility
  • compliment: Added babashka compatibility (PR #131)
  • rebel-readline: Removed JLine impl class dependencies for babashka compatibility, released 0.1.7
  • Selmer: Namespaced script tag context keys to avoid collisions, removed runtime require of clojure.tools.logging
  • charm.clj: Contributed JLine integration, FFM native terminal interface, babashka and native-image compatibility

Other projects

These are (some of the) other projects I'm involved with but little to no activity happened in the past month.

Click for more details

Published: 2026-03-05

Tagged: clojure oss updates

Archive