# A Language Server That Reads the Compiler's Mind

JETLS.jl gets its diagnostics straight from Julia's own compiler, and my plugin has to guess its command line every time that tool changes shape.


Most language servers read your file and make an educated guess about what the compiler would say. JETLS.jl skips the guessing and asks the compiler directly.
That distinction sounds small until you have watched a language server get something wrong that the real compiler would never get wrong, because the language server never ran your code through inference in the first place. It parsed the syntax tree, matched some patterns, and did its best. Good enough for most editing, and the reason so many diagnostics in so many languages are subtly stale by the time you see them.
Built on the compiler’s own machinery, not a copy of it JETLS.jl is built on JET.jl, JuliaSyntax.jl and JuliaLowering.jl - the same packages Julia’s own toolchain uses to parse, lower and analyze code, not a separate parser someone wrote to approximate them. That is why it can offer runtime-integrated, type-sensitive diagnostics: the errors it flags come from actually running your code through Julia’s inference machinery, the identical process that decides what gets compiled, so a diagnostic reflects what the compiler currently believes about your types, not a static pattern match. Go-to-definition works the same way. Ask it to jump to the definition behind a macro call and it can follow the macro to where it actually expands, because it has access to the lowered form, not just the text you typed.
That is a genuinely different design from parse-and-guess, and it is not a Julia-only idea. Editor tooling in general has been drifting this direction for years, wherever a compiler’s incremental story got fast enough to keep up with keystrokes instead of a batch build. Parse-and-guess was never the goal, it was the workaround for compilers too slow to ask directly. JETLS is what it looks like when a young, fast-moving language gets a tool willing to skip the workaround.
It is also a young one. JETLS supports Julia 1.12.2 through 1.13 and nothing outside that window, and its own maintainers describe it as explicitly not production ready. It had a dedicated talk at JuliaCon 2026, which is about right for a tool at this stage: interesting enough to talk about, not yet something you would bet a team’s tooling on.
The part I actually have to deal with Flexible Julia, my JetBrains plugin, can drive three different language server backends: LanguageServer.jl, which runs through the Julia executable itself; Fatou, a standalone Rust binary that never touches Julia at all; and JETLS.jl, installed through Pkg.Apps.add. Supporting three backends instead of one is already more code than I would like. What makes JETLS specifically annoying is that its command line has changed while I was building against it.
Early JETLS took --stdio directly. At some point in 2026 it grew subcommands - serve, check, schema, version - and --stdio moved behind serve. Nothing announced that to me. I found out because the process kept exiting instead of starting, and the fix I ended up with is a small function that runs jetls -- version before every launch and reads the output to guess which era of CLI it is talking to. If the output contains “Unknown CLI argument”, it is an old build, no subcommand needed. If the command exits cleanly with real output, it is new, use serve. If neither test is conclusive, I default to assuming the new format, on the theory that any fresh install from here on is more likely to be 2026-or-later than not. That whole function exists because a maintained tool is allowed to change its interface, and anything that shells out to it has to notice when it did.
Why I keep all three anyway None of this is a complaint about JETLS. A tool that is honest about not being production ready is allowed to move its CLI around, and I would rather sniff a version string than pin my plugin to whatever the interface looked like the week I wrote the integration. But I want to be straight about what supporting three backends actually is. It is not a feature I show off. It is three sets of failure modes, three install stories, three things that can silently stop working, all so that whichever backend a given Julia developer already has on their machine just works. If I could pick one and delete the other two, I would, and I am not there yet.
That is a fair trade for what compiler-integrated tooling buys you. I just was not expecting the price to include reading version strings for hints.
