Skip to content

Unify and automate code style, formatting and member ordering across the repository #21

Description

@rent-a-developer

Goal

Code style, formatting and the ordering of types/type members should be enforced and fixed
automatically, so that none of the three ever needs a human decision or a review comment again.

Scope is the whole repository: src/, tests/ and benchmarks/. Today only src/ has an
analyzer gate, and ordering is not enforced anywhere.

Decisions

  • C# keywords, not BCL type namesstring, int, bool, not String, Int32, Boolean.
    This reverses the current rule and touches 218 of 283 .cs files.
  • this. prefix stays. No _camelCase for fields or anything else. This becomes enforced
    rather than convention. Primary constructor parameters are accessed without this. — they are
    parameters in C#, not instance members, so the rule does not apply to them.
  • Expression-bodied members wherever possible (already the rule, stays).
  • Primary constructors wherever possible. Currently broken: .editorconfig asks for them via
    csharp_style_prefer_primary_constructors but disables IDE0290, which is that same rule.
    About 37 files have the classic "assign every parameter to a field" constructor.
  • Member order is StyleCop's order (SA1201/SA1202/SA1203/SA1204/SA1214), plus alphabetical
    sorting inside each group
    .
  • Violations break the build, not only CI.
  • Branch names follow Conventional Branch.

Architecture — three concerns, three tools, no overlap

Concern Tool Config
Formatting (whitespace, line breaks, wrapping) CSharpier .editorconfig / .csharpierrc
Code style (semantic) Roslyn analyzers (current set) .editorconfig
Ordering — fixing ReSharper file layout DbConnectionPlus.slnx.DotSettings
Ordering — checking NewStyleCop.Analyzers stylecop.json

Each tool owns its concern completely and is switched off inside the others'.

Why NewStyleCop.Analyzers and not StyleCop.Analyzers: upstream's last release is
1.2.0-beta.556 from December 2023 and predates the C# syntax this repo writes. The fork is at
1.2.1 (stable, 2025-11-29) with 1.3.0-alpha.2 from 2026-08-21, and explicitly added C# 14
extension declarations, collection expressions in SA1413/SA1137, and Roslyn 5.0.0. Same diagnostic
IDs and same stylecop.json, so switching back later is a one-line package change. The risk is
bus factor — a single maintainer — which the one-line exit cost makes acceptable.

Neither package can fix ordering. In both, ElementOrderCodeFixProvider carries
[NoCodeFix("Disabled until stable")] and has no [ExportCodeFixProvider], so Roslyn never
registers it. StyleCop detects, ReSharper fixes. That split is deliberate, not a workaround.

Where each tool runs

When Runs Tool
AI agent edits a .cs file (PostToolUse hook) format csharpier format <file>
Rider save format CSharpier plugin, "Run on Save"
Rider Code Cleanup reorder ReSharper file layout profile
scripts/tidy-cs.ps1 style fixes → format dotnet format stylecsharpier
scripts/tidy-cs.ps1 -All / preflight.ps1 + reorder jb cleanupcode, reorder-only profile
Build (all projects) style + ordering violations Roslyn analyzers + NewStyleCop
CI lint job verify all three csharpier check; dotnet format --verify-no-changes; cleanupcode + git diff --exit-code

AI agents must run formatting, styling and reordering before every commit. preflight.ps1 is the
single entry point for that.

Work items

1. .editorconfig cleanup

  • Add root = true — without it, editors keep searching parent directories outside the repo.
  • Add a [*] section: charset, indent_style, insert_final_newline,
    trim_trailing_whitespace.
  • Add sections for [*.{csproj,props,targets}] (tabs), [*.{json,yml,yaml}], [*.md].
  • Remove the UTF-8 BOM from .editorconfig itself.
  • Group related settings, order them meaningfully, add explaining comments.
  • dotnet_style_predefined_type_for_*true:error (C# keywords).
  • dotnet_style_qualification_for_field/property/method/eventtrue:error. These are not
    set at all today, so this. is currently convention-only.
  • Remove the IDE0290 suppression so primary constructors are actually enforced.
  • dotnet_diagnostic.IDE0055.severity = none — conflicts with CSharpier.
  • Disable the StyleCop rules that conflict with or duplicate CSharpier
    (SA1000-SA1028, SA1102-SA1118, SA1127-SA1137, SA1413, SA1500-SA1518 range — see CSharpier's
    "Integrating with Linters" doc for the exact list).
  • Enable the StyleCop rules we actually want: the ordering rules, plus SA1101 (this. prefix)
    and SA1309 (no _ field prefix), which match our decisions.
  • Re-check RCS1037 (trailing whitespace), currently none.
  • Do not set end_of_line for .cs.gitattributes owns line endings, and a fixed value
    would fight it on Windows. CSharpier's endOfLine: auto default is correct here.

2. Analyzers for tests and benchmarks

  • Extend the analyzer and style gate to tests/ and benchmarks/, which set neither
    EnforceCodeStyleInBuild nor TreatWarningsAsErrors today.
  • Decide per rule what does not apply there (e.g. the XML documentation rules — the
    benchmarks already switch RCS1181 off for that reason).

3. CSharpier

  • Add csharpier to .config/dotnet-tools.json.
  • Add .csharpierignore; confirm max_line_length = 120 is picked up from .editorconfig
    so there is one source of truth for the line width.
  • Add CSharpier.MsBuild so an unformatted file breaks the build.
  • Replace dotnet format whitespace with csharpier check in the CI lint job.
  • Rider: CSharpier plugin, "Run on Save".

4. Ordering

  • Rename DbConnectionPlus.sln.DotSettingsDbConnectionPlus.slnx.DotSettings. The current
    name is almost certainly not loaded at all, because the solution is .slnx. Symptom to
    check: Rider still flags the spell-check exceptions in that file as typos.
  • Define the ReSharper file layout: StyleCop order, alphabetical inside each group.
  • Create a reorder-only cleanup profile. ReSharper's own reformatting must stay off —
    that is CSharpier's job.
  • Add JetBrains.ReSharper.GlobalTools to .config/dotnet-tools.json.
  • Add NewStyleCop.Analyzers with a stylecop.json whose elementOrder matches the
    ReSharper layout. PrivateAssets=all, like the other analyzer packages.
  • CI: cleanupcode + git diff --exit-code.

5. Branch naming — adopt Conventional Branch

  • Adopt Conventional Branch as the repository's branch
    naming convention. It is the natural counterpart to the Conventional Commits this repo
    already uses, and it covers cases the current rule does not — this issue itself is neither a
    feature nor a bugfix.
  • Replace the rule in CONTRIBUTING.md:10, which currently allows only
    feature/<issue#>-<slug> and bugfix/<issue#>-<slug>, with the Conventional Branch format
    <type>/<description> and the ticket-number form <type>/issue-<issue#>-<slug>.
  • Document the allowed types: feature/, bugfix/, hotfix/, release/, chore/.
  • Document the AI agent prefixes claude/ and codex/ (the spec also lists ai/,
    copilot/, cursor/) and decide when an agent should use them instead of a purpose
    prefix. This repo is agent-heavy, so the answer should be explicit rather than implied.
  • Document the character rules: lowercase a-z, digits, hyphens as word separators; no
    underscores, spaces or special characters; no consecutive hyphens and none at the start or
    end of a description.
  • Mention it in AGENTS.md too, so Claude Code and Codex pick the right prefix without being
    told each time.

6. Documentation and scripts

  • Rename scripts/format-cs.ps1scripts/tidy-cs.ps1; it now does style, formatting and
    ordering. Add a -Check mode for CI and an -All scope.
  • Update both hook wrappers (.claude/hooks/, .codex/hooks/).
  • Wire the ordering step into preflight.ps1.
  • Update AGENTS.md (code style section), CONTRIBUTING.md, .agents/README.md.
  • Write the member order into AGENTS.md in plain prose, so agents place new members
    correctly in the first place instead of relying on the fixer.
  • Document in AGENTS.md that primary constructor parameters are accessed without this.,
    and that this is not an exception to the this. rule — they are parameters in C#, not
    instance members.

7. The four mechanical commits

Each rewrites lines across most of the repository without changing meaning. Keep them separate,
in this order, with the full unit suite after each one. Each uses a different tool, so a bad
result is reverted on its own instead of being untangled from the others.

  • 1. Stringstring and friends — dotnet format style (IDE0049). ~218 files.
  • 2. Convert to primary constructors — dotnet format style (IDE0290). ~37 files.
  • 3. CSharpier reformat — all 283 files.
  • 4. Reorder members — jb cleanupcode, all 283 files.
  • Add .git-blame-ignore-revs listing all four, and document
    git config blame.ignoreRevsFile .git-blame-ignore-revs in CONTRIBUTING.md.
    GitHub reads the file automatically.
  • Check PublicAPI.*.txt for churn after commits 1 and 2.

Risks

  • Field initializer order. Commit 4 moves fields, which changes initialization order and can
    change behavior. This is the only part of the four that is not purely cosmetic. Review that diff
    for fields whose initializer references another field, and for static readonly chains.
  • Four whole-repo diffs. Land them while nothing else is open, and run the tests after each.
  • Alphabetical order inside a group is not checked by StyleCop. Its elementOrder only knows
    kind, accessibility, const, static and readonly. Alphabetical sorting is a ReSharper-only
    feature. So a member in the wrong alphabetical position is silently fixed by the tidy script and
    by Rider, but does not break the build. Accepted.
  • NewStyleCop.Analyzers has one maintainer. Mitigated by identical rule IDs and config, which
    makes switching back to upstream a one-line change.
  • jb cleanupcode and .slnx: profile lookup was broken and fixed on 2025-12-20
    (RSRP-502346). Needs verifying against the current 2026.x tool.
  • Two tools on save in Rider (CSharpier plugin and ReSharper cleanup) can fight. Mitigation:
    on save = formatting only; reordering is a deliberate action plus preflight.ps1.
  • cleanupcode is slow — it loads and builds the solution, so minutes rather than seconds.
    It cannot be a per-edit agent hook; it belongs in the full tidy run, in preflight and in CI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions