Statement blocks should be separated by a blank line
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.
| Check | What triggers | Setting |
|---|---|---|
| Blank line before a control-flow block | A multi-line if, case, repeat, while, for, or foreach immediately follows another statement in the same block. | ControlFlowBefore |
| Blank line after a control-flow block | A 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 statement | exit, 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 construct | The 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
casestatement (the check operates on the surrounding block, not on branches) - An
exitorError(...)used directly as athen/elsebranch (the containingifis governed by the control-flow settings) - Any statement immediately after
beginor immediately beforeend(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"
}
}
| Property | Type | Default | Values | Purpose |
|---|---|---|---|---|
ControlFlowBefore | boolean | true | true / false | Require a blank line before a multi-line control-flow block when it follows another statement in the same block. |
ControlFlowAfter | boolean | true | true / false | Require a blank line after a multi-line control-flow block when a non-control-flow statement follows it directly. |
ScopeLeavingMode | string | "ExitAndError" | "Off", "ExitOnly", "ErrorOnly", "ExitAndError" | Which scope-leaving statements require a preceding blank line. |
ElseChainBeforeMode | string | "Off" | "Off", "RequireBlank" | Whether the else keyword of an if ... end else construct must sit on its own after a blank line. |
OneLinerMode | string | "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.