Skip to content

feat(FC0007): Statement blocks should be separated by a blank line (opt-in) - #424

Open
MODUSCarstenScholling wants to merge 4 commits into
ALCops:mainfrom
MODUSCarstenScholling:dev-cs-statement-blocks-separated
Open

feat(FC0007): Statement blocks should be separated by a blank line (opt-in)#424
MODUSCarstenScholling wants to merge 4 commits into
ALCops:mainfrom
MODUSCarstenScholling:dev-cs-statement-blocks-separated

Conversation

@MODUSCarstenScholling

@MODUSCarstenScholling MODUSCarstenScholling commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

feat(FC0007): Statement blocks should be separated by a blank line (opt-in)

Adds FC0007 — Statement blocks should be separated by a blank line to
ALCops.FormattingCop. Opt-in (disabled by default) and fully configurable via
alcops.json. Closes #.

What it flags

  • Multi-line control-flow blocks (if, case, repeat, while, for,
    foreach) without a blank line before them and/or after their terminating
    end;.
  • Scope-leaving statements: exit, exit(<expr>), and built-in Error(...)
    calls that are not preceded by a blank line.
  • Optionally the end else transition in if … end else … end chains.

Single-line (one-liner) control-flow statements are ignored unless
OneLinerMode is set to All.

Configuration surface

New object in alcops.json:

Property Type Default
ControlFlowBefore bool true
ControlFlowAfter bool true
ScopeLeavingMode Off / ExitOnly / ErrorOnly / ExitAndError ExitAndError
ElseChainBeforeMode Off / RequireBlank Off
OneLinerMode Off / All Off

Enums are real C# types (ALCops.Common.Settings.{Scope,ElseChainBefore,OneLiner}Mode),
serialized case-insensitively via JsonStringEnumConverter on net8+ and
StringEnumConverter on netstandard2.1. Both converter registrations were
added to ALCopsSettingsProvider.

Provider hardening (small, but notable)

ALCopsSettingsProvider.DeserializeSettings now swallows JsonException and
returns defaults. Users with a broken alcops.json (e.g. an unknown enum
value) previously would have crashed the analyzer callback and produced an
AD0001; now the rule simply behaves as if no config file existed. A dedicated
test guards this behavior with a fixture containing "ScopeLeavingMode":"NotAnEnumValue".

Schema parity guard

Settings/alcops.schema.json mirrors the three FC0007 enums. Any drift is
caught by StatementBlockSpacingSchema.cs, a NUnit test template that reflects
over the C# enum values and compares them against the enum arrays in the
schema JSON. Reusable pattern for future enum-typed settings.

Test coverage

  • 18 rule tests (positive/negative × control-flow / scope-leaving / else-chain /
    one-liner / disabled-by-default / malformed-config regression).
  • 3 schema-parity tests (one per enum).
  • 21/21 passing; full solution 1407 tests remain green.

Tests follow a single [Test] + [TestCase] pattern across two parametrized
methods (HasDiagnostic / NoDiagnostic) driven by a named settings
dictionary. The fixture folder (HasDiagnostic/ vs NoDiagnostic/) is
resolved implicitly by LoadFixtureAsync — test signatures stay free of
storage-layout details.

Docs

  • New rule instruction file
    .github/instructions/fc0007-statement-blocks-separated-by-blank-line.instructions.md
    with design decisions, test summary counts, known issues, and a Phase-2
    roadmap (CaseBranchMode, GuardClauseMode, LoopControlBeforeMode,
    TreatCommentOnlyLinesAsSeparator, SkipDirectiveBoundaries).
  • common-library.instructions.md updated for the new StatementBlockSpacing
    setting and the "add a new setting" enum-converter walkthrough.
  • Site page will be added in a follow-up to
    alcops.dev
    at content/docs/analyzers/formattingcop/fc0007.md.

Not in this PR (roadmap)

  • CodeFix (comment-attachment edge cases need care).
  • Additional spacing axes listed above.

Implements #423

- Replace WellKnownFixAllProviders.BatchFixer with a custom
  FixAllProvider.Create(FixAllAsync) that removes all matching
  ParameterSyntax nodes in one root.RemoveNodes pass. BatchFixer's
  ReplaceNode(parameterList, …) merge dropped all but one edit when
  multiple parameters in the same signature were flagged.
- Split registration into three CodeActions with distinct EquivalenceKeys
  (.All, .RegularProcedure, .EventSubscriber). FixAllAsync reads
  CodeActionEquivalenceKey to scope the batch; single-fix path uses
  RemoveNode(KeepNoTrivia) so trivia handling matches FixAll.
- Guard fixAllSpans with !IsDefaultOrEmpty and fall back to
  GetDocumentDiagnosticsAsync — the AL SDK reports HasValue=true with
  an empty array under document-scope FixAll.
- Add multiline HasFix fixture and three HasFixAll fixtures
  (single-method dual removal, multi-method, event subscriber). All
  25 ParameterNotReferenced tests pass.
- Update codefix-development, testing, and LC0095 instruction files:
  document the custom FixAllProvider exception, the HasFixAll test
  pattern, new design decisions, and known-issues entry.
…ne" (opt-in)

Introduce FC0007, a disabled-by-default formatting rule that enforces
blank-line separation around multi-line control-flow blocks (if, case,
repeat, while, for, foreach), before scope-leaving statements (exit,
built-in Error), and optionally before the else keyword of if/end-else
constructs.

Configurable via alcops.json under a new StatementBlockSpacing object
with five properties: ControlFlowBefore, ControlFlowAfter,
ScopeLeavingMode, ElseChainBeforeMode, and OneLinerMode. Three are real
C# enums serialized as case-insensitive strings via
JsonStringEnumConverter (net8+) and StringEnumConverter (netstandard2.1).

Also:
- Colocate the new settings type in Settings/StatementBlockSpacingSettings.cs
- Harden ALCopsSettingsProvider: malformed alcops.json now silently
  falls back to defaults (JsonException swallowed in DeserializeSettings)
- Add reusable schema-parity guard test template
  (StatementBlockSpacingSchema) against alcops.schema.json
- 21 tests (18 rule + 3 schema-parity), all green

No CodeFix. Additional spacing axes are captured on the FC0007 Roadmap.
- Detach StatementBlocksSeparatedByBlankLine from the exception-harness
  bridge back to plain DiagnosticAnalyzer. Matches the current policy in
  analyzer-exception-harness.instructions.md — no production analyzer
  adopts the harness until issue ALCops#389 (AL1003 loader bug) is resolved.
- Unify test class on a single [Test]+[TestCase] pattern: two
  parametrized methods (HasDiagnostic / NoDiagnostic) driven by a named
  settings dictionary. Drop the fixtureKind parameter — LoadFixtureAsync
  resolves the folder from the (globally unique) fixture name, removing
  the "HasDiagnostic" string leaking into NoDiagnostic test cases.
- Make StatementBlockSpacingSchema inherit NavCodeAnalysisBase per
  testing convention (mirrors NamingPatternSettings).
- Sync common-library.instructions.md with reality: remove documentation
  for the removed GetSettings(string?) legacy overload and non-existent
  ClearCache() method.
- Drop a stray blank line in FormattingCop DiagnosticDescriptors.
Collection expressions on ImmutableArray<T> require the CollectionBuilder
attribute, which is only present in System.Collections.Immutable v6+ (net8+).
The netstandard2.1 target pulls v5.0.0 and therefore fails with CS9210.

Replace `[]` and `[.. src]` in GetSiblingStatements with
ImmutableArray<T>.Empty and .ToImmutableArray() respectively.

Local build (net8 only) missed this; CI covers all three TFMs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant