radFormatter / docs / config-options

Configuration options reference

radFormatter is configured with a JSON document. Every key is optional; any key you omit keeps the value from the active profile (see Profiles). A missing or empty config therefore applies the chosen profile's defaults — with no profile selected, that is the built-in Default profile.

There are 72 options across nine sections: casing, spacing, indent, lineBreaks, reshaping, alignment, input, output, and conditional.

Generate a fully populated config with the current defaults using the CLI:

> radFormatter -writeDefaultConfig radFormatter.json

See the CLI reference for how the CLI loads and overlays configuration.

Prefer a visual editor? The IDE plugin includes a split-screen configuration editor that reads and writes this same file, with a live before/after preview of every option against real source — included even in the free Community Edition, so CLI users can edit configs visually too.

casing

Casing changes are the only non-whitespace edits radFormatter makes; every other change is whitespace or line breaks.

OptionTypeDefaultEffect
casing.keywordscase mode"asis"Reserved words (begin, end, if, …)
casing.numberscase mode"asis"Letters in numeric literals: hex digits AF and the float exponent marker E
casing.directivescase mode"asis"Compiler-directive keyword ({$IFDEF}, {$DEFINE}); the directive body is preserved
casing.identifiersident case mode"asis"Identifiers

spacing

AsIs means “do not touch”. For any spacing option, asis reproduces the original inter-token whitespace verbatim. At the Default profile (every spacing option asis) radFormatter makes no inter-token spacing changes at all — it only changes indentation. The opinionated profiles set spacing options to concrete values.
OptionTypeDefaultEffect
spacing.assignmentspacing option"asis"Space around :=
spacing.commaspacing option"asis"Space around ,
spacing.colonspacing option"asis"Space around :
spacing.semicolonspacing option"asis"Space around ;
spacing.insideParenstri-state"asis"Space just inside ( )
spacing.insideBracketstri-state"asis"Space just inside [ ]
spacing.beforeParenstri-state"asis"Space before an opening ( (calls / declarations)
spacing.aroundRangetri-state"asis"Space around the range operator ..
spacing.beforeBrackettri-state"asis"Space before an opening [ (index / bounds)
spacing.genericBracketstri-state"asis"Space just inside generic angle brackets < >
spacing.aroundUnaryOperatorstri-state"asis"Space around unary -, +, @
spacing.aroundBinaryOperatorstri-state"asis"Space around symbol binary operators (=, <>, +, -, *, /, …)
spacing.maxConsecutiveSpacesinteger-1-1/0 = no limit; N = collapse any interior run of spaces longer than N down to N. Never widens a 0- or 1-space gap; leading indentation is unaffected
Keyword operators are excluded from aroundBinaryOperators. Word operators such as and, or, xor, div, mod, shl, shr, in, is, as are never tightened, because removing their spaces would produce invalid code.

indent

OptionTypeDefaultEffect
indent.sizeinteger2Spaces per depth level. Applies to space indentation only — ignored when the effective style is tabs (the editor's tab stop owns the visual width). 0 disables reindentation
indent.style"spaces" | "tabs" | "asIs""asIs"Indent character: spaces always uses spaces; tabs uses one tab per structural level (continuation/alignment stay spaces); asIs (Default) preserves the source's indent character — the first line with leading indentation decides (leading tab → tabs, leading space → spaces), none → spaces. Depth is always recomputed; only the character is preserved
indent.tabWidthinteger0Display width (in columns) used only to measure existing tab-indented source when computing visual columns for right-margin / soft-break decisions. 0 = same as indent.size; >0 = an explicit editor tab-stop width. Does not affect emitted indentation
indent.directives"asIs" | "column0" | "structural""asIs"Compiler-directive indentation: asIs preserves source indent (Default); column0 forces the left margin (FormatterExe/Embarcadero); structural indents to surrounding depth
indent.continuationSizeinteger2Extra spaces added to wrapped / continuation lines
indent.anchorBasedboolfalseWhen wrapping, align continuation lines under the column after the opening delimiter instead of using a fixed continuation indent
indent.maxColumninteger00 = no cap; >0 = clamp any computed indent to this column
indent.caseLabelsbooltrueIndent case arm labels one level under the case keyword
indent.caseContentbooltrueIndent a case arm body one level under its label
indent.caseSingleLinebooltruetrue = allow a single-statement case arm body to stay on the label line; false = force it onto its own line
indent.caseElseAlignment"asIs" / "withLabels" / "withCase""asIs"asIs keeps a case's else at its source column (content re-indented one level under it); withLabels aligns it with the arm labels; withCase aligns it with the case keyword
indent.commentSingleLine"structural" / "anchored" / "asIs""structural"Single-line own-line comments (// or a single-line block): structural re-indents each to the enclosing content level; anchored preserves the comment's source column but shifts it by the same amount the statement it leads (its anchor) moved, so it tracks re-indented code without normalizing the offset (falls back to asIs when there is no following statement; never dedents onto a following end/else); asIs preserves the comment's source column
indent.commentMultiLine"structural" / "anchored" / "asIs""structural"Multi-line { } / (* *) comments: structural re-indents the opener to the content level and re-homes the closer; anchored delta-shifts the opener to track its anchor's movement (closer left verbatim, like asIs); asIs preserves the opener column and leaves the closer verbatim (interior lines are verbatim either way)
indent.labelIndent"bodyLevel" | "blockOpener" | "column0" | "asIs""bodyLevel"Indentation of a stand-alone goto-label line (findAgain: alone on its line): bodyLevel keeps it at the statement column (Default); blockOpener outdents it one level to its enclosing block-opener column (Embarcadero); column0 forces the left margin; asIs preserves the source column. A same-line label: stmt; is unaffected

lineBreaks

OptionTypeDefaultEffect
lineBreaks.keepUserbooltrueWhen true, radFormatter only adds line breaks and never removes ones you placed. Set false for full reflow
lineBreaks.beforeSingleInstrtri-state"asis"Newline before a single-statement control body (if/while/for/…)
lineBreaks.beforeBegintri-state"asis"Newline before begin
lineBreaks.betweenElseAndIfboolfalsetrue = put else and if on separate lines; false = keep else if together
lineBreaks.afterBeginboolfalseForce a line break after begin
lineBreaks.afterSectionKeywordboolfalseForce a line break after a section keyword (uses, type, var, const, interface, implementation)
lineBreaks.afterLabeltri-state"asis"Newline after a statement label's colon
lineBreaks.usesMode"preserve" / "singleLine" / "onePerLine""preserve"How uses clauses are laid out: unchanged, collapsed to one line, or one unit per line
lineBreaks.lineAfterSemicolonboolfalseForce a line break after every statement-terminating semicolon, so each statement lands on its own line. Add-only (never removes user breaks) and CST-aware — parameter-list and declaration separators are left untouched
lineBreaks.sectionKeywordJoin"preserve" / "join" / "break""preserve"Placement of a section introducer (type/const/var) that follows a visibility specifier (private type, strict private type): preserve keeps the author's layout (never splits an inline pair, never joins a broken one); join collapses the pair onto one line; break forces them onto separate lines. The atomic compound directives (strict private, class var/class function/…) are always intact regardless. An intervening comment (private {note} type) leaves the pair as authored under every mode

reshaping

Structural rewrites of a construct's layout. All default off / conservative.

OptionTypeDefaultEffect
reshaping.oneParamPerLineInCallsboolfalsePut each call argument on its own line
reshaping.collapseEndElseboolfalseCollapse end + newline + else onto one line
reshaping.collapseEndElseBeginboolfalseCollapse end + else + begin onto one line
reshaping.joinMultilineParensboolfalseCollapse a multi-line parenthesized expression to a normalized inline form (output.rightMargin may then rewrap it)
reshaping.arrayInitPerLineboolfalsePut each array-initializer element on its own line
reshaping.inheritancePerLineboolfalsePut each class/interface inheritance-list entry on its own line
reshaping.enumPerLineboolfalsePut each enum value on its own line
reshaping.delimitedGroupStyle"compact" / "allman""compact"Bracket/grouping style for delimited groups: opening delimiter trailing (compact) or on its own line (Allman)

alignment

Column alignment across consecutive, structurally similar lines. All off by default. Alignment runs after indentation and only inserts interior padding.

OptionTypeDefaultEffect
alignment.assignmentsboolfalseAlign := operators
alignment.colonsboolfalseAlign : type annotations in declarations (parameter lists excluded)
alignment.constsboolfalseAlign = in const declarations
alignment.trailingCommentsboolfalseAlign end-of-line // comments
alignment.maxPadWidthinteger00 = no limit; >0 = if aligning a group would require inserting more than N spaces of padding, that group is left unaligned

input

The source-read policy: how a file's encoding is resolved before formatting. These keys correlate 1:1 with the CLI encoding flags and use the same utf8 / utf16le / utf16be / cpNNNN / NNNN grammar, so a value set here is the committed, per-directory equivalent of the flag (a mixed monorepo can declare each tree's code page in its own radFormatter.json). Defaults reproduce the known-encoding-only default, so an absent input section changes nothing. See the Source encoding section for the model. Precedence: built-in default < input config < CLI flag.

OptionTypeDefaultMeaning
input.assumeEncodingstring""Assume this encoding for a non-BOM/non-ASCII file when it validly decodes (the CLI --assume-encoding). Empty keeps the built-in UTF-8 assumption. Declare a legacy tree with, e.g., "cp1252"
input.fallbackstring array[]Encodings tried when the assumption fails (the CLI -fallback). Requires assumeEncoding. Always evaluated strict: an ambiguous file fails rather than being decoded by a Western catch-all
input.heuristicbooltrueWhen false, disables the BOM-less UTF-16 NUL-pattern probe (the CLI --no-heuristic)
A fallback without assumeEncoding, or an unparseable encoding token, makes the config refused (the run reports the error and does not format), mirroring the CLI's usage-error handling.

output

OptionTypeDefaultEffect
output.lineEnding"platform" / "crlf" / "lf" / "cr" / "detect""platform"platform uses the platform default line ending (CRLF on Windows), regardless of the source; crlf / lf / cr force that terminator; detect keys off the source file's first line break (\r\n→CRLF, \n→LF, \r→CR; none → platform) and applies it uniformly, so a LF-only file stays LF. The NoOp profile uses detect
output.rightMargininteger0Wrap column. 0 = disabled. When >0, lines longer than this are wrapped at their declared soft-break points (call arguments, parameter lists, uses commas, binary operators, inheritance lists, and so on)
output.maxEmptyLinesinteger-1-1 = unlimited; 0+ = maximum consecutive blank lines
output.trimTrailingWhitespacebooltrueWhen true, end-of-line whitespace is removed (the renderer rebuilds each line from its tokens). When false, a line's trailing whitespace is preserved verbatim — both the run after the last token and a whitespace-only blank line's spaces. The NoOp profile sets this false so it is a true byte-for-byte pass-through; other profiles keep it true
output.strictModebooltrueWhen true, an unclassifiable feature is a validation error; when false, unknown constructs pass through untouched

output.emptyLines

Integer counts for blank lines at structural boundaries. -1 (default) means “leave as-is”; a value N >= 0 ensures exactly N blank lines there.

OptionEffect
output.emptyLines.beforeVisibilityBefore private / protected / public / published inside a class
output.emptyLines.aroundSectionsAround interface / implementation / initialization / finalization
output.emptyLines.betweenDeclarationsBetween routine declarations
output.emptyLines.beforeTypeKeywordBefore a type section

conditional

How {$IFDEF}/{$IF} branches are parsed and formatted. See the Conditional Compilation section of Known limitations for the full behavior contract.

OptionTypeDefaultEffect
conditional.mode"activeBranch" / "allBranches" / "preserveConditionals""activeBranch"activeBranch formats the branch selected by the build context and preserves inactive branches verbatim (directives included); allBranches parses every branch as one stream and formats best-effort (no build context used); preserveConditionals leaves every conditional line exactly as authored — any physical line holding a conditional directive, or lying between an opener and its matching closer, is emitted verbatim — while everything outside conditional regions is reformatted (it folds against the deterministic default context only to obtain a parse stream; that branch choice never affects the verbatim regions). Values parse case-insensitively
conditional.platformstring""Build context: a target-platform enum name (Win32Target, Win64Target, …). Empty = Win32Target (deterministic default)
conditional.compilerVersionstring""Build context (used by activeBranch only): compiler version as a product name ("Delphi 12"), an alias, or a VERxxx define. Empty = the parser's latest known version (deterministic)
conditional.definesstring array[]Build context: conditional symbols to define (a NAME=VALUE entry keeps only NAME; folding is name-only)
conditional.includePathsstring array[]Build context: extra directories searched for {$I} include files that gate conditionals. The source file's own directory is searched automatically when the caller supplies the file path
When a project supplies the context (IDE active project, or CLI -project / -buildContext), it replaces the four context values above entirely — the config context is the no-project fallback only.

Full default configuration

This is exactly what -writeDefaultConfig emits (the Default profile).

{
  "schemaVersion": "3.5",
  "casing": {
    "keywords": "asis",
    "numbers": "asis",
    "directives": "asis",
    "identifiers": "asis"
  },
  "spacing": {
    "assignment": "asis",
    "comma": "asis",
    "colon": "asis",
    "semicolon": "asis",
    "insideParens": "asis",
    "insideBrackets": "asis",
    "beforeParens": "asis",
    "aroundRange": "asis",
    "beforeBracket": "asis",
    "genericBrackets": "asis",
    "aroundUnaryOperators": "asis",
    "aroundBinaryOperators": "asis",
    "maxConsecutiveSpaces": -1
  },
  "indent": {
    "size": 2,
    "style": "asIs",
    "tabWidth": 0,
    "directives": "asIs",
    "continuationSize": 2,
    "anchorBased": false,
    "maxColumn": 0,
    "caseLabels": true,
    "caseContent": true,
    "caseSingleLine": true,
    "caseElseAlignment": "asIs",
    "commentSingleLine": "structural",
    "commentMultiLine": "structural",
    "labelIndent": "bodyLevel"
  },
  "lineBreaks": {
    "keepUser": true,
    "beforeSingleInstr": "asis",
    "beforeBegin": "asis",
    "betweenElseAndIf": false,
    "afterBegin": false,
    "afterSectionKeyword": false,
    "afterLabel": "asis",
    "usesMode": "preserve",
    "lineAfterSemicolon": false,
    "sectionKeywordJoin": "preserve"
  },
  "reshaping": {
    "oneParamPerLineInCalls": false,
    "collapseEndElse": false,
    "collapseEndElseBegin": false,
    "joinMultilineParens": false,
    "arrayInitPerLine": false,
    "inheritancePerLine": false,
    "enumPerLine": false,
    "delimitedGroupStyle": "compact"
  },
  "alignment": {
    "assignments": false,
    "colons": false,
    "consts": false,
    "trailingComments": false,
    "maxPadWidth": 0
  },
  "input": {
    "assumeEncoding": "",
    "fallback": [],
    "heuristic": true
  },
  "output": {
    "lineEnding": "platform",
    "rightMargin": 0,
    "maxEmptyLines": -1,
    "emptyLines": {
      "beforeVisibility": -1,
      "aroundSections": -1,
      "betweenDeclarations": -1,
      "beforeTypeKeyword": -1
    },
    "trimTrailingWhitespace": true,
    "strictMode": true
  },
  "conditional": {
    "mode": "activeBranch",
    "platform": "",
    "compilerVersion": "",
    "defines": [],
    "includePaths": []
  }
}

Safety guarantees

radFormatter is non-destructive. No token is ever lost: it only adds, removes, or replaces whitespace and line endings, and recases keyword, directive, number, and identifier text. Every format is checked two ways — a non-whitespace content hash and a re-lexed token fingerprint. If any rule would alter non-whitespace content, the change is discarded and your original source is returned unchanged.

Appendix: value types

The value types referenced in the Type columns above. Each entry in an option table links back here.

Case mode (casing.keywords, casing.numbers, casing.directives)

ValueEffect
"asis"Leave as written (default)
"lower"lowercase
"upper"UPPERCASE
"title"Title case (first letter upper, rest lower)

Identifier case mode (casing.identifiers)

ValueEffect
"asis"Leave as written (default)
"lower"all lowercase
"upper"all UPPERCASE
"firstOccurrence"Normalize every occurrence to the casing of the first one seen

Spacing option (spacing.assignment, spacing.comma, spacing.colon, spacing.semicolon)

Controls whitespace on each side of a token. String values only.

ValueBefore tokenAfter token
"asis"unchangedunchanged
"none"removeremove
"before"ensure one spaceremove
"after"removeensure one space
"both"ensure one spaceensure one space

Tri-state (yes / no / asis)

Used by the toggle-style spacing options and several line-break options. Accepts a JSON boolean (true = yes, false = no) or a string ("yes", "no", "asis").

ValueEffect
"asis"Preserve the source spacing/break (default for most)
"yes" / trueEnsure the space/break
"no" / falseRemove the space/break

Configuration options as of radFormatter v2.7.986 (Alpha) · config schema 3.5 — see release notes