Statement blocks should be separated by a blank line

Properties
FC0007 Info Style Code Fix Ignore Obsolete

Multi-line control-flow blocks (if, case, repeat, while, for, foreach) and scope-leaving statements (exit, built-in Error(...)) read more clearly when they are visually separated from surrounding code by a blank line. The rule reports the exact spot where a separator is missing so it can be inserted with a single keystroke, without touching the logic.

FC0007 is disabled by default and fully configurable. Enable it in your project’s .ruleset.json (or via AL: Configure ruleset) and, optionally, tune the individual checks in alcops.json.

Example

The following procedure runs several if-blocks and an exit back-to-back without visual separation:

codeunit 50100 "Sales Post"
{
    procedure Post(var SalesHeader: Record "Sales Header"): Boolean
    begin
        SalesHeader.TestField("Sell-to Customer No.");
        if SalesHeader.Status <> SalesHeader.Status::Released then begin // Insert a blank line before the block [FC0007]
            SalesHeader.Validate(Status, SalesHeader.Status::Released);
            SalesHeader.Modify(true);
        end;
        if not SalesHeader.Find() then // Insert a blank line after the block [FC0007]
            exit(false);
        if GuiAllowed then // Insert a blank line before the block [FC0007]
            Message('Posted %1', SalesHeader."No.");
        exit(true); // Insert a blank line before scope-leaving statement [FC0007]
    end;
}

Add blank lines so each block stands on its own:

codeunit 50100 "Sales Post"
{
    procedure Post(var SalesHeader: Record "Sales Header"): Boolean
    begin
        SalesHeader.TestField("Sell-to Customer No.");

        if SalesHeader.Status <> SalesHeader.Status::Released then begin
            SalesHeader.Validate(Status, SalesHeader.Status::Released);
            SalesHeader.Modify(true);
        end;

        if not SalesHeader.Find() then
            exit(false);

        if GuiAllowed then
            Message('Posted %1', SalesHeader."No.");

        exit(true);
    end;
}

Checks performed

The rule runs four independent checks. Each check can be toggled or narrowed via alcops.json.

CheckWhat triggersSetting
Blank line before a control-flow blockA multi-line if, case, repeat, while, for, or foreach immediately follows another statement in the same block.ControlFlowBefore
Blank line after a control-flow blockA non-control-flow statement immediately follows the closing end (or until) of a multi-line control-flow block.ControlFlowAfter
Blank line before a scope-leaving statementexit, built-in Error(...), or both follow a sibling statement in the same statement list without a whitespace-only line between them.ScopeLeavingMode
Blank line before else in an if ... end else constructThe closing end and the else keyword sit on adjacent lines instead of being separated by a blank line. Opt-in.ElseChainBeforeMode

Single-line control-flow statements (e.g. if Flag then Message('x'); on one line) are excluded from the before / after checks by default. Include them with OneLinerMode.

What counts as a blank line

Only a truly whitespace-only line satisfies the separator requirement. A line containing a comment or a compiler directive (#region, #pragma, etc.) is treated as regular content:

Message('Start');
// ---- section divider ----
exit;                       // Insert a blank line before scope-leaving statement [FC0007]

A trailing comment on the same line as a statement does not turn that line into a separator, but it also does not affect a real blank line that follows or precedes it:

Message('Start'); // trailing comment

exit;                       // OK: the empty line above still counts as a separator

Exception

  • The symbol is obsolete
  • Adjacent case branches inside a case statement (the check operates on the surrounding block, not on branches)
  • An exit or Error(...) used directly as a then / else branch (the containing if is governed by the control-flow settings)
  • Any statement immediately after begin or immediately before end (the block braces already act as separators)

Configuration

All settings live under a single StatementBlockSpacing object in alcops.json. Any omitted property keeps its default.

{
    "StatementBlockSpacing": {
        "ControlFlowBefore": true,
        "ControlFlowAfter": true,
        "ScopeLeavingMode": "ExitAndError",
        "ElseChainBeforeMode": "Off",
        "OneLinerMode": "None"
    }
}
PropertyTypeDefaultValuesPurpose
ControlFlowBeforebooleantruetrue / falseRequire a blank line before a multi-line control-flow block when it follows another statement in the same block.
ControlFlowAfterbooleantruetrue / falseRequire a blank line after a multi-line control-flow block when a non-control-flow statement follows it directly.
ScopeLeavingModestring"ExitAndError""Off", "ExitOnly", "ErrorOnly", "ExitAndError"Which scope-leaving statements require a preceding blank line.
ElseChainBeforeModestring"Off""Off", "RequireBlank"Whether the else keyword of an if ... end else construct must sit on its own after a blank line.
OneLinerModestring"None""None", "All"Whether single-line control-flow statements participate in the ControlFlowBefore / ControlFlowAfter checks.

Enum values are matched case-insensitively ("off", "OFF", and "Off" are equivalent). Unknown values fall back to the defaults silently.

Enabling only what you need

Turn every check off and re-enable individual ones:

{
    "StatementBlockSpacing": {
        "ControlFlowBefore": true,
        "ControlFlowAfter": false,
        "ScopeLeavingMode": "ErrorOnly",
        "ElseChainBeforeMode": "Off",
        "OneLinerMode": "None"
    }
}

With this configuration the rule flags only the space before a multi-line block and the space before an Error(...) call; exit statements, trailing blanks and one-liners are ignored.

Enforcing blank line before else

Some teams prefer the if ... end else construct to break visually before else:

{
    "StatementBlockSpacing": {
        "ElseChainBeforeMode": "RequireBlank"
    }
}

The check runs only when the whole construct spans multiple lines (single-line if ... else statements are unaffected).

Including one-liners

OneLinerMode = "All" extends ControlFlowBefore / ControlFlowAfter to control-flow statements written entirely on one source line, so this snippet gets flagged too:

Foo();
if Flag then Message('Bar'); // Insert a blank line before the block [FC0007]
Baz();

See also

  • Ruleset files on Microsoft Learn — how to enable disabled-by-default rules.