radFormatter / docs / known limitations

Known limitations

Known constraints and deliberate limits for radFormatter v2. Some of these are engineering trade-offs; others are principled decisions about what a layout formatter should and should not do. They are documented here so behavior is predictable rather than surprising.

What the formatter changes — and what it does not

radFormatter is a layout tool. It adjusts whitespace, line breaks, indentation, and casing. It does not change what your code means:

If you are looking for naming-convention enforcement or refactoring, that is outside the scope of a formatter.

AsIs = preserve the source

as-is for any spacing option means do not alter the source spacing — the original inter-token whitespace (including multiple spaces and tabs) is reproduced verbatim. Normalization happens only where an option is set to a non-AsIs value, and only for that option's gap type.

Consequence: at the default profile (every spacing option AsIs) the formatter performs no inter-token spacing changes at all — it only changes casing, indentation, and line breaks per their own options. The opinionated profiles (Embarcadero, FormatterExe) set the spacing options to their idiomatic non-AsIs values.

Tabs and indent style

indent.style extends the same “preserve source by default” stance to the indent character. At the default profile it is asIs: the formatter detects the source's indent character (the first ordinary indented line decides — a leading tab → tabs, a leading space → spaces; none → spaces) and preserves it, rather than silently converting tab-indented source to spaces. Only the character is preserved — indent depth is always recomputed. The opinionated profiles set style: "spaces".

Consequently indent.size is a spaces-only setting: under tabs the tab count is the structural level and the editor's tab stop owns the visual width, so indent.size does not apply. Continuation indent and alignment padding stay spaces even under tabs (“smart tabs”). Right-margin measurement counts a leading-indent tab at its true visual tab-stop width, so soft-break decisions on tab-indented lines use the correct column.

Conditional compilation

Inactive conditional branches are treated as preserve barriers — their content is reproduced untouched. Rich directive formatting is limited to indent (left margin vs structural depth) and casing. Formatting never joins, splits, aligns, or wraps through conditional boundaries.

Conditional code is best-effort in every mode. See the dedicated Conditional compilation guide for the three modes (activeBranch, allBranches, preserveConditionals), the activeBranch build context, and worked examples.

Delphi only (FPC and other dialects)

radFormatter targets Delphi. A source in another dialect — most commonly Free Pascal with {$mode objfpc} / {$modeswitch ...} and constructs Delphi does not have (operator overloading, generic / specialize, and similar) — is not supported. When radFormatter detects such a dialect it leaves the file unformatted and reports it, rather than producing a possibly-wrong best-effort layout. It is treated exactly like a file that cannot be parsed: the CLI honors -onParseError (skip warns, fail exits non-zero) and lists it as skipped (unsupported dialect); the IDE leaves the buffer unchanged and shows why. If a construct is valid newer Delphi that this build's parser does not yet recognize, update radFormatter so the bundled parser catches up.

Right margin scope

Right-margin wrapping works by activating declared soft break points, so coverage depends on which constructs declare them. Currently supported wrap points:

Iterative multi-wrap is supported: after a wrap, indentation is recomputed and over-long lines are re-measured.

Not yet supported for wrapping:

Comment indentation

An own-line comment is aligned to the innermost block of code still open at its position, rather than computing an indent of its own. In practice the formatter matches the comment to a nearby statement in the same block; for an empty block it uses the block's opener plus one indent level; and a comment that is the empty or leading body of an unbraced if / for / while / with branch — sitting directly after then, else, or do — takes that branch's body column. A comment that follows a block's own closing end returns to the enclosing level. Only when none of these apply does it fall back to matching the next non-comment line.

So a comment inside a block indents with the block body, not with the dedenting closer that follows it:

begin
  if AOptions.UsesMode = umOnePerLine then
  begin
    ...
  end;
  // umSingleLine handled below via join logic.   <- block-body level, not the outer 'end'
end;

and a placeholder comment in an otherwise-empty then branch takes the deeper then-body column, not the else:

if (...) then
  // { skip -- option explicitly requests collapsing }   <- then-body column
else
  IsStructural := True;

Residual limitation

One narrow ambiguity remains. A comment sitting between a completed statement and a following dedenting closer (else, end, until, finally) falls back to the next line, so it takes the closer's shallower level rather than the deeper statement it might be trailing:

if X then
  A
  // prose introducing the else   <- 'else' level, not A's deeper level
else
  B;

This is a deliberate default: position alone cannot tell “trailing the statement above” from “introducing the construct below,” and the two intents want opposite indents. Aligning such a comment to the deeper preceding line would break the common case of a comment that heads an else / until / finally, so the predictable next-line default is kept for the cases the block-relative rules cannot resolve.

Alignment

Alignment runs after indent and owns only interior padding. Alignment interacts correctly with right-margin wrapping — alignment is computed before wrapping, and wrapping triggers indent recomputation but not alignment recomputation.

Safety

If the formatter ever produces output that does not match the input token-for-token (aside from the whitespace and casing it is allowed to change), it declines and returns your source unchanged rather than emit a corrupted result. If you see a validation error like this, it is a bug we want reported — your code is safe, but the formatter should have handled it.

Applies to radFormatter v2 (alpha). This list grows and shrinks as the formatter evolves; when in doubt about whether something is a limitation or a bug, report it.