Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Comparison

Fatou overlaps with several Julia tools: the language servers LanguageServer.jl and JETLS.jl, and the formatters Runic.jl and JuliaFormatter.jl.

The two servers also carry linters, JuliaWorkspaces.jl in the case of LanguageServer.jl and JET.jl in the case of JETLS.jl. Although some of the linting is done inside the servers in both cases.

The primary difference with respect to the tools above is that Fatou is a compiled Rust binary that never starts Julia. It reads your code the way rust-analyzer reads Rust, rather than loading it into a running session. Everything below follows from that. All of these tools are MIT-licensed.

Language Servers

FatouLanguageServer.jlJETLS.jl
Requires a Julia installNoYesYes
Analysis modelSyntax and name/scopeStatic analysis + runtime symbol indexType inference (JET.jl)
Type-aware diagnosticsNoBest-effortYes
FormatterBuilt inDelegates to JuliaFormatter or RunicDelegates to Runic or JuliaFormatter
LinterBuilt inBuilt in (StaticLint)Built in (lowering + type checks)
First-run costNonePrecompilation and package indexingPrecompilation
MaturityYoungMature, de-facto defaultExperimental

Not running Julia means no toolchain to install, no precompilation, and no package-indexing phase before the server is useful. It also means results depend only on the source text, not on which packages happen to be precompiled in the active environment.

The cost is types. Fatou cannot tell you the inferred type of an expression or flag anything that depends on one, and it does not load your dependencies to learn their exported symbols, methods, and docstrings.

LanguageServer.jl

The mature default, and the backend of the Julia VS Code extension. Its architecture is closer to Fatou’s than you might expect: since the v5 rewrite the analysis engine is JuliaWorkspaces.jl on top of Salsa.jl, the same incremental query model Fatou gets from Rust’s salsa, and it parses with JuliaSyntax, which Fatou’s parser is written to match.

The runtime is where they part. LanguageServer.jl discovers your dependencies’ symbols by inspecting them in a spawned Julia process and caching the results. That index is what powers completion and hover for third-party packages, and it is also why the first run on a large project can take minutes.

JETLS.jl

A language server made by the author of JET.jl, and built on the actual compiler: type inference through JET.jl, macro-aware navigation through JuliaLowering.jl. It offers what the others cannot, including types on hover, inlay type hints, and diagnostics for non-existent field access or out-of-bounds indexing.

It is also the furthest from Fatou. As of 2026 its README calls it experimental and not production-ready, it needs Julia 1.12 or newer, and it is under heavy development. It is being integrated into the Julia VS Code extension.

Formatters

FatouRunic.jlJuliaFormatter.jl
Requires a Julia installNoYesYes
ConfigurationWidth, indent, line endingNone~38 options, .JuliaFormatter.toml
Named stylesOneOneDefault, Blue, YAS, SciML, Minimal
Line-width limitYes (default 92)NoneYes (margin, default 92)
Reflow modelAlways full reflowPreserves the author’s breaksFull reflow, unless join_lines_based_on_source
Output depends on input layoutNeverYesOnly with source-honoring enabled

The interesting difference is what decides where the line breaks go. Given this input:

foo(
  a,
  b,
)
bar(aaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccc, dddddddddddddddd, eeeeeeeeeeeeeeee)

Fatou produces:

foo(a, b)
bar(
    aaaaaaaaaaaaaaaa,
    bbbbbbbbbbbbbbbb,
    cccccccccccccccc,
    dddddddddddddddd,
    eeeeeeeeeeeeeeee,
)

It collapses the short call the author had split, and breaks the long call the author had left on one line, purely by measuring against line-width. Runic does the opposite on both: it keeps foo expanded because the author broke it, and leaves bar alone because it has no width limit. JuliaFormatter’s defaults behave like Fatou’s here.

Equivalent code formats identically under Fatou no matter how it was laid out. There is no way to hand-arrange your source into a different result. Formatting is also idempotent, and the test suite checks format(format(x)) == format(x) over every fixture.

Runic.jl

Modeled on gofmt, with no configuration at all: indentation is four spaces and there are no style knobs. Fatou and Runic agree that formatting should not be re-litigated per project.

They disagree about reflow. Runic has no line-width limit and never breaks or joins lines for width; its own docs say “Line width limit: No. Use your Enter key or refactor your code.” It normalizes how a construct looks once you have broken it, but single-line versus multi-line stays your decision. Fatou takes that decision from the width instead.

Beyond whitespace the two overlap heavily. Both normalize numeric literals (1. to 1.0, .5 to 0.5), operator spacing, for iteration syntax (= and to in), and where clauses (where T to where {T}). Runic also inserts explicit return statements; Fatou does not.

Runic is the better fit if you want to place line breaks by hand and already run Julia.

JuliaFormatter.jl

Dominique Luna’s formatter takes the opposite approach to configuration: around 38 options read from a .JuliaFormatter.toml, plus named styles (Default, Blue, YAS, SciML, Minimal). Its width-driven reflow is the closest of the three to Fatou’s.

Where it goes further is control. It can convert between short and long function definitions, rewrite import to using, add return statements, and honor the source’s line breaks with join_lines_based_on_source. Fatou exposes line-width, indent-width, and line-ending, and nothing else.

Reach for JuliaFormatter when you want a specific named style or the AST-level rewrites.

Linters

FatouLanguageServer.jl (StaticLint)JETLS.jl
Requires a Julia installNoYesYes
Type-based diagnosticsNoLimitedYes
Cross-package symbol knowledgeBase/Core snapshot + workspaceIndexes installed dependenciesLoads code
Rule modelNamed rules, select/ignore, severityjulia.lint.* togglesDiagnostic stages
AutofixSafe and unsafe fixesSome quick fixesSome code actions
Standalone CLIfatou lintNo, runs inside the serverjetls check

Fatou’s rules resolve names and scopes over the syntax tree, so they stay silent whenever certainty runs out. undefined-name resolves an identifier against locals, file bindings, workspace siblings, whole-module using exports, and a Base/Core snapshot, and flags it only when no tier provides it; if the file calls eval, includes outside a known workspace, or usings a module Fatou cannot resolve, the rule skips the file rather than guess. call-arity treats an unknown method as a reason to say nothing.

Both rules need project context to be sound, so the CLI leaves them opt-in and the language server turns them on for workspace member files. The bargain is that Fatou misses real problems a type-aware linter would catch, and rarely cries wolf. StaticLint’s MissingReference has the opposite reputation: false positives on valid using and import code.

The check families otherwise overlap a good deal with StaticLint’s: missing references, unused bindings and arguments, incorrect call arguments, nothing comparisons, constant conditionals, include loops, module naming, and unused type parameters have counterparts on both sides. Fatou also has a type-piracy rule, which needs enough project context to tell an owned type from a foreign one and is opt-in for the same reason.

JETLS diagnoses in three stages: syntax errors from JuliaSyntax, lowering-stage checks (undefined and unused bindings, unreachable code, scope ambiguities, import issues), and type inference through JET.jl. The first two overlap with Fatou’s rules. The third does not, and cannot be replicated without running the compiler; JETLS itself defers that pass to save, while Fatou’s static rules run on every keystroke.

Fatou’s rule model is borrowed from Ruff: stable rule IDs in categories, select/ignore and per-rule severity in [lint], --fix for safe fixes with --unsafe-fixes for the rest, and pretty, concise, or json output from the CLI. See the rule reference for the catalogue.

Running Them Together

Fatou and a Julia-native server coexist happily: Fatou formats and runs its static checks instantly and everywhere, including CI via fatou-action or pre-commit with no Julia setup, and the Julia server contributes the type-aware analysis that needs a running compiler. Editor Setup shows how to give Fatou formatting while another server keeps the rest.

For formatting throughput and cold-start numbers against Runic and JuliaFormatter, see Performance.