Formatting
soli fmt rewrites your .sl files into Soli's canonical style — 2-space indent, Ruby-style end, normalized operator spacing. Run it on save, in CI, or by hand.
AST-driven, never lossy
The formatter parses your file, then re-emits the AST. Output is guaranteed to be the same program — only the whitespace and keyword spelling change.
Usage
# Format every .sl file under the cwd, in place
soli fmt
# Format a single file
soli fmt path/to/file.sl
# Format every .sl file under one or more paths
soli fmt app/ lib/
# Don't rewrite — exit non-zero if any file would change (CI-friendly)
soli fmt --check
# Filter mode for editor integration
soli fmt --stdin < in.sl > out.sl
- With no path argument, the current working directory is walked recursively.
- Already-formatted files are left untouched — no spurious
mtimebump. --checkprintswould reformat: <path>for each unformatted file and exits with status1if any are found.--stdinreads from stdin and writes to stdout — no file is touched. Use this for editor "format on save" hooks.
Style
| Rule | Detail |
|---|---|
| Indent | 2 spaces, never tabs |
| Functions | def name() ... end (parens kept even when empty) |
| Classes | class X < Y ... end |
| Control flow | if cond ... end, while cond ... end, etc. |
| Operators | Normalized spacing — a + b, a == b, a && b |
| Comments | Preserved at their original line positions |
| Comment marker | // line comments are normalized to # |
| Blank lines | Multiple consecutive blank lines collapse to one |
| Guard clauses | if cond return … end with no else gets a trailing blank line |
Editor Integration
Hook soli fmt --stdin into your editor's "format document" command. The filter mode is the same shape as gofmt, rustfmt --emit=stdout, or black -, so most LSP and format-tool wrappers can drive it with no extra config.
soli fmt --stdin
See Editor Integration for language-server setup.
CI Usage
The recommended CI gate:
soli fmt --check
It exits non-zero on the first unformatted file it sees, listing each one as would reformat: <path>. Pair with soli lint for a complete style gate.
Coverage & Fallback
A handful of advanced grammar forms aren't canonicalized yet — @sdbql{ … } query blocks, list/hash comprehensions, and some complex match patterns. For these nodes the formatter copies the original source bytes verbatim via the AST span: semantics are preserved, the surrounding code is still formatted, and the body of the un-modeled node is left exactly as you wrote it.
Fixed point
Running soli fmt on an already-formatted file is a no-op. You can mix formatted code and un-modeled forms freely without churn.