TL;DR
If you’re using Default or Blue styles with JuliaFormatter.jl, v2.10 is (probably) production ready. It’s extensively tested and produces idempotent formatting as long as you disable certain options which are known to be problematic.
Background
Hello! You may have seen a couple of recent posts about JuliaFormatter (1, 2). If you haven’t been following this:
-
JuliaFormatter v2 was released in Oct 2024, with a large-scale migration from CSTParser.jl to JuliaSyntax.jl.
-
v2 has unfortunately been quite buggy, with a number of people sticking to v1, and others switching to Runic.jl. There are many reasons for this (please feel free to ask if you’re interested!) but fundamentally it’s because formatting is really difficult.
-
Since early this year JuliaFormatter hasn’t been actively maintained, with Dominique (the creator and previous maintainer) citing burnout.
In May we agreed to move JuliaFormatter to an organisation. I’ve been working on trying to bring v2 up to a level where it can be depended upon. (Yes, Claude has helped!)
What’s happened?
It would be impossible to enumerate everything that’s changed, but here are the highlights:
Bug fixes
Loads of bugs have been fixed. Particularly nasty ones included the meaning of array literals being silently changed (1, 2) and syntax transformations firing when they shouldn’t (1, 2, 3).
Testing
JuliaFormatter’s tests previously consisted of its unit tests plus some vendored codebases. Given that the v2 move to JuliaSyntax managed to pass all these CI tests, but broke a ton of code elsewhere, it seems reasonable to say that this wasn’t sufficient.
I’ve added tests which check that formatting succeeds and is idempotent across approximately 5 million lines of Julia code. These have caught dozens more bugs that hadn’t previously been reported, and going forward will help to minimise the chances of introducing new bugs.
LSP compatibility
We’ve upgraded to use JuliaSyntax v1, which makes JuliaFormatter v2 compatible with LanguageServer.jl and thus the Julia VSCode extension.
JETLS.jl also supports JuliaFormatter, and in recent versions line-range formatting was introduced which allows you to format only specific parts of an expression.
Syntax transformations are more conservative
JuliaFormatter has a number of options which change the AST of your code. always_use_return, which adds return to function bodies, is a simple example. In v2.10 these are always disabled by default inside macros and Exprs because they can change the meaning of code.
This can be overly conservative because, for example, many macros are “just” things like @testset and @inline which don’t pattern-match on the AST. However, JuliaFormatter has no way of knowing this and so I choose to err on the safe side. You can re-enable them inside macros if you want, by enabling the transform_syntax_in_macros option.
Note even with this turned on, some transformations are still disabled inside macros (see the docs for details). Furthermore, transformations are always disabled inside Exprs and there is no way to re-enable this.
Idempotence
Ideally a formatter should be idempotent, i.e., formatting text that has been already been formatted should not cause any changes. JuliaFormatter succeeded at this in many cases, but I think it’s safe to say that it hasn’t really been a priority until now.
JuliaFormatter performs formatting in several passes, or stages. The main way where idempotence can be lost is if a decision made at a later pass affected one made in an earlier pass (meaning that the second time formatting was performed, it would go down a different path). See e.g. this example. The corresponding solution is to either decouple these decisions, or to pre-calculate the later one so that we had enough information at the earlier stage.
Unfortunately, some options are fundamentally incompatible with this. These are described in the docs. Specifically, for Default style, I recommend adding the following to .JuliaFormatter.toml:
v2_stable_multiline_strings = true
and for Blue style:
v2_stable_multiline_strings = true
conditional_to_if = false
pipe_to_function_call = false
The other options that are enabled in Default and Blue styles are unlikely to cause any problems (as evidenced by testing).
What about SciML and YAS styles?
SciML and YAS styles come with default options which can cause lack of idempotence, and I haven’t yet investigated these thoroughly. They also aren’t tested thoroughly in CI (there are unit tests, but they aren’t tested against external corpora like Default and Blue styles). They also contain more custom code than Default and Blue styles.
I’m therefore quite wary that there may be more correctness bugs lurking with these styles which just haven’t been discovered yet. This is the main reason why I can’t yet promise that SciML and YAS styles are reliable.
Thanks
Finally I’d just (again) like to say thanks to everyone who opened issues or PRs! I really appreciate it.
I would also be very pleased if you, dear reader, would like to take a gamble on Default or Blue styles now and tell me if anything is messed up!
Please do be aware that formatting will be different from that in previous versions. There’s genuinely no way that I can promise that formatting won’t change. But, hopefully, it will be to your liking.