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.

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.

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.