radFormatter / docs / cli
radFormatter CLI
A non-destructive Delphi source code formatter.
radFormatter tokenizes your source, builds a full Concrete Syntax Tree, and runs an owned-decision pipeline that assigns spacing, line breaks, indentation, and alignment — then renders the result without ever losing or altering a token beyond whitespace and casing.
Key features
Non-destructive. Every format is validated with a non-whitespace content hash and a re-lexed token fingerprint. If anything beyond whitespace or casing would change, the output is discarded and your original source is returned untouched.
CST-aware. Indentation, case structure,
generic parameters, and nested control flow are driven by real parse data,
not line heuristics.
72 configuration options across nine sections (casing,
spacing, indent, line breaks, reshaping, alignment, output, conditional, input). Commit one
radFormatter.json and every developer and CI job formats
identically. See the
configuration options reference.
Built-in profiles. Start from Default,
FormatterExe (RAD Studio formatter compatibility),
Embarcadero, or NoOp, then override.
CI-ready. -check reports files that need
formatting and exits 1 when any do; -diff shows
the unified diff of what would change.
Supported file types
.pas.dpr.dpk.dpkw.inc
Usage
radFormatter <file|pattern> [<file|pattern> ...] [-b] [-profile <name>] [-config <file>]
radFormatter -d <directory> [-r] [-b] [-profile <name>] [-config <file>]
radFormatter <file|pattern> [...] -check [-profile <name>] [-config <file>]
radFormatter <file|pattern> [...] -diff [-profile <name>] [-config <file>]
radFormatter -writeDefaultConfig <file>
Running with no arguments prints usage (exit 2). An
unrecognized -/-- token is a usage error
(Error: unknown option "...", exit 2) — a
mistyped flag is never silently treated as a filename.
Options
Grouped to match the radFormatter --help sections.
General
| Flag | Description |
|---|---|
| --help, -help, -h, -? | Print the usage screen and exit 0 (wins over every other argument) |
| --version | Print the version line and exit 0 (long form only — -v is --verbose) |
| -q, --quiet | Suppress the per-file success lines (Formatted: / would format:); errors, skips, and the end-of-run summary still print |
| -v, --verbose | Print per-file detail to stderr: a run-context line, then for each file its encoding decision (encoding=<enc> BOM=<yes/no> via=<basis>), change + line delta (changed=142->138 lines / unchanged=142 lines), and validation (validated=hash+fingerprint). STDOUT is unchanged. Mutually exclusive with -q |
Actions (default: format in place)
| Flag | Description |
|---|---|
| -b | Create a .bak backup of each file before overwriting |
| -check | Report files that need formatting; exit code 1 if any do (does not modify files) |
| -diff | Print a unified diff of what formatting would change; exit code 1 if any (does not modify files) |
| -writeDefaultConfig <file> | Write the default config JSON to <file> and exit |
Files are formatted in place by default (single file, multiple files, or
-d); use -check or -diff to inspect
changes without modifying files.
Input and scope
| Flag | Description |
|---|---|
| -d <directory> | Format all supported files found in this directory |
| -r | Recurse into subdirectories (with -d or a glob pattern) |
| -exclude <patterns> | Semicolon-separated exclude patterns (file names or wildcards); cumulative across repeated -exclude flags |
Supported extensions: *.pas, *.dpr,
*.dpk, *.dpkw, *.inc. Wildcards
(*, ?) are supported in file arguments.
Configuration
| Flag | Description |
|---|---|
| -profile <name> | Base profile: Default, FormatterExe, Embarcadero, or NoOp |
| -config <file> | JSON config file, overlaid on the selected profile |
Parse errors
| Flag | Description |
|---|---|
| -onParseError <policy> | What to do when a file cannot be parsed into a compilation unit: skip (default — leave it unformatted, warn to stderr, keep going) or fail (leave it unformatted and exit non-zero). Applies to formatting, -check, and -diff |
Conditional compilation / build context
These flags apply only to activeBranch mode;
allBranches and preserveConditionals ignore the
build context.
| Flag | Description |
|---|---|
| -conditionalMode <mode> | How {$IFDEF}/{$IF} branches are handled: activeBranch (default), allBranches, or preserveConditionals (see Conditional compilation) |
| -define <NAME[=VALUE]> | Add a conditional define for activeBranch mode (repeatable) |
| -platform <name> | Target platform for activeBranch mode (e.g. Win32Target, Win64Target) |
| -compilerVersion <ver> | Compiler version for activeBranch mode (e.g. "Delphi 12" or VER360) |
| -includePath <dir> | Include search dir for {$I} under activeBranch mode (repeatable) |
| -buildContext <file> | Build-context JSON (defines/platform/version/include paths) as the activeBranch base; explicit flags override it |
| -project <file.dproj> | Derive defines / include paths / platform / compiler version for activeBranch mode from a Delphi project. Mutually exclusive with -buildContext (both supply the conditional base) |
| -projectConfig <name> | Project build config to read with -project (e.g. Debug, Release) |
| -projectPlatform <name> | Project platform to read with -project (e.g. Win32, Win64) |
Source encoding
| Flag | Description |
|---|---|
| --assume-encoding <enc> | Assume <enc> for a non-BOM/non-ASCII file when it validly decodes (short: --assume). <enc> = utf8 | utf16le | utf16be | cpNNNN | NNNN. See Source encoding |
| -fallback <list> | Comma-separated encodings tried when the assumption fails (requires --assume-encoding). Always evaluated strict |
| --no-heuristic | Disable the BOM-less UTF-16 NUL-pattern detection probe |
Output and safety rules
- Format in place (default): every successful run
rewrites the source file(s) directly — a single file, multiple files,
and
-d/-d -rall format in place with no flag required. Use-bto keep a.bakbackup. - Inspect without modifying: use
-check(report which files need formatting; exit code1if any) or-diff(print a unified diff). Both are read-only and never write. -checkand-diffare mutually exclusive.- Files radFormatter cannot format are their own category:
one that cannot be parsed into a compilation unit (an incomplete unit, or
{$IFDEF}branches that cannot be resolved without a build context) or a detected non-Delphi dialect (Free Pascal{$mode}with unsupported constructs) is left unformatted and reported to stderr — never silently counted as “already formatted”, and never rewritten best-effort.-onParseError skip(default) warns and continues (exit unaffected);-onParseError failtreats it as an error (exit1). Under-check/-diffit is listed as skipped (could not parse) or skipped (unsupported dialect), distinct from “would format” and from “no change.” - The reason is specific where the parser can tell: when it is due to an unrecognized language element, the message names the construct and its location and points at the right fix — “looks like an FPC/other-dialect source” (radFormatter targets Delphi) versus “if this is valid newer Delphi, update radFormatter” (the message includes the bundled parser version) — rather than the generic missing-build-context hint.
- Auto config: when
-configis not given, radFormatter looks for aradFormatter.jsonnext to the executable and uses it if present. - End-of-run summary: a multi-file run prints one
trailing tally line, e.g.
Summary: 42 formatted, 3 skipped, 1 error (46 files)(-check/-diffsay would format). It is additive and does not affect the exit code. A single-file run prints no summary (its one progress line already says everything). For-diffthe summary is written to stderr so stdout stays a clean patch stream; the other modes print it to stdout with their per-file progress.
Source encoding
radFormatter reads and rewrites your files, so it is deliberately the strictest reader: it only formats a file whose encoding it can determine with confidence, and it never guesses. A wrong guess would re-encode identifiers to different bytes and write mojibake back to disk.
By default it reads known-encoding-only: a UTF-8/UTF-16
BOM, pure ASCII, BOM-less UTF-16, or valid (BOM-less) UTF-8. Anything else
— typically a BOM-less legacy code page (CP1252, CP1251, Shift-JIS,
...) — is skipped with exit code 4 and
left byte-for-byte unchanged, rather than mis-decoded. Writes always
round-trip the file's original encoding and BOM state; radFormatter never
transcodes (there is deliberately no output-encoding option).
To process a legacy tree, declare its encoding:
> radFormatter --assume-encoding cp1252 -d legacy-src -r
--assume-encoding utf8 is the explicit form of the default
assumption. For a mixed CJK tree, add a strict fallback ladder (a file that is
ambiguous under two or more pages fails rather than being guessed):
> radFormatter --assume-encoding utf8 -fallback 932,936,949,950 -d src -r
-fallback requires --assume-encoding. radFormatter
evaluates any fallback strictly and never applies a silent Western (CP1252)
catch-all — a single-byte legacy tree is named explicitly with
--assume-encoding cp1252, not rescued by a fallback. (Because a
writer's fallback is always strict, there is no separate strict-fallback
flag.)
These flags follow the shared radProgrammer source-encoding grammar, so the
same syntax works across the tool suite. Every one is also settable in
radFormatter.json under an
input section
(assumeEncoding / fallback / heuristic)
— the committed, per-directory way to declare a tree's encoding, so a
mixed monorepo can give each project its own code page. A CLI flag overrides
the config value.
Examples
Format a single file in place using the Default profile:
> radFormatter source/MyUnit.pas
Format a whole tree in place:
> radFormatter -d source -r
Format matching files in place, with backups:
> radFormatter -d source -r -b *.pas
Check formatting in CI without modifying files:
> radFormatter -check -config radFormatter.json -d source -r
Show what would change:
> radFormatter -diff -config radFormatter.json source/MyUnit.pas
Format a tree, excluding generated and vendored code:
> radFormatter -d source -r -exclude "generated;vendor;*.g.pas" *.pas
Use a profile with project overrides:
> radFormatter -profile Embarcadero -config team.json source/MyUnit.pas
Generate a starter configuration:
> radFormatter -writeDefaultConfig radFormatter.json
Configuration
All formatting behavior is set through a JSON config overlaid on the
chosen profile. Generate a fully populated default with
-writeDefaultConfig and edit to taste. The sections are:
| Section | What it controls |
|---|---|
| casing | Keyword, number, directive, and identifier casing |
| spacing | Operator, punctuation, parenthesis, bracket, generic, and range spacing; max consecutive spaces |
| indent | Indent size, tabs, continuation indent, case indentation, max column |
| lineBreaks | Keep-user-breaks, breaks around begin / single-instruction bodies / labels / sections, uses layout |
| reshaping | Per-line reflow of call args, enums, arrays, inheritance; end/else collapsing; delimited group style |
| alignment | Column-align assignments, colons, consts, and trailing comments |
| output | Line endings, right margin, empty-line control, strict mode |
| conditional | {$IFDEF}/{$IF} handling mode and the activeBranch build context (defines, platform, compiler version, include paths) |
See the configuration options reference for the full option reference, value types, defaults, and a complete example.
Per-directory config (radFormatter.json)
When you format a directory tree with -d, radFormatter also
looks for a radFormatter.json in each folder it walks. A config
found in a folder applies to that folder and every subfolder beneath
it, on top of your -profile + -config +
command-line settings. A radFormatter.json deeper in the tree
overlays one higher up, so each project (or subtree) can carry its own
settings — most usefully its own conditional include search path.
- Overwrite, not append. A per-directory config
replaces the setting it names (an
includePathsarray replaces the inherited list; it does not add to it), so a subtree can never inherit a wrong include path. For its subtree a directoryincludePathsalso overrides a command-line-includePath. - Relative paths in a
radFormatter.json(like./Source) resolve against that file's own folder. - Bounded to the
-dtree. Discovery does not look above the directory you pass to-d. (Formatting a single file does not search parent folders.) - Invalid config (bad JSON or an unsupported schema) is reported as a warning and skipped — the enclosing configuration stays in effect and the run continues.
Profiles
| Profile | Intent |
|---|---|
| Default | Conservative: reindents and applies case structure, but preserves spacing, casing, and user line breaks |
| FormatterExe | Approximates RAD Studio's built-in Formatter.exe |
| Embarcadero | The Embarcadero Delphi style guide |
| NoOp | Pass-through: output matches input byte-for-byte (modulo line endings) |
Omitting -profile uses Default. A
-config file is overlaid on the profile, so you only specify
the keys you want to change. See Profiles for a
side-by-side comparison with before/after examples.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (and, for -check / -diff, no files needed changes) |
| 1 | A file failed to format (validation/parse error), or — under -check / -diff — at least one file needs formatting |
| 2 | Usage error (unknown/incomplete flag, mutually exclusive flags, missing profile/config path, -fallback without --assume-encoding) |
| 3 | A file could not be read (I/O error) |
| 4 | A file's encoding could not be resolved — it was skipped and left unchanged. Declare it with --assume-encoding (see Source encoding) |
A run reports the most significant code it reached (4 over
3 over 1); any non-zero code fails a CI gate.
Safety guarantees
Content-hash validation — a whitespace-excluded hash of the source is compared before and after formatting; any mismatch discards the change.
Token fingerprinting — the output is re-lexed and its token fingerprint compared to the original; a mismatch discards the change.
Graceful failure — when validation fails, the original source is returned unchanged. No data loss.
Backups — -b writes a
.bak copy before overwriting.
Integration
Pre-commit hook
#!/bin/sh
radFormatter -check -config radFormatter.json -d source -r
GitHub Actions
- name: Check formatting
run: radFormatter -check -config radFormatter.json -d source -r
Team workflow
Commit radFormatter.json to your repository. Every developer
and CI job uses the same configuration — formatting disputes are
settled once, in the config file.
CLI reference as of radFormatter v2.7.986 (Alpha) — see release notes