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

FlagDescription
--help, -help, -h, -?Print the usage screen and exit 0 (wins over every other argument)
--versionPrint the version line and exit 0 (long form only — -v is --verbose)
-q, --quietSuppress the per-file success lines (Formatted: / would format:); errors, skips, and the end-of-run summary still print
-v, --verbosePrint 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)

FlagDescription
-bCreate a .bak backup of each file before overwriting
-checkReport files that need formatting; exit code 1 if any do (does not modify files)
-diffPrint 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

FlagDescription
-d <directory>Format all supported files found in this directory
-rRecurse 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

FlagDescription
-profile <name>Base profile: Default, FormatterExe, Embarcadero, or NoOp
-config <file>JSON config file, overlaid on the selected profile

Parse errors

FlagDescription
-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.

FlagDescription
-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

FlagDescription
--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-heuristicDisable the BOM-less UTF-16 NUL-pattern detection probe

Output and safety rules

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:

SectionWhat it controls
casingKeyword, number, directive, and identifier casing
spacingOperator, punctuation, parenthesis, bracket, generic, and range spacing; max consecutive spaces
indentIndent size, tabs, continuation indent, case indentation, max column
lineBreaksKeep-user-breaks, breaks around begin / single-instruction bodies / labels / sections, uses layout
reshapingPer-line reflow of call args, enums, arrays, inheritance; end/else collapsing; delimited group style
alignmentColumn-align assignments, colons, consts, and trailing comments
outputLine 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.

Profiles

ProfileIntent
DefaultConservative: reindents and applies case structure, but preserves spacing, casing, and user line breaks
FormatterExeApproximates RAD Studio's built-in Formatter.exe
EmbarcaderoThe Embarcadero Delphi style guide
NoOpPass-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

CodeMeaning
0Success (and, for -check / -diff, no files needed changes)
1A file failed to format (validation/parse error), or — under -check / -diff — at least one file needs formatting
2Usage error (unknown/incomplete flag, mutually exclusive flags, missing profile/config path, -fallback without --assume-encoding)
3A file could not be read (I/O error)
4A 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