# StyleBro
> Roslyn analyzers and code fixes for C#, a modern StyleCop replacement that dotnet format fixes.
---
Source: https://bisforboman.github.io/stylebro/
# StyleBro
Roslyn analyzers and code fixes that keep C# code tidy. It's a modern alternative to StyleCop, built so that
**`dotnet format` fixes everything it reports**.
> StyleCop writes you a ticket. StyleBro just fixes it. Rule IDs use the `BRO` prefix.
- **Every rule has a code fix, and Fix All works.** Fixes are written for `dotnet format`: deterministic, idempotent,
and they converge in a single pass. One exception, on purpose: a rename that would break code only the fix can see
(a name read by reflection, used in generated code) keeps its warning, and `stylebro-migrate format` lists it with
the reason ([Findings kept on purpose](getting-started.md#findings-kept-on-purpose)).
- **Don't duplicate the SDK.** If a built-in `IDE` rule already covers a StyleCop rule, StyleBro uses that rule instead
of shipping a copy: the preset sets its options and `stylebro-migrate init` turns it on in `.editorconfig`.
- **Configurable through `.editorconfig`**, with a recommended preset shipped in the package.
## Install
```xml
```
```
dotnet tool install --global StyleBro.Migrate --prerelease
stylebro-migrate init --write # turns on the built-in rules StyleBro relies on, in .editorconfig
stylebro-migrate format # whitespace + StyleBro's rules + those built-in rules
```
Coming from StyleCop? Run `stylebro-migrate --write` instead of `init`: see
[Migrating from StyleCop](migrating.md).
## Where next
- [Getting started](getting-started.md): from install to the first `dotnet format` run.
- [Rules](rules/README.md): every rule, by area, with what it replaces and whether it's on by default.
- [Configuration](configuration.md): every `stylebro_*` setting.
- [Baseline](baseline.md): fail only on new violations. [CI](ci.md): checking it in a pipeline.
- [StyleBro vs StyleCop](stylebro-vs-stylecop.md): coverage, speed, correctness and migration, with measured numbers.
- Source, issues and releases: [github.com/bisforboman/stylebro](https://github.com/bisforboman/stylebro).
---
Source: https://bisforboman.github.io/stylebro/getting-started/
# Getting started
From install to the first `dotnet format` run. Pick the path that fits:
- [Preview first](#preview-first): what either path would change, without changing anything
- [A. A new project, or no StyleCop](#a-a-new-project-or-no-stylecop)
- [B. Coming from StyleCop](#b-coming-from-stylecop)
Both use two packages: **StyleBro.Analyzers** (the rules, referenced by your projects) and **StyleBro.Migrate** (the
`stylebro-migrate` command line tool, used once to write settings):
### Which version
The latest release on nuget.org is **0.7.0-alpha.1**, a prerelease: install the tool with `--prerelease` and reference
that exact version. This page describes `main`, which matched that release when it shipped; what changed since then
ships in the next one and is listed in [changes/](https://github.com/bisforboman/stylebro/tree/main/changes).
To try the code on `main` before the next release, build both
packages from a clone into a local folder (any version string works; `0.8.0-dev.1` here):
```
git clone https://github.com/bisforboman/stylebro
dotnet pack stylebro/src/StyleBro.Package -o stylebro-feed -p:Version=0.8.0-dev.1
dotnet pack stylebro/src/StyleBro.Migrate -o stylebro-feed -p:Version=0.8.0-dev.1
dotnet tool install --global StyleBro.Migrate --add-source stylebro-feed --version 0.8.0-dev.1
```
Then add the folder as a package source in your repository's `nuget.config` and use `0.8.0-dev.1` wherever this page
says `0.7.0-alpha.1`. When the `nuget.config` has a ``, the new source needs a mapping too: NuGet
only looks for a package in the sources mapped to it, so the source alone gives NU1102 (package not found):
```xml
```
## Preview first
Before changing anything, `--diff` shows what a path would do to your code. It works on all three commands:
```
stylebro-migrate init --diff # path A: init's settings, then format
stylebro-migrate path/to/repo --diff # path B: the migration's settings (--write), then format
stylebro-migrate format --diff # only the format run
```
The preview copies the repository to a temporary folder (git's tracked and untracked files, not the ignored ones; the
working trees of submodules too), runs the command there, then `stylebro-migrate format` until a run changes nothing,
and deletes the copy. Your repository isn't touched. If it doesn't reference StyleBro.Analyzers yet, the copy gets the
reference from [step 1](#1-add-the-package) (the tool's own version) and the output says so. It prints a summary and
writes the full diff to `.git/stylebro-preview.patch` in the repository's git folder, where it isn't an untracked file
(outside a git repository: the temp folder; `--diff=other.patch` names another file).
`--keep` keeps the copy and prints where it is; `--all` passes on to format; with several solutions or projects in the
folder, `--project MySolution.sln` names the one to format. `--no-restore` skips the restore in the copy: it takes the
repository's own restore output, so run `dotnet restore` first (it's ignored when the preview adds the package, which
needs a restore). Where the preview adds the reference under central package management: next to StyleCop's (a
`GlobalPackageReference` gets a `GlobalPackageReference`, in the same `Directory.Packages.props`), else a version-less
`PackageReference` with the version in the `Directory.Packages.props` that applies (also one that a folder's
`Directory.Packages.props` imports). FFMpegCore, `stylebro-migrate init --diff`:
```text
Previewing 'stylebro-migrate init --write' and 'stylebro-migrate format' on a copy; nothing in C:\src\FFMpegCore changes.
Copying the repository to C:\Users\me\AppData\Local\Temp\stylebro-preview-fmsw0dps.3qa\FFMpegCore... 0s
Settings: 31 lines in .editorconfig; 3 lines in Directory.Build.props (StyleBro.Analyzers)
StyleBro.Analyzers wasn't referenced: added StyleBro.Analyzers 0.7.0-alpha.1 to Directory.Build.props for the preview, as docs/getting-started.md says (the patch includes it).
Finding what format fixes ('dotnet format --verify-no-changes')... 16s
Format run 1... 29s
Format run 2... 14s
Format: 124 files, 579 reported changes, clean after 1 run
BRO1603 Documentation text should end with a ... 65 files
BRO1001 Members should be ordered 33 files
BRO1401 Use a trailing comma in multi-line in... 33 files
...
... and 24 more rules
Sample (BRO1603, FFMpegCore.Extensions.Downloader/Exceptions/FFMpegDownloaderException.cs):
-/// Custom exception for FFMpegDownloader
+/// Custom exception for FFMpegDownloader.
Sample (BRO1401, FFMpegCore.Examples/Program.cs):
- FrameRate = 30 //set source frame rate
+ FrameRate = 30, // set source frame rate
Full diff: .git\stylebro-preview.patch (5,966 lines)
Done in 1m 2s.
```
Files are counted under every rule `dotnet format --verify-no-changes` reported in them before the fixes; a file with
only whitespace changes counts as IDE0055, one with nothing reported (another fix's side effect) as `other`. The patch
is what the real commands write (byte for byte on FFMpegCore); `git apply .git/stylebro-preview.patch` applies it.
## A. A new project, or no StyleCop
### 1. Add the package
Add it to every project, for example in `Directory.Build.props` at the repository root:
```xml
```
With central package management, the version goes in `Directory.Packages.props` and the reference has none:
```xml
```
A project that turns central package management off itself (`false`,
often an analyzer or source generator project) fails its restore with the version-less reference (NU1015). Give such
projects a reference with a version, next to the version-less one:
```xml
```
MSBuild imports only the nearest `Directory.Build.props` above a project: if a folder (say `src/`) has its own and
it doesn't import the root one, put the reference there (or in each such file). If it does import it (`GetPathOfFileAbove`,
or a property like Orleans' `$(_ParentDirectoryBuildPropsPath)`), the root's reference is enough: a second one fails the
restore (NU1504, duplicate reference). A reference inside a conditional
`ItemGroup` (`Condition="'$(TargetFramework)' == 'net8.0'"`) reaches only that target framework, and
`false` turns all analyzers off where it applies. `--diff` adds the reference to every
`Directory.Build.props` the projects use and names the projects whose restore output has no StyleBro.Analyzers (a
code base that already follows every rule reports nothing, and that's fine).
With lock files (`true`), the next restore adds StyleBro.Analyzers
to every `packages.lock.json`: commit them with the reference. Where `true` is on
(always, or in CI), that restore fails instead (NU1004, "the package references have changed"): update the lock files
once with locked mode off, `dotnet restore -p:RestoreLockedMode=false`, and commit them. `--diff` does this in its copy
by itself, and its patch shows the updated lock files.
Two things that get in the way of a package from a local feed (trying a build before it's on nuget.org):
- A `` property (often in `Directory.Build.props`) replaces the sources of every `nuget.config`: a feed
added there is never asked, and the restore fails with NU1101/NU1102. Add the feed to `RestoreSources` too
(`$(RestoreSources);C:\feed`), or use `RestoreAdditionalProjectSources`.
- Projects a test run generates inside the repository (say under `artifacts/` or a test's temp folder) inherit the root
`Directory.Build.props`, and with it the StyleBro reference, which their own restore may not find. Leave them out with a
condition on the reference, e.g. `Condition="!$(MSBuildProjectDirectory.Contains('artifacts'))"`, or give that folder a
`Directory.Build.props` of its own that doesn't import the root one.
The package brings StyleBro's rules and its preset: rule severities and formatting options, close to StyleCop's
defaults. Your own `.editorconfig` wins over the preset. The preset has a version, and `stylebro-migrate init --write`
(step 2) pins it in `Directory.Build.props` (`1.0`), so a later package upgrade adds no
new rules until you raise it ([versioning](versioning.md#new-rules)). To configure everything yourself instead:
`none`.
### 2. Turn on the built-in rules
StyleBro relies on some built-in .NET rules (IDE0055 formatting, IDE0036 modifier order, IDE0049 type aliases, ...).
Their severities have to be in `.editorconfig`, because `dotnet format` ignores rule severities in a package's preset.
In the repository root:
```
stylebro-migrate init # shows the block
stylebro-migrate init --write # adds it to .editorconfig (created if missing)
```
`stylebro-migrate init --diff` shows what the block and the format run in step 3 would change, without writing
anything ([Preview first](#preview-first)).
The block sits between `# BEGIN stylebro-migrate` and `# END stylebro-migrate`; running the command again replaces it.
Put your own settings outside it. To get these rules reported by the build too, add
`true` to `Directory.Build.props` (`init` says so when no project
file sets it yet).
An `.editorconfig` that silences analyzers in bulk (`dotnet_analyzer_diagnostic.severity = silent`, or a category's
severity below warning) would hide StyleBro's rules from `dotnet format`, while the build still reports them at the
preset's severities. `init` then writes each rule the preset turns on by its id, so both see the same rules; a rule
your `.editorconfig` sets by id stays as you set it. `stylebro-migrate format` never says clean while such a bulk setting
hides rules from it.
A folder whose own `.editorconfig` turns every analyzer off (`dotnet_analyzer_diagnostic.severity = none` or `silent`,
with or without `root = true`; test samples, for example) is left alone instead: `init` writes StyleBro's rules, IDE0055
and the other built-in rules it manages as `none` by id there (a rule's own entry wins over the bulk one, in the
preset as in the root block), so the build reports nothing there and `format` doesn't touch those files, not even their
whitespace. A rule that folder's `.editorconfig` sets by id stays as you set it. These lines go into a block between
`# BEGIN stylebro-folder` and `# END stylebro-folder` (in a `root = true` folder: its `stylebro-migrate` block, with
nothing else in it). Your own IDE0055 severity (say `suggestion`) is kept everywhere: the block doesn't set it then, and
`format` still formats whitespace, except where your severity is `none` (by id, for the Style category or in bulk):
`format` leaves the whitespace of those files alone, as it does where a block turns IDE0055 off.
#### init also looks at your code
`init` looks at your code first and keeps the conventions it clearly follows. For each setting below it counts both
forms in your C# (generated code, EF Core migrations, vendored folders and submodules don't count; code inside `#if`
counts too, as `format` sees it in the target framework that defines the symbol):
Vendored folders are recognized by their name (`vendor`, `third_party`, ...). Code copied in under another name (a
`src/Sdk` from another product) counts as yours and can decide a convention; mark its folder `generated_code = true` in
your root `.editorconfig` (`[src/Sdk/**]`, see [excluding vendored code](#excluding-vendored-code)) and run `init` again:
folders marked that way don't count.
- **fewer than 3 places** (or none): too few to tell, StyleBro's default stays and nothing is written;
- **3 to 9 places**: followed when they all agree;
- **10 places or more**: followed when one form has at least 75 %;
- **mixed** (no form gets there): the StyleBro rule that enforces the setting is turned off, so the code stays as it is
(`off`: `dotnet_diagnostic.BRO1520.severity = none`; `both`: for braces on one-line bodies `csharp_prefer_braces =
when_multiline`, which allows both). Where no StyleBro rule enforces the setting (`{` placement, ``
spacing, `default(T)`), the verdict is `mixed`: the default stays and nothing is turned off.
A followed form that is StyleBro's default needs no line; any other is written into the block. A key your root
`.editorconfig` sets wins, so does a Sonar setup's, and a `dotnet_naming_rule` for private fields decides the field style
(naming rules for interfaces or constants don't). One exception: when your `.editorconfig` sets an SDK key that only a
StyleBro rule enforces (operator placement, braces = `true`, using placement, arithmetic parentheses =
`always_for_clarity`) and your code clearly does the other thing, nothing ever enforced that key. Then `init` leaves the
key alone and turns that rule off (`your .editorconfig says end_of_line, ... so BRO1520 is off`), instead of letting it
rewrite the code. The same goes when the code mixes both forms under such a key (the starter kit says
`beginning_of_line`, 486 operators start the line and 285 end it): nothing enforced the key, so the rule goes off as for
any mixed convention (`.editorconfig says beginning_of_line, unenforced, code mixed: BRO1520 is off`). Running `init`
again keeps such a line, even when `format` changed the counts.
A key your `.editorconfig` sets at severity `silent` or `none` (`csharp_prefer_braces = true:silent`, or
`dotnet_diagnostic.IDE0011.severity = silent` next to it) isn't enforced by anything: it's what the IDE wrote, not what the
team checks. `init` treats it like a key that isn't set and follows the code; where the code's form differs from the key,
it writes the code's form into the block, which comes later in the file and wins (also when that form is StyleBro's
default). This applies to every convention whose key takes a severity: braces (IDE0011), using placement (IDE0065),
arithmetic parentheses (IDE0048), default values (IDE0034) and operator placement.
**Folders with their own `root = true` `.editorconfig`** (a `sample/` with its own solution): the root's settings never
reach their files, since `root = true` stops the lookup there. `init` gives each such folder with C# code a block of its
own in its `.editorconfig`, with the built-in rule severities, judged from that folder's code and settings alone (the
report has a section per folder). A folder with too little code to tell keeps StyleBro's defaults, and the report says so.
`stylebro-migrate --write` writes the whole block into such a folder too.
A few settings work a little differently:
- **One-line statements** (`if (a) return;`, `case 1: a(); break;`): the preset sets
`csharp_preserve_single_line_statements = false`, which moves each to a line of its own. From 3 such statements on,
`init` writes `true`, majority or not, unless your `.editorconfig` sets the key.
- **`` on overrides and implementations** ([BRO1601](rules/BRO1601.md)): in projects that generate
documentation, `init` counts the overrides and interface implementations BRO1601 checks, with and without a doc
comment. Mostly documented: the rule stays on; mostly undocumented or mixed: it's turned off.
- **Line endings** count files. Without `end_of_line`, `format` writes your OS's line ending into the lines it rewrites
(on Windows CRLF into LF files), so `init` writes the files' ending when it's clear and nothing when it's mixed. Files
git converts on checkout (`core.autocrlf`, or a `.gitattributes` `text` attribute without `eol`: LF in the repository,
CRLF in a Windows checkout) don't count, and the report names which of the two converts them: there the OS's ending is
what the checkout has, and a fixed one would make `format` rewrite every file on the other OS. When git converts at
least as many files as it leaves alone, those are the convention and nothing is written: a few files committed with
CRLF don't decide for the rest (one trial repository had 3 such files next to 145 converted ones, and
`end_of_line = crlf` from those 3 meant 12,593 line-ending errors in an LF checkout). `stylebro-migrate --write`
writes `end_of_line` the same way. When your `.editorconfig` sets `end_of_line` but git converts the files on
checkout (`end_of_line = lf` with `*.cs text=auto` means CRLF files in a Windows checkout), `init` keeps endings out of
it: `end_of_line = unset` for C# files and IDE0055 as a suggestion, so neither `format` nor the build touches them, and
the report says how to settle it (`*.cs text eol=lf` in `.gitattributes`, or removing `end_of_line`). When your
`end_of_line` says one ending but the files git leaves alone clearly have the other (`crlf`, with nearly every file LF),
nothing ever enforced it: `init` writes the files' ending for C# files, so `format` doesn't flip every file it touches,
and the report says how to switch to the key's ending instead.
- **`s_`/`t_` prefixes** count private static fields (constants aside) with and without the .NET runtime's `s_` (or `t_`)
prefix. Mostly without: [BRO1307](rules/BRO1307.md) stays on; mostly with or mixed: it's turned off, so `format` doesn't
rename them (there is no switch for static fields alone, so `m_` fields keep their prefix too). A `dotnet_naming_rule`
of yours that requires `s_` decides instead.
- **Type names** count files: files that name built-in types only with keywords (`string`, `int`), and files with a
framework name IDE0049 would rewrite (`String`, `System.Int32`). Mostly keywords: IDE0049 stays on; mostly framework
names or mixed: it's turned off. Your own `dotnet_style_predefined_type_*` keys decide instead.
- **Member order** ([BRO1001](rules/BRO1001.md)) counts types whose members BRO1001 would sort apart (two or more
members with different sort keys), with your own order settings: already in its order or not. A type that is in order
once overloads stay together counts as sorted (whether they do is the overloads convention's call). Mostly sorted: the
rule stays on; mostly unsorted or mixed: it's turned off, so `format` doesn't reorder code that was never sorted
(StabilityMatrix: 370 sorted, 414 not). Only type members count: BRO1001 also sorts the types of a namespace, but most
files hold one type, so they would say little.
- **Unsigned literal suffixes** ([BRO1135](rules/BRO1135.md)) count `u`/`ul` suffixes in upper or lower case (`0U` or
`0u`); `L` alone doesn't count, since nobody writes `1l`. Mostly upper case: the rule stays on; mostly lower case or
mixed: it's turned off (Orleans: 423 upper, 185 lower). With it off, BRO1122 writes `6u`/`1ul` instead of `6U`/`1UL`.
- **Items of split lists** ([BRO1108](rules/BRO1108.md)) count argument and parameter lists whose items start on more
than one line: each item on its own line, or grouped (`F(tag, 1,` and `"a", "b")` on the next line). Mostly one per
line: the rule stays on; mostly grouped or mixed: it's turned off (metadata-extractor: 104 one per line, 197 grouped).
- **Implicit `private`** ([BRO1404](rules/BRO1404.md)) counts the members of classes, structs and records that are private
when nothing says otherwise (fields, methods, properties, events, constructors, nested types; not what BRO1404 skips:
partial members, explicit implementations, static constructors): with `private` or without an access modifier. Mostly
with: the default stays; mostly without: `dotnet_style_require_accessibility_modifiers = omit_if_default`, so neither
BRO1404 nor [BRO1007](rules/BRO1007.md) adds modifiers (IDE0040, which would remove them, is off in `init`'s block);
mixed: BRO1404 is turned off. Interface members are counted apart: when the `.editorconfig` says `always` (which asks
for `public` on interface members too), the build doesn't enforce it (no `EnforceCodeStyleInBuild`), and the code's
interface members are mostly without an access modifier or mixed, `init` writes `for_non_interface_members`, so
BRO1404 adds no `public` there (BaGetter: none of its 102 interface members had one).
- **Implicit `internal`** ([BRO1404](rules/BRO1404.md)) counts the types in a namespace or a file (not partial ones):
with `internal` or without an access modifier. No setting asks for modifiers on types only, so mostly without, or
mixed, turns BRO1404 off (AngleSharp: 273 types without, 26 with `internal`).
- **`case` blocks** count blocks below a `case` label (`case X:`, then `{` on the next line): indented one level below
the label or at its level. Mostly at its level: `csharp_indent_case_contents_when_block = false`, so the SDK
formatter (IDE0055) keeps them (AngleSharp: all 131 at the label's level).
- **Combined declarations** ([BRO1142](rules/BRO1142.md) for locals, [BRO1114](rules/BRO1114.md) for fields) count
declarations of two or more (`int n = h.Length, i = 0;`) and one-variable declarations right after one of the same type
and modifiers they could have been combined with (not `var`, not fields with attributes). Mostly one by one: the rule
stays on; mostly combined or mixed: it's turned off, so `format` doesn't split them. Locals and fields are counted apart:
a repository may combine locals in hot loops and never fields.
- **Combined attributes** ([BRO1102](rules/BRO1102.md)) count declarations with two or more attributes (not parameters
or type parameters): in one list (`[A, B]`) or one per list. Mostly one per list: the rule stays on; mostly combined or
mixed: it's turned off, so `format` doesn't split them.
- **Summary wording** ([BRO1604](rules/BRO1604.md) for properties, [BRO1606](rules/BRO1606.md) for constructors) counts
summaries that start the StyleCop way (`Gets ...`, `Gets or sets ...`; `Initializes a new instance of ...`) against
ones in their own words (`The samples, ...`, `Whether ...`; `Opens a file ...`). Mostly the StyleCop way: the rule
stays on; mostly their own words or mixed: it's turned off, so `format` doesn't reword public documentation. A summary
that starts with an element (``) isn't counted.
- **CSharpier**: when the repository uses [CSharpier](https://csharpier.com) (a `.csharpierrc`, `.csharpierrc.json`,
`.csharpierrc.yaml` or `.csharpierrc.yml`, or a `csharpier` tool in `.config/dotnet-tools.json` or
`dotnet-tools.json`, at the root or in a solution's or project's folder or a folder above it; or a `CSharpier.MsBuild`
package reference), the two formatters would undo each other's layout
(StabilityMatrix's pre-commit check rejected 161 files StyleBro had formatted). `init` then turns off IDE0055 (so
`stylebro-migrate format` skips the whitespace pass; when CSharpier formats only some folders, IDE0055 is off in those
folders' sections, one section per folder CSharpier formats, and the whitespace pass leaves just them out) and every StyleBro rule for something CSharpier decides, and the
layout conventions in the table are reported as `set` (`CSharpier formats it`). Off:
- line breaks in lists: [BRO1107](rules/BRO1107.md)-[BRO1110](rules/BRO1110.md),
[BRO1116](rules/BRO1116.md)-[BRO1119](rules/BRO1119.md); constraints [BRO1111](rules/BRO1111.md), constructor
initializers [BRO1105](rules/BRO1105.md), enum values [BRO1121](rules/BRO1121.md), query clauses
[BRO1127](rules/BRO1127.md)-[BRO1130](rules/BRO1130.md);
- wrapping: [BRO1520](rules/BRO1520.md)-[BRO1524](rules/BRO1524.md) (operators, `=>`, `=`, call chains, `?:`);
- single-line blocks and members, accessors: [BRO1508](rules/BRO1508.md)-[BRO1510](rules/BRO1510.md),
[BRO1527](rules/BRO1527.md);
- the blank lines CSharpier normalizes itself: before `{` and `else`/`catch`/`finally` ([BRO1501](rules/BRO1501.md),
[BRO1502](rules/BRO1502.md)), after `{` ([BRO1503](rules/BRO1503.md)), before `}` ([BRO1518](rules/BRO1518.md)),
before a do-while's `while` ([BRO1512](rules/BRO1512.md)), runs of them ([BRO1517](rules/BRO1517.md)), and the
ones CSharpier removes where StyleBro would add them: before a comment ([BRO1504](rules/BRO1504.md): CSharpier
removes it inside initializers and above `else`) and between elements ([BRO1505](rules/BRO1505.md): CSharpier
removes it between accessors).
Everything else stays: naming, member order, documentation, braces on one-line bodies, readability rules, and the
blank lines CSharpier keeps as you wrote them (after `}`, after attributes, around doc comments, between switch
sections). `init` also writes `stylebro_layout_by = csharpier` and CSharpier's line width
(`stylebro_layout_print_width`: `printWidth` from the `.csharpierrc`, else `max_line_length`, else 100): an `if` merged
into the enclosing one ([BRO1149](rules/BRO1149.md)) gets its condition the way CSharpier writes it, and the brace
rules count a statement CSharpier will split over lines as multi-line, so its braces come in the same run. When
CSharpier formats only one folder (its tool or config there, with C# code outside it), these settings go into that
folder's section (`[Source/**.cs]`) and the rest of the code is judged as usual. CSharpier
sorts using directives itself (System first), so `init` writes no sort key (`dotnet format` sorts usings whenever
`dotnet_sort_system_directives_first` or `dotnet_separate_import_directive_groups` is set, and its order differs from
CSharpier's in places: `App3._0` against `App3_0`). Where your `.editorconfig` sets one (`false`, or `true` for the
groups), `init` writes the closer value in its block: a key can't be unset. Run
CSharpier after `format`: other fixes that rewrite code (attributes split, parentheses added) leave lines in StyleBro's
layout, which CSharpier then lays out its way, and StyleBro leaves that alone: after CSharpier, `stylebro-migrate
format` changes nothing more. `stylebro-migrate --write` (from StyleCop) does the same. To have StyleBro
format instead, remove CSharpier and run `init` again.
- **Running `init` again** keeps the convention lines the first run wrote (the ones below `# Conventions in this
repository's code`, with their `# init:` comments), and the report says `kept from the last init`. They describe your
code as it was before StyleBro formatted it: counted again after a `format` run, StyleBro's own output would decide
(a rule that added blank lines in some places would make your code look mixed, and the setting would flip). To have a
convention counted again, delete its line from the block and run `init` again; to count everything again, delete the
block. Conventions the first run left at StyleBro's default (no line) are counted again each time.
- **Private constants and static readonly fields** ([BRO1306](rules/BRO1306.md)) count three forms: `PascalCase`,
`camelCase` and `_camelCase`. A clear one is written as `stylebro_private_static_field_naming` (PascalCase is the
default and needs no line); mixed turns BRO1306 off, so `format` doesn't rename them (Ical.Net: 26 PascalCase, 8
camelCase, 50 `_camelCase`; BRO1306 renamed them in 28 files). With it off, public and internal fields aren't checked
either. A `dotnet_naming_rule` for constants or static readonly fields decides instead.
- **Usings inside a namespace** ([BRO1126](rules/BRO1126.md)) count fully qualified ones (`using Company.App.Common;`)
and ones relative to an enclosing namespace (`using Common;` inside `namespace Company.App.Orders`). Mostly qualified:
the rule stays on; mostly relative or mixed: it's turned off (Fitnet: 91 qualified, 92 relative). A name is relative
when an enclosing namespace has a namespace your code declares with its first part; a namespace from a package doesn't
count.
- **File headers with empty `//` lines**: when your `file_header_template` starts or ends with a line break (IDE0073
writes an empty `//` line there), or most files' headers start or end with an empty `//` line, `init` turns
[BRO1120](rules/BRO1120.md) off: it would remove those lines, and every new file from the template would bring them
back (Ical.Net: 222 of 223 files).
- **Charset** and **final line breaks**: when your `.editorconfig` sets `charset` (`utf-8` or `utf-8-bom`) or
`insert_final_newline` but the C# files don't clearly follow it (BOM or not, last line break or not, the same 75 %),
nothing ever enforced it, and `format` would add or strip the BOM or the last line break in every file. Like a line
ending the files contradict, `init` writes the files' value for C# files (`unset` when they're mixed: each file keeps
its own), and the report says how to switch (remove the line and run `stylebro-migrate format`). Ical.Net:
`charset = utf-8` with 108 of 223 files starting with a BOM, all 108 stripped; the starter kit:
`insert_final_newline = false` with 1,092 of 1,597 files ending in a line break, all 1,092 removed. `stylebro-migrate
--write` does the same. `trim_trailing_whitespace = false` can't be kept this way: `dotnet format` doesn't read the key
and trims trailing whitespace on every line it formats, so the report only says so (with IDE0055 off nothing is trimmed).
These keys (and `end_of_line`) are judged per file against the `.editorconfig` that decides it, as the compiler reads
them: the nearest one that sets the key wins, within it the last matching section. A nearer file overrides anything
the root block says, so when a folder's own `.editorconfig` (one without `root = true`, like Bogus's `Source/`) sets
the key, `init` writes its line there, in a block between `# BEGIN stylebro-folder` and `# END stylebro-folder`
(Bogus: the root block's `insert_final_newline = unset` never reached `Source/`, and `format` stripped 48 files' last
line break).
- **Spacing** that IDE0055 enforces counts places on one line: a space after `if`/`for`/`foreach`/`while`/`switch`/
`using`/`lock`/`catch` or not (`if (x)` or `if(x)`), spaces inside parentheses (control-flow statements, parenthesized
expressions, casts), after a cast (`(int) x`), and inside call and parameter lists (`F( x )`). A clear form other than
the SDK's default is written (`csharp_space_after_keywords_in_control_flow_statements`,
`csharp_space_between_parentheses`, `csharp_space_after_cast`,
`csharp_space_between_method_call_parameter_list_parentheses`,
`csharp_space_between_method_declaration_parameter_list_parentheses`). When any of them is mixed, IDE0055 is turned
off with one `# init: spacing:` note, since it can't be limited to one option, and `format` skips the whitespace pass
(Bogus: `foreach( var x in y )` in 341 places, 65 %, which IDE0055 had rewritten). With CSharpier, column alignment or
an IDE0055 severity of your own, nothing is written.
- **Blank lines between members** ([BRO1505](rules/BRO1505.md)) count the neighbouring members of a type that need one
(one spans several lines, or is documented), with a blank line between them or without; **blank lines above doc
comments** ([BRO1513](rules/BRO1513.md)) count doc comments right below code or after a blank line. Mostly with: the rule
stays on; mostly without or mixed: it's turned off, so `format` doesn't add blank lines to compact code (Kavita: 392
and 312 files).
- **Blank lines below `//` comments** ([BRO1506](rules/BRO1506.md)) count the comments it checks (the last line of a
comment group; not the file header, trailing comments or comments below code) right above the code or with a blank line
below. Mostly right above: the rule stays on; mostly with a blank line or mixed: it's turned off (RecyclableMemoryStream
keeps one below commented-out tests).
- **`this.`**: when your `.editorconfig` requires it (a `dotnet_style_qualification_for_*` key `= true:warning`, or
`= true` with IDE0009 at `suggestion` or above, by its id, the `Style` category's severity or the bulk one), the block
leaves IDE0009 alone instead of turning it off.
- **Interface members**: `dotnet_style_require_accessibility_modifiers = always` asks for `public` on interface members
too. When your code doesn't have them and the build doesn't enforce it (`EnforceCodeStyleInBuild` with IDE0040 at
warning or error by id, category or bulk; a `:warning` after the value doesn't count in the build), `init` writes
`for_non_interface_members`, so [BRO1404](rules/BRO1404.md) doesn't add them.
- **`else`/`catch`/`finally` placement** counts only blocks that span lines: in a one-line method
(`{ if (x) { return 1; } else return 2; }`) the layout isn't a choice.
- **Blank lines below doc comments** ([BRO1511](rules/BRO1511.md)) count doc comments right above their element or with
a blank line between. Mostly right above: the rule stays on; mostly with one or mixed: it's turned off (MoreLINQ).
- **Primary-constructor parameters** ([BRO1302](rules/BRO1302.md)) count those named like parameters (`logger`) or like
the fields they replace (`_logger`). Mostly `_logger` or mixed turns BRO1302 off: it has no switch for these
parameters alone, so it's then off for every parameter (the `# init:` line says so).
- **Tuple element names** ([BRO1311](rules/BRO1311.md)) count `(int Count, ...)` and `(int count, ...)`. Mostly camelCase
writes `stylebro_tuple_element_name_casing = camelCase`; mixed turns the rule off.
- **Preset version**: `init --write` sets `` in each `Directory.Build.props` your projects read, but not
in one whose projects no solution lists and that doesn't mention StyleBro.Analyzers (example or test-fixture folders).
A `Directory.Build.props` it has to create below another one imports that one, so nothing the projects read gets lost.
- **Run from a subfolder**: when the folder you run `init` (or `stylebro-migrate --write`) in is part of a git repository
whose build reaches past it (a `Directory.Build.props`, `Directory.Packages.props`, `.editorconfig` or `.globalconfig`
above it, or a solution in it that lists projects outside it, like `build/App.sln`), it works on the repository's root
and says so. A self-contained folder stays on its own.
- **`.globalconfig`**: the root's `.globalconfig` counts like its `.editorconfig` for what your team set and enforces (a
`this.` it requires, its IDE0055 severity, the conventions' keys). With `AnalysisMode` `All` and
`EnforceCodeStyleInBuild`, every code-style rule is a warning in the build, so a `dotnet_style_qualification_for_* = true`
without a severity counts as enforced.
- **A folder that turns the Style category off** (`dotnet_analyzer_diagnostic.category-Style.severity = none` in its
`.editorconfig`): a rule's own entry wins over a category, so `init` turns the built-in rules its block sets by id off
there too (in the folder's own block), and says so.
- **Column alignment**: when your `.editorconfig` aligns code in columns for ReSharper or Rider
(`resharper_int_align_variables = true`, `resharper_csharp_int_align_fields = true` and the other `int_align` keys;
the comments' alignment aside), IDE0055's whitespace formatting would flatten it (RestSharp:
`var doc = CreateAttributesXml();` became `var doc = CreateAttributesXml();` in 171 files). `init` turns IDE0055
off, with a `# init:` comment, so `stylebro-migrate format` skips the whitespace pass. No StyleBro rule changes the
spaces inside a line, so they stay on. An IDE0055 severity of your own wins.
- **One-line jumps** (`if (x) return;`, also `break`, `continue`, `throw`, `goto`, `yield break` on the `if` line): from
3 of them on, `init` writes `stylebro_allow_single_line_jump_statements = true`, majority or not, so
[BRO1514](rules/BRO1514.md) doesn't brace them (the starter kit: 878 of 1,130 bodies with braces, and BRO1514 braced
194 one-line jumps).
- **`#region`s inside members** ([BRO1113](rules/BRO1113.md), the starter kit's `#region Arrange` / `Act` / `Assert` in
its tests): from 3 of them on, the rule is turned off, so `format` doesn't remove them. [BRO1112](rules/BRO1112.md)
(regions between members) is off in the preset already.
- **Using order** counts files with 2 or more usings: sorted with System first, sorted without, or not sorted. `dotnet
format` sorts usings whenever `dotnet_sort_system_directives_first` is set (to any value), so the preset doesn't set
it: `init` writes it when your usings are sorted (or there are too few files to tell), and only a comment when they
aren't, or when some files put System first and others don't. When your `.editorconfig` sets the key itself and 3 or
more files aren't in its order, the report says how many `format` will reorder and that removing the key keeps them
(the starter kit: `false`, 262 of 1,120 files); `init` can't do it for you, since `unset` sorts them too.
- **No root `.editorconfig` without code for it**: when all your C# sits in folders whose `.editorconfig` says
`root = true` (the starter kit's `src/`) and the root has no `.editorconfig`, `init` writes none there. Likewise the
preview adds the package reference only where projects with C# files read it (a template pack project without code
doesn't get a root `Directory.Build.props`).
| Setting | What's counted | Rule | Key |
|---|---|---|---|
| Private field names | `field` or `_field` | [BRO1303](rules/BRO1303.md) | `stylebro_private_field_naming` |
| `{` placement | `{` of a multi-line block on its own line or at the end of the line | SDK formatter (IDE0055) | `csharp_new_line_before_open_brace` |
| `else`/`catch`/`finally` | after `}` on a new line or the same line | SDK formatter (IDE0055) | `csharp_new_line_before_else`, `_catch`, `_finally` |
| Braces | one-line `if`/`else`/`for`/`foreach`/`while`/`using`/`lock` bodies with or without braces | [BRO1514](rules/BRO1514.md)-[BRO1516](rules/BRO1516.md) | `csharp_prefer_braces` |
| Operator placement | binary and `?:` operators at the start or end of a wrapped line | [BRO1520](rules/BRO1520.md) | `dotnet_style_operator_placement_when_wrapping` |
| `=>` placement | `=>` of expression bodies and switch arms where a line wraps | [BRO1521](rules/BRO1521.md) | `stylebro_arrow_placement_when_wrapping` |
| `=` placement | `=` where a line wraps | [BRO1522](rules/BRO1522.md) | `stylebro_equals_placement_when_wrapping` |
| Trailing commas | multi-line initializers, enums and switch expressions | [BRO1401](rules/BRO1401.md) | `stylebro_trailing_comma` |
| Empty strings | `string.Empty` or `""` (outside constant contexts) | [BRO1106](rules/BRO1106.md) | `stylebro_empty_string_style` |
| Null checks | `is null` or `== null` | [BRO1133](rules/BRO1133.md) | `stylebro_null_check_style` |
| Summaries | one-line `` texts with the tags on their own lines or on one line | [BRO1616](rules/BRO1616.md) | `stylebro_summary_layout` |
| `` | `` or `` | [BRO1601](rules/BRO1601.md) | `stylebro_inheritdoc_style` |
| Default values | `default(T)` or `default` | IDE0034 | `csharp_prefer_simple_default_expression` |
| `)` of split lists | after the last item or on its own line | [BRO1110](rules/BRO1110.md) | `stylebro_closing_parenthesis_placement` |
| First item of split lists | on the next line or after `(` | [BRO1107](rules/BRO1107.md) | `stylebro_split_list_first_item` |
| Items of split lists | one per line or grouped on lines | [BRO1108](rules/BRO1108.md) | `dotnet_diagnostic.BRO1108.severity` |
| Leading multi-line lambdas | calls whose first argument is a multi-line lambda, anonymous method or anonymous object: the other arguments on its last line (`}, Kind);`) or one argument per line (mixed: both allowed) | [BRO1107](rules/BRO1107.md), [BRO1108](rules/BRO1108.md) | `stylebro_allow_leading_multiline_lambda` |
| Constructor initializers | `: base(...)`/`: this(...)` on its own line or the constructor's | [BRO1105](rules/BRO1105.md) | `stylebro_constructor_initializer_placement` |
| Constraints | `where` on its own line or the declaration's | [BRO1111](rules/BRO1111.md) | `stylebro_constraint_placement` |
| `new T { ... }` | with or without `()` before an initializer | [BRO1141](rules/BRO1141.md) | `stylebro_object_creation_parentheses` |
| Switch sections | after a blank line or without one | [BRO1526](rules/BRO1526.md) | `stylebro_blank_line_between_switch_sections` |
| Using directives | files with a namespace and their usings on one level: inside it (file-scoped too) or outside; only `inside_namespace` is written, the preset already says `outside_namespace` | [BRO1008](rules/BRO1008.md) | `csharp_using_directive_placement` |
| Arithmetic parentheses | `a + (b * c)` or `a + b * c` | [BRO1406](rules/BRO1406.md) | `dotnet_style_parentheses_in_arithmetic_binary_operators` |
| One-line statements | a braceless `if`/`else`/loop body or a `case` label's statements on their owner's line or below it | SDK formatter (IDE0055) | `csharp_preserve_single_line_statements` |
| Documented overrides | overrides and interface implementations with or without a doc comment | [BRO1601](rules/BRO1601.md) | `dotnet_diagnostic.BRO1601.severity` |
| Member order | types whose members BRO1001 sorts apart: already in its order or not | [BRO1001](rules/BRO1001.md) | `dotnet_diagnostic.BRO1001.severity` |
| Overloads | overload groups whose members BRO1001 sorts to different places (by access, `static`, ...): next to each other or apart | [BRO1001](rules/BRO1001.md) | `stylebro_keep_overloads_together` |
| Unsigned literal suffixes | `u`/`ul` suffixes in upper or lower case (`0U` or `0u`) | [BRO1135](rules/BRO1135.md) | `dotnet_diagnostic.BRO1135.severity` |
| `s_`/`t_` prefixes | private static fields with or without the `s_`/`t_` prefix | [BRO1307](rules/BRO1307.md) | `dotnet_diagnostic.BRO1307.severity` |
| Implicit `private` | members that are private by default, with or without `private` | [BRO1404](rules/BRO1404.md) | `dotnet_style_require_accessibility_modifiers` |
| Implicit `internal` | types that are internal by default, with or without `internal` | [BRO1404](rules/BRO1404.md) | `dotnet_diagnostic.BRO1404.severity` |
| Partial parts | `partial class X` parts with or without an access modifier | [BRO1007](rules/BRO1007.md) | `dotnet_diagnostic.BRO1007.severity` |
| `case` blocks | a block below a `case` label indented or at the label's level | SDK formatter (IDE0055) | `csharp_indent_case_contents_when_block` |
| Combined attributes | `[A, B]` or one attribute per list | [BRO1102](rules/BRO1102.md) | `dotnet_diagnostic.BRO1102.severity` |
| Combined locals | `int a, b;` or one local per declaration | [BRO1142](rules/BRO1142.md) | `dotnet_diagnostic.BRO1142.severity` |
| Combined fields | `int _a, _b;` or one field per declaration | [BRO1114](rules/BRO1114.md) | `dotnet_diagnostic.BRO1114.severity` |
| Type names | files with only keywords (`int`) or framework names (`Int32`) | IDE0049 | `dotnet_diagnostic.IDE0049.severity` |
| Private constants and static readonly fields | `PascalCase`, `camelCase` or `_camelCase` | [BRO1306](rules/BRO1306.md) | `stylebro_private_static_field_naming` |
| Usings inside a namespace | fully qualified or relative to an enclosing namespace | [BRO1126](rules/BRO1126.md) | `dotnet_diagnostic.BRO1126.severity` |
| File headers | headers with or without an empty `//` line at the top or bottom (or a `file_header_template` with one) | [BRO1120](rules/BRO1120.md) | `dotnet_diagnostic.BRO1120.severity` |
| One-line jumps | `if (x) return;` on the `if` line, or a lone jump in braces | [BRO1514](rules/BRO1514.md) | `stylebro_allow_single_line_jump_statements` |
| Empty blocks | `{ }` on one line or on two (mixed: both allowed) | [BRO1508](rules/BRO1508.md), [BRO1509](rules/BRO1509.md) | `stylebro_allow_empty_single_line_blocks` |
| One-line properties | properties with a block-bodied accessor on one line (`X { get { return x; } }`) or on several | [BRO1509](rules/BRO1509.md) | `dotnet_diagnostic.BRO1509.severity` |
| Accessor bodies | multi-line properties whose accessors all have block bodies: all one-line or all multi-line, or mixed | [BRO1510](rules/BRO1510.md) | `dotnet_diagnostic.BRO1510.severity` |
| Auto-property accessors | `{ get; set; }` on the declaration's line or on several lines | [BRO1527](rules/BRO1527.md) | `dotnet_diagnostic.BRO1527.severity` |
| `#region`s inside members | the `#region`s inside members (3 turn the rule off) | [BRO1113](rules/BRO1113.md) | `dotnet_diagnostic.BRO1113.severity` |
| Column alignment | your `resharper_*int_align_*` keys set to `true` | SDK formatter (IDE0055) | `dotnet_diagnostic.IDE0055.severity` |
| Charset | files with or without a BOM, when the `charset` that applies to them doesn't match them | SDK formatter | `charset` |
| Final line breaks | files with or without a last line break, when the `insert_final_newline` that applies to them doesn't match them | SDK formatter | `insert_final_newline` |
| Line endings | files with LF or CRLF line endings (not files git converts on checkout) | SDK formatter | `end_of_line` |
| Using order | files with 2 or more usings: sorted with System first, sorted without, or not sorted | `dotnet format` | `dotnet_sort_system_directives_first` |
| Spacing | `if (x)` or `if(x)`; spaces inside parentheses, after casts, inside call and parameter lists (mixed: IDE0055 off) | SDK formatter (IDE0055) | `csharp_space_after_keywords_in_control_flow_statements`, `csharp_space_between_parentheses`, `csharp_space_after_cast`, `csharp_space_between_method_call_parameter_list_parentheses`, `csharp_space_between_method_declaration_parameter_list_parentheses` |
| Member separation | neighbouring members that need a blank line, with or without one | [BRO1505](rules/BRO1505.md) | `dotnet_diagnostic.BRO1505.severity` |
| Blank line above doc comments | doc comments right below code or after a blank line | [BRO1513](rules/BRO1513.md) | `dotnet_diagnostic.BRO1513.severity` |
| Blank line below comments | `//` comments right above code or with a blank line below | [BRO1506](rules/BRO1506.md) | `dotnet_diagnostic.BRO1506.severity` |
| Blank line below doc comments | doc comments right above their element or with a blank line between | [BRO1511](rules/BRO1511.md) | `dotnet_diagnostic.BRO1511.severity` |
| Primary-constructor parameters | named `logger` or `_logger` (classes and structs) | [BRO1302](rules/BRO1302.md) | `dotnet_diagnostic.BRO1302.severity` |
| Tuple element names | `(int Count, ...)` or `(int count, ...)` | [BRO1311](rules/BRO1311.md) | `stylebro_tuple_element_name_casing` |
The keys are described in [Settings](configuration.md). `init` prints each verdict (`kept`, `default`, `off`, `both`,
`mixed`, `too few`, or `set` when your configuration decides) with the value that applies, then a summary that names the keys
(RealWorld):
```
Conventions in the code (judged from 3 places: all must agree below 10, 75% from 10 on).
off: mixed, or not what your setting says, so the rule that enforces it is turned off; both: mixed, both allowed;
mixed: the default stays, nothing is turned off (no StyleBro rule enforces it, or your .editorconfig sets the rule):
too few private fields: 2 places -> default stays (camelCase)
set '{' of multi-line blocks: 196 of 196 on its own line -> set in .editorconfig (all)
too few operators where a line wraps: 1 place -> default stays (beginning_of_line)
kept multi-line initializers: 18 of 18 without a trailing comma -> omit
default empty strings: 3 of 3 string.Empty -> string_empty
off null checks: 7 'is null', 10 '== null' -> BRO1133 is off
off split lists: 13 first item on the next line, 17 first item after '(' -> BRO1107 is off
...
Summary:
Kept your style for 5 settings: wrote stylebro_trailing_comma; 4 were already StyleBro's default.
Turned 2 rules off because your code mixes both forms: BRO1133 (null checks), BRO1107 (split lists).
To choose later: set the key in .editorconfig, remove its init line, run 'stylebro-migrate format'.
4 settings had fewer than 3 places to tell, StyleBro's defaults stay: stylebro_private_field_naming,
dotnet_style_operator_placement_when_wrapping, stylebro_equals_placement_when_wrapping, stylebro_summary_layout.
```
Each written line gets a comment in the block saying why:
```ini
# init: multi-line initializers: without a trailing comma in 18 of 18 places in your code
stylebro_trailing_comma = omit
# init: null checks: your code mixes both forms (7 'is null', 10 '== null'), so BRO1133 is off. To choose one: set stylebro_null_check_style and remove the next line.
dotnet_diagnostic.BRO1133.severity = none
```
#### Changing a detected setting later
Set the key in your own part of `.editorconfig`, outside the block (for example `stylebro_null_check_style =
equality_operator`), remove the rule's `severity = none` line from the block, and run `stylebro-migrate format` to bring
the code in line. The block is rewritten each time `init` runs, but a key you set yourself wins over what it counts: it
then writes neither the setting nor the `severity = none` line.
EF Core migrations (files with `[Migration(...)]`, a `Migration` base class or a `ModelSnapshot`) are written by
`dotnet ef`, so `init` marks their folders `generated_code = true`: formatting and StyleBro leave them alone. Vendored
folders (`vendor`, `vendored`, `third_party`, `thirdparty`, `external`) get the same section.
When it finds a StyleCop setup (`stylecop.json`, StyleCop rule ids in `.editorconfig`, rulesets or
global configs, a StyleCop.Analyzers reference), it stops without writing and tells you to run
`stylebro-migrate --write` instead: that's [path B](#b-coming-from-stylecop), which turns StyleBro's rules on only where
StyleCop enforced them.
With SonarQube or SonarCloud (a `SonarAnalyzer.CSharp` reference, Sonar rule severities in rulesets or configs, or a
quality profile you export and pass with `--sonar-profile profile.xml`), `init` also turns on the StyleBro and .NET rules
that fix what your Sonar rules report (S927 -> BRO1313, S4136 -> overloads kept together, ...) and prints which:
[Coming from SonarQube](migrating.md#coming-from-sonarqube). The same works for `stylebro-migrate --write` in path B.
### 3. Run stylebro-migrate format
```
stylebro-migrate format
```
This fixes whitespace, the built-in rules and every StyleBro rule. One fix can make work for another rule, so the
command runs `dotnet format` again until a run changes no file (at most three runs) and prints what each run changed
(`Run 1: 293 files changed:` and the first 10 of them, `Run 2: 0 files changed, clean.`); `--once` runs it once, with the same report and the findings kept in that run. It ends
with the findings StyleBro's fixes leave on purpose, each with its reason ([Findings kept on purpose](#findings-kept-on-purpose)),
and exits with 0 when clean, 2 when a run still changed files. When `dotnet format` skips a project because its
references didn't load (a broken restore or package cache), the command names it and says the run is incomplete (for
one target framework only: the code only that framework compiles); `--diff` stops then instead of showing a partial
preview, and `--verify-no-changes` fails when a project wasn't loaded in any run (a project that references an F#
project, which `dotnet format` can't load, stays a warning). When no project has StyleBro.Analyzers in its restore output (`obj/project.assets.json`), the command stops
before `dotnet format` runs: without the analyzers it would fix only whitespace and the built-in rules and report the
code clean. Projects without it next to ones with it are named (`Without StyleBro: ...`). The command restores once
before the runs; that restore skips NuGet's audit (`-p:NuGetAudit=false`): vulnerability warnings (NU1901-NU1904) are
the build's business, and with `TreatWarningsAsErrors` they failed every run. A solution filter (`.slnf`) works like
its solution, with only its projects. A solution with several filters of it next to it (Steeltoe's
`Steeltoe.All.slnx` and its seven `.slnf` parts) counts as that solution; the command says how to name a filter instead.
A folder with several solutions or projects needs one named (`stylebro-migrate format MySolution.slnx`): the command lists them instead of picking one.
With none at the root, it looks for a Traversal project there (`dirs.proj` with `Sdk="Microsoft.Build.Traversal"`,
which `dotnet format` can't load: `format` runs on a temporary solution with the projects it lists), then for solutions
one folder down (`src/MySolution.slnx`): the only one is used, and the command says so (`Using src/MySolution.slnx`);
several are listed, as at the root. `baseline` and `--diff` find them the same way.
If `dotnet format` crashes, the command names the projects of that run, and when the crash came after some files were
written, lists every file it changed (`Run 1 failed after changing 12 files:`).
A solution in a folder of its own (`solutions/App.slnx` listing `../src/...`) works too: the command runs from the
folder that holds all its projects. Where the repository enforces IDE0005 (unnecessary usings, by id, its Style category
or the bulk severity), the command fixes it too: other fixes (`UInt32` to `uint`) can leave a using unused. After the
runs it names the generated files git tracks in the projects it changed (`*.g.cs`, `*.Designer.cs`, a T4 template's
output, files with an `` header): `X is generated and checked in: rebuild and commit it.` Where
CSharpier formats the repository, it ends with a reminder to run CSharpier.
Review the diff and commit it. In an established codebase, land that commit when few pull requests are open (they
will conflict with it), and add its hash to `.git-blame-ignore-revs` so `git blame` skips it (GitHub reads that file
too; locally: `git config blame.ignoreRevsFile .git-blame-ignore-revs`). In a large codebase you may not want one big
change: see [Large codebases](#large-codebases).
**Why not plain `dotnet format`?** It also applies every other analyzer's fixes and the compiler's own, and some of
those change behavior or break the build: on one trial repository it added `required` to properties (CS8618) and a
Sonar fix removed `init;` accessors, nine build errors. `stylebro-migrate format` passes `dotnet format` only StyleBro's
rule ids and the built-in rules `init` or the migration turn on (`--diagnostics ...`); whitespace formatting runs as
usual. It also runs once per target framework where needed ([Multi-targeted repositories](#multi-targeted-repositories))
and never touches git submodules. The plain equivalent is `dotnet format --diagnostics` with the ids to fix:
```
dotnet format --diagnostics BRO1001 BRO1505 BRO1601 IDE0055 IDE0036 BRO1008
```
`stylebro-migrate format --all` applies everything, like plain `dotnet format`.
[What to expect on the first run](#what-to-expect-on-the-first-run): solution names, run time, warnings as errors.
### 4. Check it in CI
Run `stylebro-migrate format --verify-no-changes --severity warn` after the build: it changes nothing and fails (exit
code 2) when formatting would change a file. Findings kept on purpose don't fail it; plain
`dotnet format --verify-no-changes` fails on those too, so a script or an AI agent that loops "until clean" never gets
there with it. Set-up for GitHub Actions and Azure Pipelines: [ci.md](ci.md). `init` and the migration
name the workflow lines (`.github/workflows`, `azure-pipelines.yml`) that still run plain `dotnet format --verify-no-changes`.
To check the staged files before each commit: [Pre-commit hook](pre-commit.md).
With `--agents-md`, `init --write` also puts a short StyleBro section into `AGENTS.md` for AI coding
agents (opt-in). For scripts and agents there are `format --files` (just the files an edit touched) and `--json`:
[Scripts and AI agents](agents.md).
## B. Coming from StyleCop
Still deciding? [StyleBro vs StyleCop](stylebro-vs-stylecop.md) compares the two with measured numbers.
`stylebro-migrate` reads your StyleCop setup (rulesets, global configs, `.editorconfig` files, `stylecop.json`) and
writes StyleBro and SDK settings that enforce the same things, so code StyleCop was happy with stays as it is. A
walk-through on a small project, with real output: [samples/StyleCopMigration](https://github.com/bisforboman/stylebro/blob/main/samples/StyleCopMigration/README.md).
### 1. Dry run
```
stylebro-migrate path/to/repo
```
It prints what it read, the settings it would write, and the StyleCop rules nothing enforces after the switch (dropped
by design, partly covered, or not covered yet). Nothing is changed. To also see what the settings and the format run
would do to the code: `stylebro-migrate path/to/repo --diff` ([Preview first](#preview-first)).
### 2. Write the settings
```
stylebro-migrate path/to/repo --write
```
This writes the settings into your `.editorconfig` files (between the `stylebro-migrate` markers), turns StyleBro's
preset off in the root `Directory.Build.props` (the settings replace it), and adds the replacing rule ids to existing
StyleCop suppressions (`#pragma warning disable`, `[SuppressMessage]`, ``). You don't need `init` after this:
the block includes the built-in rules.
### 3. Swap the package
Remove the StyleCop reference and add `StyleBro.Analyzers` instead, as in [path A, step 1](#1-add-the-package). Look
for both package ids, `StyleCop.Analyzers` and the prerelease `StyleCop.Analyzers.Unstable`, and for a
`GlobalPackageReference` in `Directory.Packages.props` (central package management): replace it with
``. `stylecop.json` and the StyleCop suppressions
can stay; StyleBro ignores them.
### 4. Run stylebro-migrate format
```
stylebro-migrate format
```
Why not plain `dotnet format`: [step 3 of path A](#3-run-stylebro-migrate-format). In a StyleCop-clean repository this should change
little or nothing. Look at what it did change: usually code StyleCop
missed, or a rule where StyleBro deliberately differs ([differences-from-stylecop.md](differences-from-stylecop.md)).
Then check the report from step 1 for rules your team relied on that nothing enforces any more. Details:
[migrating.md](migrating.md). CI: [ci.md](ci.md).
## Newer C# and APIs (optional)
To also rewrite code into newer C# and newer APIs (target-typed `new()`, collection expressions, file-scoped
namespaces, `ArgumentNullException.ThrowIfNull`, ...) with the SDK's own rules, after either path:
```
stylebro-migrate init --modernize --write
dotnet format
```
In multi-targeted projects, StyleBro's multi-target guard hides the newer-API rules where a target framework lacks the
API, and the newer-C# rules are suggestions unless the project sets `LangVersion`. Tiers, examples and the rules left out:
[modernizing.md](modernizing.md).
## What to expect on the first run
- **Name the solution when the root has several.** With more than one project or solution file in the folder (say
`Build.csproj` next to `MySolution.slnx`), `dotnet format` stops with MSB1011 ("more than one project or solution
file"). Pass the one to format: `dotnet format MySolution.slnx` (the same for `stylebro-migrate format`).
- **Multi-targeted repositories take longer.** `stylebro-migrate format` runs `dotnet format` once per target
framework, and each run loads the solution again. That's minutes, not seconds: Dapper, which builds in 13 seconds,
took 345 seconds to format.
- **`TreatWarningsAsErrors` fails the first build** until everything is fixed or recorded: StyleBro's findings are
warnings, so they become errors. Run `dotnet format` (or `stylebro-migrate format`) before building, or record what's
left in a [baseline](baseline.md).
- **Some renames are left to you.** The naming rules don't rename a member whose name also appears in a string
(reflection, serialized names, `DebuggerDisplay`): the rename would compile but break at run time. Those warnings
stay after `dotnet format`: see [Findings kept on purpose](#findings-kept-on-purpose).
## Findings kept on purpose
Every StyleBro finding has a fix, but a fix can see more than the analyzer: the whole solution, other projects
included. When a change would break something only the fix can see, it leaves the finding, the warning stays, and the
fix offers no code action for it. That's the one exception to "`dotnet format` fixes everything StyleBro reports",
and it's on purpose. The reasons, per finding:
- the name is in a string elsewhere in the solution (serialized names and test JSON for properties and public fields;
for other fields only where code reaches a member by name: `GetField("_count")`, `GetProperty(...)`, `Type.GetType(...)`,
`[MemberData("...")]`, an attribute's `Name = "..."`): the rename would compile but break at run time;
- the name is in a `nameof(...)` elsewhere, whose text would change with it (an HTTP header name, a log key);
- another member would hide the new name, or the new name already means something else where the name is used;
- a use is in generated code (a source generator, a Razor page, a designer file), which a tool writes again;
- a related override or implementation is public API, or also implements a member that isn't renamed;
- the rename would add compile errors (tuple element names), or the namespace is also defined outside the solution.
`stylebro-migrate format` lists them after its last run:
```
Run 2: 0 files changed, clean.
Clean: 1 finding kept on purpose (listed below). StyleBro's fixes leave these: renaming by hand breaks what the check protects. Check those uses first, or suppress or baseline them (...).
src/Counter.cs(5,17): BRO1303 Rename '_count' to 'count'. Kept: the name is in a string in the solution (reflection, serialization): renaming would compile but break at run time.
```
What to do with one: **don't rename by hand to silence it** (that's what breaks the reflection or the generated code).
Check the uses the reason names; if they can change too, rename both by hand. Otherwise suppress it with a reason
(`#pragma warning disable BRO1303 // read by reflection in Tests`), or record it in the [baseline](baseline.md).
**With `TreatWarningsAsErrors`** the kept findings are build errors after the first run, so make dealing with them part
of it: run `stylebro-migrate format`, then either suppress each one as above or run `stylebro-migrate baseline` (it
records exactly what is left, the kept findings included), and commit that together with the formatting. The list is
printed after every run, also when `format` ends "Not clean".
Exit codes of `stylebro-migrate format`, for CI and agents:
| Exit code | Plain run | `--verify-no-changes` |
|---|---|---|
| 0 | clean: the last run changed nothing (kept findings don't count) | formatting would change nothing; only kept findings are left |
| 2 | still changing after three runs | formatting would change a file |
| 1 | | a project no run loaded (`Incomplete:`): its files weren't checked |
| other | `dotnet format` failed (load or build errors) | the same |
`--verify-no-changes` changes nothing: when `dotnet format --verify-no-changes` reports findings, it formats the files
with findings (for their rules) in a temporary copy of the repository to see whether any file would change and which
findings the fixes keep, then deletes the copy. After a clean `stylebro-migrate format` on the same code, settings and
options, it already knows the findings that run kept (recorded in the temporary folder) and formats no copy.
Plain `dotnet format --verify-no-changes` can't tell the two apart and fails on kept findings.
## Excluding vendored code
Code you don't own (a copied library, generated sources checked in, a `ThirdParty` folder) shouldn't be reformatted.
Mark it as generated code in the root `.editorconfig`:
```ini
[ThirdParty/**]
generated_code = true
# Entity Framework Core migrations: generated by 'dotnet ef', regenerated rather than edited
[**/Migrations/**]
generated_code = true
```
EF Core's migration files (`Migrations/*.cs` and the model snapshot) are generated but carry no ``
header, so without this the first run reformats every one of them (291 files in one trial repository).
StyleBro's rules, the built-in rules (IDE0055, IDE0073, ...) and `dotnet format`'s whitespace pass all skip generated
code, in the build and in `dotnet format`. One side effect: the compiler treats generated files as nullable-oblivious
unless they say `#nullable enable` themselves.
`dotnet_analyzer_diagnostic.severity = none` in that folder isn't enough: a rule turned on by its id
(`dotnet_diagnostic.BRO1001.severity = warning` in the root) wins over it. `stylebro-migrate --write` therefore writes
every replacing rule as `none` into such a folder's `.editorconfig`, and it never edits files inside git submodules
(folders with their own `.git`).
**Files linked from a submodule.** A project can compile files from another folder (``,
or a shared `.projitems`). `stylebro-migrate format` never formats anything inside a git submodule (it passes `--exclude`
with every submodule folder), so StyleBro doesn't report findings there either: the package's build targets read the
submodule paths from the `.gitmodules` above the project and hide StyleBro's findings in those folders. A folder that
is not a submodule (a copied library) still needs the `generated_code = true` section above.
## Multi-targeted repositories
If any project sets several ``, plain `dotnet format` doesn't work at all; `stylebro-migrate format`
does. It takes the same arguments and options:
```
stylebro-migrate format
stylebro-migrate format MySolution.sln --verify-no-changes --severity warn
```
Plain `dotnet format` crashes there on the SDK's formatting fix (IDE0055) without writing anything (reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000) and [dotnet/roslyn#86001](https://github.com/dotnet/roslyn/issues/86001)). The command runs
`dotnet format` once per target framework instead; without multi-targeted projects it's one run. A diagnostic
every framework sees is printed once, and `--report` writes one `format-report.json` covering all runs. Spellings of
one framework share a run (`net6` and `net6.0`, NuGet's short names). A run loads only the projects that target its
framework, so the renaming fixes also check the names in every other C# file of the repository: a field the tests
read with `GetField("count")` keeps its name in the library's run too, and a name a project outside the run uses
(`UsedInProjectNotLoaded`) is left to a run that loads both, or kept. Files a project compiles from outside its own
folder (``) are formatted in the runs of the projects that compile them, and
`--files` finds such a file's project too. Projects that compile the same files with other settings (a library and its AOT variant
compiling `..\Lib\*.cs` with other `DefineConstants`, language version or nullable context for that framework) run in
separate parts of a framework's run (`== net8.0, part 1 of 2`), each with what it references: loaded together, `dotnet
format` crashes on their different edits of one file ([dotnet/roslyn#86001](https://github.com/dotnet/roslyn/issues/86001)).
Projects that compile a shared file with the same settings (a polyfill every library links) share a run: they parse it
the same, so its copies get the same edits (`latest` counts as the version it means). This
applies without multi-targeted projects too. Why: [ci.md](ci.md#notes). `stylebro-migrate init` tells you when it finds such projects.
A framework whose code compiles exactly like another's in a project gets no run of its own there: when no `#if` tests a
symbol that tells them apart (say the code tests `NET8_0_OR_GREATER` but nothing newer), `net8.0`, `net9.0` and `net10.0`
see the same files, settings and branches, and one run formats them all. Only frameworks of one kind share a run (.NET,
.NET Standard or .NET Framework, and the same platform such as `-windows`), since their APIs differ more. The
`Multi-targeted:` line names the frameworks that ran and those that didn't need to.
After a run that changed files, the next one checks every framework at once first: a run whose check is clean isn't
made again, and one whose check found something formats only the files with findings (all of its files when an earlier
run of the same round changed a file it formats too).
A multi-line string can hold lines starting with `#` (`#pragma warning disable` in a source generator's test input)
inside an `#if` branch some framework leaves out: there those lines read as directives, and the SDK's formatter moves
them to column 0, breaking the string where the branch is compiled (CS8999). `format` names such files (`Protected:
...`) and, while `dotnet format` runs, swaps those lines' `#` for a character the file doesn't have, then puts it back,
so the rest of the file is formatted as usual. If `format` is killed before it puts the `#` back (a closed terminal, a
crash), the next `stylebro-migrate` command does: it keeps a record of the swap in `%TEMP%/stylebro-mask` (`$TMPDIR` on
Linux and macOS) and says `Restored: `. A file changed in a way that moved the swapped characters is left alone
and named (`Not restored: ...`); `format` and `--verify-no-changes` then stop with `Stopped: ...` until the `#` is back.
A project `dotnet format` can't load at all (F#, `.fsproj`) makes it skip the C# projects that reference it, for the
frameworks where the reference matters. `format` names them once, with that cause ("Partly incomplete: ... Likely
cause: they reference TestFSharp.fsproj"); a restore doesn't change it, and the rest of the repository is formatted.
## Old-style (non-SDK) projects
Projects in the old format (`` without `Sdk=`, usually .NET Framework with
`packages.config`) work with `stylebro-migrate format`, with a few extra steps:
- **Restore with Visual Studio's MSBuild first**, from a Developer Command Prompt:
```
msbuild -t:restore -p:RestorePackagesConfig=true
```
`dotnet restore` can't restore `packages.config`, and for the other old-style projects it writes restore output that
Visual Studio's MSBuild then rejects ("Your project file doesn't list 'win' as a RuntimeIdentifier"). So `format`
restores only the SDK-style projects when it finds old-style ones, and says so (the `-p:RestorePackagesConfig=true`
part only matters with `packages.config`). If a build complains like that anyway (something ran
`dotnet restore`), run the MSBuild restore again.
- **Reference assemblies.** `dotnet format` loads the projects with the .NET SDK, which needs the .NET Framework
reference assemblies: without them every project is skipped ("Required references did not load"). Install the
targeting pack (the .NET Framework Developer Pack, or Visual Studio's ".NET Framework 4.x targeting pack"), or use the
`Microsoft.NETFramework.ReferenceAssemblies.net462` NuGet package (the version your projects target) and point
MSBuild at it with an environment variable, which reaches both `msbuild` and `dotnet format`:
`set TargetFrameworkRootPath=%NUGET_PACKAGES%\microsoft.netframework.referenceassemblies.net462\1.0.3\build`.
- **Install StyleBro.Analyzers the packages.config way**, like StyleCop was: Visual Studio's package manager
(or `Install-Package StyleBro.Analyzers` in its console) writes all three parts, or add them by hand. In
`packages.config`:
```xml
```
In the project (paths relative to it; the `packages` folder is next to the solution):
```xml
```
plus an `` line in the project's
`EnsureNuGetPackageBuildImports` target, as NuGet adds for every package with build targets. Don't use a
`PackageReference` there: it makes NuGet restore the project the PackageReference way, its `packages.config` packages
are no longer restored, and a fresh clone doesn't build. A `PackageReference` in a `Directory.Build.props` that also
serves SDK-style projects needs a condition that keeps it away from them:
```xml
```
`stylebro-migrate` names the packages.config projects and says this in its report.
- **`--diff` leaves `packages.config` projects out**: the copy it formats has no git-ignored `packages` folder, and
`dotnet restore` can't restore `packages.config`. It previews the other projects and names the ones it left out; only
when every project uses `packages.config` does it stop. Other old-style projects preview like `format` runs:
unrestored, so the preview has no StyleBro changes for them, and it says which. To see the changes in the
`packages.config` projects, on a clean working tree: run the command without `--diff`, restore with MSBuild as above,
run `stylebro-migrate format`, and review `git diff` (`git checkout .` undoes it).
- If StyleCop came in through `packages.config` or an ``
hint path, `stylebro-migrate` reads its version there: rules that version doesn't have stay off.
## Large codebases
If `dotnet format` would change too much at once, record today's violations in a baseline instead:
```
stylebro-migrate baseline # in the folder with your solution
stylebro-migrate baseline --project MySolution.sln # or name it
```
This writes `stylebro.baseline`; commit it. The build, the IDE and `dotnet format` then ignore those violations, and
only new code has to follow the rules. A baseline can't hide whitespace formatting. Details: [baseline.md](baseline.md).
**How long a full run takes.** `format` loads the whole solution once per run, and in a multi-targeted repository once
per target framework. On Orleans (about 200 projects, several target frameworks) the first `stylebro-migrate format`
took 57 minutes and about 9 GB of memory, and `format --verify-no-changes` 11.5 minutes. A repository that size with
`TreatWarningsAsErrors` is best started with a baseline: the build passes right away, and the backlog can be fixed a
project at a time (`stylebro-migrate format path/to/Project.csproj`) or a few files at a time (`--files`).
---
Source: https://bisforboman.github.io/stylebro/migrating/
# Migrating from StyleCop
Switching a StyleCop-clean repository to StyleBro shouldn't reformat code StyleCop was happy with. StyleBro's preset
follows StyleCop's defaults with a few deliberate exceptions ([differences-from-stylecop.md](differences-from-stylecop.md)),
but most teams have changed some of StyleCop's defaults anyway. `stylebro-migrate` reads what your team changed
and writes the matching settings.
```
dotnet tool install --global StyleBro.Migrate --prerelease
stylebro-migrate path/to/repo # dry run: prints the report and the settings
stylebro-migrate path/to/repo --diff # preview: what the settings and the format run would change in the code
stylebro-migrate path/to/repo --write # writes them
```
`--diff` runs `--write` and then `stylebro-migrate format` (until a run changes nothing) on a temporary copy of the
repository, prints which files each rule changed with a few sample hunks, and writes the full diff to
`.git/stylebro-preview.patch` (`--diff=file` for another name, `--keep` keeps the copy). The copy also gets the
StyleBro.Analyzers reference if the repository has none, next to StyleCop's (see [Swap the package](#swap-the-package));
StyleCop.Analyzers stays, but format only fixes StyleBro's
and the built-in rules' ids, so it changes nothing. In a StyleCop-clean repository the format part should be close to
empty. Details: [Preview first](getting-started.md#preview-first).
The tool is on nuget.org from 0.1.0-alpha.5, released with the analyzers under the same version (latest: 0.7.0-alpha.1;
[which version](getting-started.md#which-version) says how to build `main` instead). To run it straight from a clone: `dotnet run --project src/StyleBro.Migrate -- path/to/repo`.
Then swap the package (below) and run `stylebro-migrate format`. It runs `dotnet format` with only StyleBro's rules
and the built-in rules the block turns on (plus whitespace formatting, unless the block turns IDE0055 off: `dotnet
format`'s whitespace pass ignores that severity, so then only its style and analyzer passes run), once per target framework where projects
target several, and never inside git submodules. Plain `dotnet format` also applies every other analyzer's fixes and
the compiler's: in one trial repository it added `required` (CS8618) and a Sonar fix removed `init;` accessors, nine
build errors. The plain equivalent is `dotnet format --diagnostics ` with every id to fix (`--diagnostics BRO1001
BRO1505 IDE0055 IDE0036 ...`); `stylebro-migrate format --all` keeps everything.
## Swap the package
Remove StyleCop and add StyleBro wherever the repository references StyleCop. Look for both package ids,
`StyleCop.Analyzers` and the prerelease `StyleCop.Analyzers.Unstable`, in `.csproj`, `Directory.Build.props` and
`Directory.Packages.props`. With central package management the reference is often a `GlobalPackageReference`, which
applies to every project:
```xml
```
With central package management the version goes into the `Directory.Packages.props` that holds StyleCop's version,
which may be a folder's (`src/Directory.Packages.props`) rather than the root's; a `PackageReference` with a `Version`
there fails the restore (NU1008). `--diff` edits the same file: a `GlobalPackageReference` next to StyleCop's
`GlobalPackageReference` (in a folder's `Directory.Packages.props` too), a version-less `PackageReference` and a
`PackageVersion` otherwise. `--write` turns the preset off (`none`) only in the
`Directory.Build.props` files of projects StyleCop runs in: elsewhere no settings are written, so the preset applies.
A project that opts out of the StyleCop reference it inherits (``, often
an analyzer or source generator project) needs the same for StyleBro, else it gets StyleBro, and when it turns central
package management off itself, the version-less reference fails its restore (NU1015):
```xml
```
The migration's report names the files that reference StyleCop and the ones that remove it, and `--diff` makes both
changes in its copy. A project that turns central package management off but keeps the inherited reference gets a
second, versioned reference instead ([getting-started.md](getting-started.md#1-add-the-package) shows the pair);
`--diff` writes that pair too.
**Old-style projects** (`packages.config`, StyleCop as ``):
removing StyleCop means removing its `packages.config` entry and the `` items. StyleBro goes in the same way:
a `packages.config` entry, its `` items and the import of its build targets, not a `PackageReference`.
Running `format` there takes extra steps, and `--diff` leaves those projects out:
[getting-started.md](getting-started.md#old-style-non-sdk-projects).
### StyleCop referenced in a git submodule
Some repositories get StyleCop from a shared submodule, such as SixLabors' `shared-infrastructure`, whose
`SixLabors.Global.props` the repository's `Directory.Build.props` imports. The submodule belongs to another repository,
so it can't be edited here. The report names the file and says where the swap goes instead: in the repository's own
`Directory.Build.props`, after the line that imports the submodule's file.
```xml
```
After the swap, a second `--write` recognizes the migrated setup from the StyleCop version the first one recorded in
the block. It doesn't treat the setup as leftovers. Code that a project compiles from the submodule is not reported:
StyleBro can't fix it there, and `format` skips submodules. The package's build targets read the submodule paths from
`.gitmodules`.
`stylecop.json` and the StyleCop suppressions can stay; StyleBro ignores them. The steps in order:
[getting-started.md](getting-started.md#b-coming-from-stylecop). A small StyleCop project migrated step by step, with
real output: [samples/StyleCopMigration](https://github.com/bisforboman/stylebro/blob/main/samples/StyleCopMigration/README.md).
## What it reads
- **Only where StyleCop runs.** When some projects reference StyleCop.Analyzers (in the project, the
`Directory.Build.props`/`.targets` or `Directory.Packages.props` it gets, or what those import) and others don't (tests,
samples, benchmarks, a project with a `Remove` item), the settings apply only to the folders of the projects that run
it, with the rules it had on there: the block's section names those folders (`[src/{App,App.Core}/**.cs]`; the
topmost folders with no project without StyleCop below, and sibling folders sharing a name start no such project has
as one wildcard, `src/{LiteBus,LiteBus.Inbox*}`), a
sub-directory `.editorconfig` that covers all of them counts as the base (a `src/.editorconfig` that turns everything
but documentation off), and folders without StyleCop get no settings at all; the report lists them, and so does a
comment line at the top of the block (`# No StyleCop ran in: examples; ...`), which tells `stylebro-migrate format` to
leave those folders alone: its whitespace pass would reformat them whatever the rule severities say. Remove that line
to have them formatted. StyleBro's rules still report there in the build (at the preset's or their default
severities), since no settings turn them off. Whether StyleCop runs in a project is MSBuild's answer: with several
projects, each one is evaluated (`dotnet msbuild -getItem`, in parallel, a few seconds in all); only a project MSBuild
can't evaluate falls back to reading the files' text, with a warning. When every project or none
references StyleCop, the settings apply to the whole repository.
- StyleCop's own defaults (from the StyleCop 1.2 DLL's rule list).
- `*.ruleset` files, then `*.globalconfig` files, then the root `.editorconfig`'s sections for all C# files. Later
sources win, like in the compiler: `dotnet_diagnostic.SAxxxx.severity` and
`dotnet_analyzer_diagnostic.category-StyleCop.CSharp.*.severity` keys, and ruleset `` actions.
Also like in the compiler, a bulk setting (a category, or `dotnet_analyzer_diagnostic.severity` for every rule)
doesn't override a rule's own entry in a ruleset or another config. A ruleset's ``d rulesets count, under
its own entries. When several rulesets or global configs disagree (one per project type), the strictest wins, a file
that doesn't mention a rule counting with StyleCop's default, so production code keeps everything StyleCop enforced
there.
- **Each project's own ruleset and `stylecop.json`.** A project's ruleset is the `` its project
file, its `Directory.Build.props`/`.targets` or what they import select (the last one counts), its `stylecop.json` the
one it adds as ``. Only projects that run StyleCop count: a sample project without StyleCop (and so
without a ruleset) doesn't bring StyleCop's defaults back. The repository-wide settings take the strictest of those
rulesets and the `stylecop.json` most projects use; projects whose ruleset or `stylecop.json` gives other settings get
a section of their own in the root `.editorconfig` (`[tests/**.cs]` with BRO1001 off where the tests' ruleset turns
ordering off, `[src/**.cs]` with src's own license header). Projects that share a folder but not their settings can't
be told apart by a section: the report warns and names the project whose settings the folder gets.
- Settings the repository's MSBuild files point to are read wherever they are, also inside a git submodule (a shared
infrastructure repository): imported `.props`/`.targets` (for `GenerateDocumentationFile` and the StyleCop version),
the rulesets they select, and a `stylecop.json` added as ``. They're only read.
- Sub-directory `.editorconfig` files and path-specific sections (like `[tests/**.cs]`) are translated in place: they
get the settings that differ from the repository-wide ones, in the same file and section. A folder that turns every
analyzer off (`dotnet_analyzer_diagnostic.severity = none`, common for vendored code) gets every replacing rule as
`none`: a rule the root turns on by its id would win over the folder's bulk setting. A folder whose `.editorconfig`
says `root = true` gets a block of its own, judged from its own code; one without C# files (an npm app) gets none.
- Git submodules and other nested repositories (folders with their own `.git` file or folder) are otherwise skipped:
their settings, code and project files belong to another repository, and nothing in them is changed
(`stylebro-migrate format` excludes them too). Files a project
links from a submodule are another matter: see [excluding vendored code](getting-started.md#excluding-vendored-code).
- The StyleCop.Analyzers version: from a `PackageReference` or `PackageVersion`, a `packages.config` entry, or an
`` hint path into a `packages` folder (old-style projects). Rules the version's DLL doesn't have count as
off: with 1.1.x the rules added in 1.2 (SA1141, SA1142, SA1316, SA1414), with 1.0.x also SA1135, SA1136, SA1137,
SA1139, SA1314 and SA1413. 1.1.x's SA1413 doesn't check switch expressions yet: when the code has a multi-line switch
expression without a trailing comma, [BRO1401](rules/BRO1401.md) stays off (it has no setting to leave them out), and
the report says so.
- StyleCop spacing rules that are off while others are on (kafkaflow: SA1000, SA1003, SA1009): IDE0055 enforces every
SDK spacing option, so the code decides, as in `init`. A clear other form gets the option that keeps it
(`csharp_space_after_keywords_in_control_flow_statements = false` for `if(x)`, `csharp_space_around_binary_operators`
`none`, or `ignore` when the operators are mixed, `csharp_space_after_cast`, the parentheses options); where no option
expresses the code (`new ()`, mixed `if(x)`/`if (x)`, a few outliers), IDE0055 stays on and the report names what
`format` changes.
- Whether any project sets `GenerateDocumentationFile`. Without it the build doesn't parse XML documentation and
StyleCop's rules that read it never run (it reports SA0001 instead), but `dotnet format` does parse it. So the
StyleBro rules that read documentation as XML (BRO1603-BRO1611) stay off.
- StyleCop's alternative rules: SX1101 (no `this.`) turns on the SDK's IDE0003, SX1309 (fields begin with `_`) sets
BRO1303 to `_camelCase`, which also covers SX1309S (private static fields begin with `_`; on its own, with instance
fields named `count`, the report lists it as not expressible: SA1309 is off then, so StyleCop accepts instance fields
with and without `_`, which BRO1303 would unify), and SA1412 (UTF-8 with BOM) writes `charset = utf-8-bom`.
- `stylecop.json`: `elementOrder`, `usingDirectivesPlacement`, `systemUsingDirectivesFirst`,
`blankLinesBetweenUsingGroups`, `allowBuiltInTypeAliases`, indentation, the file header settings,
`documentationCulture` and `excludeFromPunctuationCheck`.
## What it writes
A block between `# BEGIN stylebro-migrate` and `# END stylebro-migrate` in each `.editorconfig` it changes. Running the
tool again replaces the block, so put your own settings outside it.
- **StyleBro rules.** A rule is on only when StyleCop enforced every rule it replaces, with the weakest of their
severities. [BRO1001](rules/BRO1001.md) replaces SA1201-SA1204 and SA1214 with one sort. A team that turned SA1201
off doesn't get the full sort, so BRO1001 stays off and the report lists SA1203/SA1204 as partly covered. Rules
that `stylecop.json` makes moot don't count: SA1203 when `elementOrder` leaves out `constant`, and so on. When
`elementOrder` leaves out `kind` or `accessibility`, BRO1001 stays off: it always sorts by both.
- **Private field naming.** [BRO1303](rules/BRO1303.md) uses `_camelCase` when SA1309 (no leading underscore) is off
and most of the repository's private fields start with `_`, and `camelCase` otherwise. Private constants and
`static readonly` fields are pinned to StyleCop's PascalCase (`stylebro_private_static_field_naming = PascalCase`,
[BRO1306](rules/BRO1306.md)), and private static fields that aren't readonly to BRO1303's style
(`stylebro_private_mutable_static_field_naming`): without these keys BRO1303 and BRO1306 would follow a camel-case
`dotnet_naming_rule` for static fields that StyleCop never enforced (IDE1006 off), and rename fields StyleCop was
happy with. With SA1309 off, `stylebro_allow_leading_underscore_in_pascal_fields = true` keeps constants, `static
readonly` and non-private fields that start with `_` (`_defaultSelector`): StyleCop's PascalCase rules only check
that the first character isn't lower-case, and the underscore is SA1309's.
- **SDK rules.** The rules StyleBro relies on (IDE0055 formatting, IDE0036 modifier order, IDE0049 type aliases, and so
on) get the strongest severity of the StyleCop rules they cover, with options from `stylecop.json`.
- **Every rule by its id.** Each StyleBro rule gets its own `dotnet_diagnostic.BROxxxx.severity` line, `none` included
(also the rules beyond StyleCop and those off by default): a bulk `dotnet_analyzer_diagnostic.severity = warning`
in your `.editorconfig` would otherwise turn them on.
- **File header.** With StyleCop's XML header and a `companyName`, [BRO1615](rules/BRO1615.md) is on and IDE0073 off;
with `xmlHeader: false`, IDE0073 (with `file_header_template`) and BRO1615 off. Never both: each would add its
header above the other's on every run. Without a `companyName` BRO1615 stays off (StyleCop's default company,
`PlaceholderCompany`, is nobody's real header). An `.editorconfig` value ends at a `;` or `#` and loses its leading
spaces, so the header settings are written with escapes BRO1615 reads back: `\n` for a line break, `\u003B` for `;`,
`\u0023` for `#`, `\u005C` for a backslash, `\u0020` for a space at either end (a GPL text's `WARRANTY;` used to cut
the rest of the license off). IDE0073's `file_header_template` has no escapes: a plain header with a `;` or `#` leaves
IDE0073 off, and the report says why.
- **Line endings.** `end_of_line` when your C# files clearly use one ending (judged like `init` does; files git
converts on checkout don't count, and when they're the majority nothing is written) and your `.editorconfig`
doesn't set it: without it `format` writes the OS's ending into the lines it changes. When your `.editorconfig` sets
`end_of_line` but git converts the files on checkout (a `.gitattributes` `text` attribute without `eol`, or
`core.autocrlf`), the endings are kept out of it: `end_of_line = unset` for C# files and IDE0055 as a suggestion (not
the error SA1500 would give it), so a Windows checkout neither fails the build nor gets rewritten; the report says how
to settle it.
- **CSharpier.** When the repository formats with [CSharpier](https://csharpier.com) (detected like `init` does, see
[getting started](getting-started.md)), IDE0055 and the same layout rules `init` turns off are off, so the two
formatters don't undo each other. The StyleCop rules only those replaced (SA1000-SA1028, SA1116, SA1117, ...) are
listed in the report under "CSharpier decides it" instead of being enforced.
- **The StyleCop version** is recorded in the block (`# StyleCop.Analyzers version: ...`), so running `--write` again
after removing the package still leaves the rules that version didn't have off.
- **A build that copies `.editorconfig`.** Some shared build setups copy an `.editorconfig` over the root one on every
build (SixLabors' shared infrastructure does). The report notes such a file: the block would be lost on the next
build, so put it into the copied file (or stop the copy).
- **Documentation scope.** `documentExposedElements`, `documentInternalElements` and `documentPrivateElements`
become [BRO1601](rules/BRO1601.md)'s `stylebro_document_*` settings.
- **Documentation language and punctuation.** A `documentationCulture` other than English (`de-DE`, `fr-FR`; any
`en-*` counts as English) turns off [BRO1604](rules/BRO1604.md)-[BRO1607](rules/BRO1607.md), which write English
summary sentences (StyleCop checks translated ones); the report says so. `excludeFromPunctuationCheck`, when it
differs from StyleCop's default (`["seealso"]`), becomes [BRO1603](rules/BRO1603.md)'s
`stylebro_exclude_from_punctuation_check`.
Every key the preset sets is written, and `--write` turns the preset off (`none` in
every `Directory.Build.props` the projects import: the root one, created if needed, and a nested one that doesn't import
the root's): the block replaces it. That matters because an `.editorconfig`
can't take a key back from the preset. Using order is the clearest case: `dotnet format` sorts `using` directives whenever
`dotnet_sort_system_directives_first` is set, even to `false` (so the preset doesn't set it), and the block sets it only when StyleCop sorted usings
(SA1208 or SA1210 on), and where a sub-directory or path section has both off, the keys go only into the sections that
sort (a scope can't unset the main block's keys). SDK settings your root
`.editorconfig` already sets for C# files are left out: your code is already formatted with them.
## Suppressions
With `--write`, StyleCop suppressions in the code and in MSBuild files (`` in `.csproj`, `.props`,
`.targets`) get the replacing rules too, so code your team deliberately exempted
stays exempt:
```csharp
#pragma warning disable SA1202, SA1642 // before
#pragma warning disable SA1202, SA1642, BRO1001, BRO1606 // after
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:PrefixLocalCallsWithThis", Justification = "...")]
[SuppressMessage("Style", "IDE0009", Justification = "...")] // added
```
```xml
$(NoWarn);SA1123;SA1600;BRO1113;BRO1601
```
The StyleCop suppressions stay: they're harmless once StyleCop is gone, and needed while both run. Running the tool
again adds nothing. Only files where StyleCop ran get them (when only some projects reference it): elsewhere the
generated settings turn no rule on. A suppression gets every rule that replaces the StyleCop one, also one that can't report on the
code it covers (SA1300 around a local function gets BRO1312, the namespace rule, too): an id that doesn't apply is
harmless, and the suppression stays right if the code changes. Rules the generated settings turn off get none (an
analyzer like StyleSharp's SST1462 reports a suppression of a rule that never reports). The count the tool prints is of rule ids added (a
`disable`/`restore` pair counts twice), and the preview counts the same way. Sonar's suppressions get the same treatment ([Coming from SonarQube](#coming-from-sonarqube)).
EF Core migrations are `dotnet ef`'s output: when the repository has them, the block marks their folders
`generated_code = true` (a section per folder, e.g. `[src/Ordering.Infrastructure/Migrations/**]`), so formatting
and StyleBro leave them alone.
## The report
The dry run lists the StyleCop rules that are on but that nothing enforces after the switch:
- **Partly covered or not expressible:** the StyleBro rule also enforces rules your team turned off (BRO1001 sorts by
kind, accessibility and static together, so SA1203/SA1204 alone can't be carried over), or a setting can't be
expressed for the SDK.
- **Dropped by design:** there's no safe automatic fix (the fix would be placeholder documentation, a file rename, or
a changed public API).
- **Not covered yet:** candidates for future StyleBro rules. See [stylecop-mapping.md](stylecop-mapping.md).
## Coming from StyleSharp
[StyleSharp.Analyzers](https://www.nuget.org/packages/StyleSharp.Analyzers) is a StyleCop successor with `SST` ids
(`SST1309`) and `stylesharp.*` settings in `.editorconfig`. `stylebro-migrate` reads it the way it reads StyleCop: a
project that references `StyleSharp.Analyzers` (a `PackageReference`, a central `PackageVersion`, an `` item or
`packages.config`) counts as running StyleCop, and each SST rule's severity (from `.editorconfig`, `.globalconfig` and
rulesets, plus StyleSharp's own defaults) counts as the StyleCop rule it stands for. Most SST rules kept StyleCop's
number (`SST1413` is SA1413); the exceptions:
| StyleSharp | Read as |
| --- | --- |
| SST1117 (`this.` as `stylesharp.instance_member_qualification` says) | SA1101 with `require_this`, StyleCop's SX1101 (no `this.`) with `omit_this`, StyleSharp's default |
| SST1309 (private fields are `_camelCase`) | StyleCop's SX1309 and SX1309S: BRO1303 keeps the `_` (`_camelCase`); SST1306 doesn't count then |
| SST1150-SST1171 (one per kind of list) | SA1117, on when all of them are |
| SST1214 and SST1215 (static and instance readonly fields first) | SA1214, on when both are |
| SST1633 (a header from `file_header_template`) | SA1633 with a plain header (IDE0073); nothing without `file_header_template` |
| SST1116, SST1119 (other rules at those numbers) | nothing; StyleCop's SA1116 and SA1119 count as off |
StyleSharp doesn't read `stylecop.json`, so neither does the migration in a StyleSharp repository (a leftover one is
named in the report). Its settings come from the `stylesharp.*` keys of the root `.editorconfig`'s sections for all C#
and the global configs: `instance_member_qualification`, `tuple_element_naming`, `allowed_hungarian_prefixes`,
`document_exposed_elements`/`document_internal_elements`/`document_private_elements`, and `file_header_template`; usings
go outside the namespace (SST1200). Suppressions of SST ids (`#pragma`, `[SuppressMessage]`, ``) are carried over
to the rules that replace the StyleCop rules they stand for, like StyleCop's. The block records the StyleSharp version
(`# StyleSharp.Analyzers version:`), so after the package swap a second `--write` still reads the setup as StyleSharp's.
The report lists the StyleSharp rules that are on and stand for no StyleCop rule (digit separators, complexity limits,
...): StyleBro doesn't replace them, so they stop when StyleSharp.Analyzers is removed. When StyleCop.Analyzers is
referenced too, a StyleCop rule counts as on when either package enforces it, and `stylecop.json`'s settings are used.
Data: `scripts/stylecop-survey/data/inventory-stylesharp-3.40.1.csv` (StyleSharp's rules, `Get-Inventory.ps1 -Package
StyleSharp.Analyzers`) and `stylesharp-mapping.csv` (written by `New-StyleSharpMapping.ps1`, which holds the exceptions).
**Using order.** When your `.editorconfig` sets `dotnet_sort_system_directives_first` (or
`dotnet_separate_import_directive_groups`), `dotnet format` sorts usings whatever the value. Files that aren't in that
order while your ordering rule (SA1208/SA1210, or StyleSharp's SST1208/SST1210, which compares case-sensitively) is on
and would report their sorted order are named in the report and in the block (`# Usings kept as they are ...`), and
`stylebro-migrate format` leaves them out, so it doesn't write what that rule reports. Files whose sorted order the rule
accepts (`Microsoft.*` above `System` under SA1210 alone: System first is fine with `systemUsingDirectivesFirst`) are
sorted, and the report says how many. Remove the line once that rule is gone to have them
sorted. The key itself is yours and stays.
**Settings without a package.** StyleCop or StyleSharp settings (`stylecop.json`, `SA`/`SST` severities) that no project
uses describe nothing the build enforces: the migration writes nothing and points to `stylebro-migrate init`, also when
the repository has a Sonar setup (init follows that too, and the code's own conventions).
## Coming from SonarQube
Many teams run SonarQube or SonarCloud next to (or instead of) StyleCop. `stylebro-migrate` and `stylebro-migrate init`
read that setup too and turn on the StyleBro and .NET rules that fix what the team's Sonar rules report, so
`stylebro-migrate format` cleans up findings Sonar can only list. Sonar stays: StyleBro doesn't turn any Sonar rule off,
and Sonar keeps reporting its own ids.
What it reads, in this order (later wins, like for StyleCop):
- **The base.** A quality profile exported from the server, when you pass one: `--sonar-profile profile.xml` (the XML from
the server's `api/qualityprofiles/backup?language=cs&qualityProfile=`, or Quality Profiles > Back up in the UI;
its `csharpsquid` rules are on). Without it, when a project references `SonarAnalyzer.CSharp` (`PackageReference`,
`GlobalPackageReference`, central package versions or `packages.config`), the package's own defaults ("Sonar way", from
SonarAnalyzer.CSharp 10.35's rule list, adjusted to the referenced version for the rules in the mapping: S8969 only from
10.30, S2325 on by default only from 9.32). A rule the referenced version doesn't have counts as off, also when a
ruleset or config sets it.
- **Rulesets** with Sonar rules (``): your own, SonarLint's
`.sonarlint/*csharp.ruleset`, and the scanner's `.sonarqube/conf/Sonar-cs.ruleset` when it's there (rulesets named
`*none*` are skipped).
- **Global configs** (`dotnet_diagnostic.Sxxxx.severity`); with several (one per project type) the strictest wins.
- **The root `.editorconfig`'s** sections for all C# files.
- **Sub-directory `.editorconfig` files and path sections** (like `[tests/**.cs]`) with Sonar severities are scopes, like
StyleCop's: each gets the fixing rules' lines that differ there, in its own file and section, so a Sonar rule lowered
for the tests (`S3878 = suggestion`) lowers the rule that fixes it there too, and one turned off turns it off. `init`
writes them as sections of its block in the root `.editorconfig`.
Without a Sonar package, a profile or Sonar rule ids in those files, nothing Sonar-related happens.
The Sonar rules that are on turn on these rules, at the strongest severity of the Sonar rules behind each. A mapping never
reports what the Sonar rule itself accepts (owner's policy): a rule that is stricter than Sonar's and can't be narrowed to it
isn't mapped.
| Sonar | Fixed by |
|---|---|
| S121 (braces) | [BRO1514](rules/BRO1514.md) with `csharp_prefer_braces = true` and `stylebro_braces_statements = if_and_loops` (not `using`, `lock`, `fixed` or a deconstructing `foreach`, like S121) |
| S818 (upper-case literal suffixes) | [BRO1135](rules/BRO1135.md) with `stylebro_upper_case_literal_suffixes = lone_l` (only a suffix that is just `l`, like S818) |
| S927 (parameter names match the base) | [BRO1313](rules/BRO1313.md) with `stylebro_match_base_parameter_names = non_generic_methods` (methods only, and not a parameter whose type in the base is a type parameter the method fills in, like S927) |
| S1066 (mergeable `if`s) | [BRO1149](rules/BRO1149.md) with `stylebro_allow_nested_dynamic_ifs = true` (conditions with a `dynamic` part aren't merged, like S1066) |
| S1116 (empty statements) | [BRO1101](rules/BRO1101.md) with `stylebro_allow_semicolon_after_closing_brace = true` (the `;` after a type's `}` stays, like S1116) |
| S1659 (one variable per declaration) | [BRO1142](rules/BRO1142.md) |
| S1939 (redundant base types) | [BRO1408](rules/BRO1408.md) |
| S2971 (LINQ predicate into `Count`/`Any`/...) | BRO1150 |
| S3878 (arrays for `params`) | [BRO1151](rules/BRO1151.md) with `stylebro_params_arrays = except_object_and_jagged` (not for `params object[]`, `params Array[]` or jagged arrays, like S3878) |
| S4136 (overloads together) | [BRO1001](rules/BRO1001.md)'s `stylebro_keep_overloads_together = same_access` (overloads with the same access, like S4136), only when BRO1001 is on |
| S8969 (redundant `!`) | [BRO1147](rules/BRO1147.md) with `stylebro_allow_null_forgiving_on_unannotated = true` (a `!` on a value declared without nullable annotations stays, like S8969) |
**Not covered** (the report lists each under "Not covered" with its reason; they stay Sonar's). The fixing rule reports what
the Sonar rule accepts, and has no setting that narrows it. (BRO1101, BRO1135, BRO1147, BRO1149, BRO1151 and BRO1514 got
one each for S1116, S818, S8969, S1066, S3878 and S121, the owner's decision of 2026-10-12; the .NET SDK's rules can't be
changed.)
| Sonar | Rule | What it reports that Sonar accepts |
|---|---|---|
| S100, S101 (PascalCase names) | BRO1309 | events, enum members, enums, delegates, names with `_` inside, test classes (`fooTests`) |
| S1118 (utility classes) | CA1052 | a private or protected constructor, a singleton member, a non-static nested type |
| S1125 (redundant boolean literals) | IDE0075, IDE0100 | constants and `default`, `c ? true : true`, a comparison inside a conditional with a throw |
| S1481, S1854 (unused locals and assignments) | IDE0059 | initial values like `0`, `null`, `""`, stores to captured variables, dead stores to used variables |
| S1905 (redundant casts) | IDE0004 | upcasts and numeric conversions Sonar doesn't call redundant (Autofac: `(Service)`, `(int)`) |
| S2325 (members that could be static) | CA1822 | members with attributes, empty and recursive methods, events, members of generic types |
| S3052 (initialized to the default value) | CA1805 | `default`, `new Guid()`, an enum set to `0`, a nullable value type set to `null` |
| S3260 (seal private classes) | CA1852 | its fix also seals private nested classes that have `protected` members, and every such member then gets CS0628 (SmartEnum: 144 warnings) |
| S3442 (public constructors of abstract classes) | CA1012 | a constructor in another part of a partial abstract class |
**Sonar's own warnings for S1066, S2971 and S3878** are fixed too when the project references SonarAnalyzer.CSharp:
BRO1149-BRO1151's fixes are registered for those ids, so `dotnet format` (or a light bulb) applies them to Sonar's
diagnostics, at the places the StyleBro rule would fix; where it skips the code (a comment in the merged text, an array of a
derived type, Sonar's other S2971 cases, ...) the Sonar warning stays. This needs the package in the build: findings only
the scanner reports in CI aren't seen by `dotnet format`. Checked against SonarAnalyzer.CSharp 10.35.0.4138
(`scripts/sonar-interop.ps1`). Sonar reports more than the StyleBro rules fix in places: S3878 on an array spread over several lines
(BRO1151 leaves those, see its page), S8969 on a `!` that hides a nested nullability mismatch or in a multi-targeted
project (BRO1147 leaves those); those warnings stay.
The data lives in the tool: `src/StyleBro.Migrate/data/sonar-mapping.tsv` (the mapping) and
`sonar-rules-10.35.0.4138.tsv` (Sonar's rule list and defaults, public metadata only).
**With StyleCop too**, a rule is on when either setup turns it on, at the stronger severity: the migration normally turns
the rules beyond StyleCop off (BRO1142, BRO1313, ...), but not when the matching Sonar rule is on. A setting
Sonar's rule needs replaces the one from the StyleCop setup, and the report lists each such conflict. A `stylebro_` setting that
only narrows a rule to the Sonar rule's cases isn't written when the StyleCop setup turns that rule on (SA1106 also wants
the `;` after a type's `}` gone, SA1503 braces on `using`): StyleCop's wider scope stays, and the report says so. An option of a rule StyleCop's setup leaves off
isn't applied: S4136 only sets an option of BRO1001, so with SA1201 or SA1202 off it stays Sonar's alone.
The report has a Sonar part: what was read, how many Sonar rules are on, which of them StyleBro or the SDK now fixes (and
with which rule), which mapped ones aren't applied and why, which have no safe fix (S3260, ...), and that the rest stay Sonar's (no safe automatic fix: unused
private members, cognitive complexity, ...). The generated block has the Sonar-driven lines last, under
`# From the SonarQube setup`, each with the Sonar rule's title. `--diff` prints the same part of the report before its
summary. `init` does the same in a repository without StyleCop, and prints it.
Suppressions of Sonar ids are carried over like StyleCop's ([Suppressions](#suppressions)): `#pragma warning disable
S1659` gets `BRO1142`, `[SuppressMessage("...", "S927:...")]` a sibling for `BRO1313`, `` the replacing ids, so
code where a team suppressed a Sonar rule doesn't get the fix (`init --write` does this too when it finds a Sonar
setup). A Sonar rule that only sets an option is different: S4136 (overloads together) in a `` gets a section
for that folder with `stylebro_keep_overloads_together = false`, and BRO1001 stays on there. Limits: Sonar's rule parameters (`SonarLint.xml`) aren't read.
## Modernizing afterwards
StyleCop has no rules for newer C# or APIs, so the migration turns none on. To add the SDK's modernization rules
(their own block, kept when you run `stylebro-migrate --write` again): `stylebro-migrate init --modernize --write`,
see [modernizing.md](modernizing.md).
## Limits
- Global configs (`.globalconfig`) apply to every project here; with several, the strictest wins. Per-project rulesets
and `stylecop.json` files are mapped (above), except for projects that share a folder.
- StyleCop's XML file header (``, the default for SA1633) becomes [BRO1615](rules/BRO1615.md) with
stylecop.json's `companyName`, `copyrightText` (custom `variables` filled in) and `headerDecoration`; a plain
header (`"xmlHeader": false`) becomes IDE0073's `file_header_template`. BRO1615 is one rule for SA1633-SA1641, so
it's off when any of them is off. With a plain header StyleCop's SA1633 only asks for a header and SA1636 compares
its text (line by line, ignoring leading and trailing spaces; only while SA1635 is on too). IDE0073 always compares
the text, so it's on only when SA1633, SA1635 and SA1636 all are.
---
Source: https://bisforboman.github.io/stylebro/rules/
# Rules
Every StyleBro rule, by area. Each one has a code fix that `dotnet format` applies (Fix All included), and each page
has an example, the reasons, the configuration and how it differs from StyleCop.
**Default**: *on* means the rule reports as a warning with the package's preset; *off* rules are opt-in
(`dotnet_diagnostic.BROxxxx.severity = warning` in `.editorconfig`). `stylebro-migrate` sets each rule from your
StyleCop setup instead, and turns off the rules StyleCop doesn't have.
Where the rules and the preset differ from StyleCop: [differences-from-stylecop.md](../differences-from-stylecop.md).
How every StyleCop rule maps to StyleBro or the SDK: [stylecop-mapping.md](../stylecop-mapping.md), and the ones
StyleBro leaves out: [skipped-rules.md](../skipped-rules.md).
**Files from NuGet packages** aren't checked: a source package's files (`contentFiles`, or files a package's build
targets compile) live in the package folder, where nobody can fix them and `dotnet format` would edit the shared
package cache. The package passes the restore's package folders (`NuGetPackageFolders`) to `PackageFileSuppressor`,
which hides every StyleBro finding there; without them (no package build targets), a path with a `contentFiles`
folder counts. Shared source linked from elsewhere in the repository is still checked.
**Source generator output** isn't checked either: `dotnet format` can't fix or report a generated document, and the
generator writes it again on every build. Roslyn only treats a file as generated by its name (`.g.cs`) or an
`` header, which generators don't have to write. The compiler puts every generated file under the
project's `obj` folder (or `CompilerGeneratedFilesOutputPath` with `EmitCompilerGeneratedFiles`); the package passes
both to `GeneratedFileSuppressor`, which hides every StyleBro finding there; without them, a path with an `obj` folder
counts.
## Ordering, spacing and comments
| ID | Title | Replaces | Default |
|----|-------|----------|---------|
| [BRO1001](BRO1001.md) | Members should be ordered | SA1201-SA1204, SA1214 | on |
| [BRO1002](BRO1002.md) | Single-line comments should begin with a space | SA1005 | on |
| [BRO1003](BRO1003.md) | Property accessors should follow order | SA1212 | on |
| [BRO1004](BRO1004.md) | Event accessors should follow order | SA1213 | on |
| [BRO1005](BRO1005.md) | Documentation lines should begin with single space | SA1004 | on |
| [BRO1006](BRO1006.md) | Preprocessor keywords should not be preceded by a space | SA1006 | on |
| [BRO1007](BRO1007.md) | Partial elements should declare an access modifier | SA1205 | on |
| [BRO1008](BRO1008.md) | Using directives should be placed correctly | SA1200 | on |
## Readability
| ID | Title | Replaces | Default |
|----|-------|----------|---------|
| [BRO1101](BRO1101.md) | Code should not contain empty statements | SA1106 | on |
| [BRO1102](BRO1102.md) | Each attribute should be in its own brackets | SA1133 | on |
| [BRO1103](BRO1103.md) | Constants should be on the right-hand side of comparisons | SA1131 | on |
| [BRO1104](BRO1104.md) | Use default instead of a value type's default constructor | SA1129 | on |
| [BRO1105](BRO1105.md) | Constructor initializers should be on their own line | SA1128 | on |
| [BRO1106](BRO1106.md) | Use string.Empty for empty strings | SA1122 | on |
| [BRO1107](BRO1107.md) | Split parameters should start on the line after the declaration | SA1116 | on |
| [BRO1108](BRO1108.md) | Parameters should be on the same line or on separate lines | SA1117 | on |
| [BRO1109](BRO1109.md) | Opening parenthesis or bracket should be on the declaration line | SA1110 | on |
| [BRO1110](BRO1110.md) | Closing parenthesis or bracket should be on the line of the last item | SA1111 | on |
| [BRO1111](BRO1111.md) | Generic type constraints should be on their own line | SA1127 | on |
| [BRO1112](BRO1112.md) | Do not use regions | SA1124 | off in the preset |
| [BRO1113](BRO1113.md) | Regions should not be placed inside code elements | SA1123 | on |
| [BRO1114](BRO1114.md) | Do not combine fields | SA1132 | on |
| [BRO1115](BRO1115.md) | Use shorthand for nullable types | SA1125 | on |
| [BRO1116](BRO1116.md) | Closing parenthesis should be on line of opening parenthesis | SA1112 | on |
| [BRO1117](BRO1117.md) | Comma should be on the same line as previous parameter | SA1113 | on |
| [BRO1118](BRO1118.md) | Parameter list should follow declaration | SA1114 | on |
| [BRO1119](BRO1119.md) | Parameter should follow comma | SA1115 | on |
| [BRO1120](BRO1120.md) | Comments should contain text | SA1120 | on |
| [BRO1121](BRO1121.md) | Enum values should be on separate lines | SA1136 | on |
| [BRO1122](BRO1122.md) | Use literal suffix notation instead of casting | SA1139 | on |
| [BRO1123](BRO1123.md) | Use tuple syntax | SA1141 | on |
| [BRO1124](BRO1124.md) | Refer to tuple elements by name | SA1142 | on |
| [BRO1125](BRO1125.md) | Use lambda syntax | SA1130 | on |
| [BRO1126](BRO1126.md) | Using directives should be qualified | SA1135 | on |
| [BRO1127](BRO1127.md) | Query clause should follow previous clause | SA1102 | on |
| [BRO1128](BRO1128.md) | Query clauses should be on separate lines or all on one line | SA1103 | on |
| [BRO1129](BRO1129.md) | Query clause should begin on new line when previous clause spans multiple lines | SA1104 | on |
| [BRO1130](BRO1130.md) | Query clauses spanning multiple lines should begin on own line | SA1105 | on |
| [BRO1131](BRO1131.md) | Do not prefix calls with base unless local implementation exists | SA1100 | on |
| [BRO1132](BRO1132.md) | Block statements should not contain embedded comments | SA1108 | on |
| [BRO1133](BRO1133.md) | Check for null in one form | (none; Roslynator RCS1248) | on |
| [BRO1134](BRO1134.md) | Declarations should not contain embedded comments | (none; StyleCop issue #605) | on |
| [BRO1135](BRO1135.md) | Integer literal suffixes should be upper case | (none; Sonar S818) | on |
| [BRO1136](BRO1136.md) | A lambda's single parameter should not be in parentheses | (none; StyleCop issue #762) | on |
| [BRO1137](BRO1137.md) | Remove a redundant 'return;' or 'yield break;' | (none; StyleCop issue #760, Roslynator RCS1134) | on |
| [BRO1138](BRO1138.md) | A string literal should be a plain string when nothing needs more | (none; Roslynator RCS1214/RCS1192/RCS1262) | on |
| [BRO1139](BRO1139.md) | Write 'else if' on one line | (none; Roslynator RCS0041/RCS1006) | on |
| [BRO1140](BRO1140.md) | A record with an empty body should end with ';' | (none; Roslynator RCS1251) | on |
| [BRO1141](BRO1141.md) | Object creation with an initializer: parentheses in one style | (none; Roslynator RCS1050) | on |
| [BRO1142](BRO1142.md) | Do not combine local variables | (none; Roslynator RCS1081) | on |
| [BRO1143](BRO1143.md) | No 'else' after a branch that ends in a jump | (none; Meziantou MA0071, Roslynator RCS1211) | off |
| [BRO1144](BRO1144.md) | Escape identifiers that C# 14 reads as keywords | (none; Sonar S8367/S8368/S8380, no fix) | on |
| [BRO1145](BRO1145.md) | A class, struct or interface with an empty body should end with ';' | (none; Meziantou MA0206) | off |
| [BRO1146](BRO1146.md) | Write 'record' without 'class' | (none; Meziantou MA0174) | on |
| [BRO1147](BRO1147.md) | No redundant null-forgiving `!` | (none; Sonar S8969, IDE0370) | off |
| [BRO1148](BRO1148.md) | `x is not null` instead of `x.HasValue` | (none; Meziantou MA0171) | off |
| [BRO1149](BRO1149.md) | Merge an 'if' into the enclosing 'if' | (none; Sonar S1066) | on |
| [BRO1150](BRO1150.md) | Pass the predicate to the LINQ call instead of calling Where first | (none; Sonar S2971) | on |
| [BRO1151](BRO1151.md) | Pass the elements, not an array, to a params parameter | (none; Sonar S3878) | on |
## Naming
| ID | Title | Replaces | Default |
|----|-------|----------|---------|
| [BRO1301](BRO1301.md) | Variable names should begin with a lower-case letter | SA1312 | on |
| [BRO1302](BRO1302.md) | Parameter names should begin with a lower-case letter | SA1313 | on |
| [BRO1303](BRO1303.md) | Private field names should be camelCase | SA1306, SA1309 | on |
| [BRO1304](BRO1304.md) | Interface names should begin with I | SA1302 | on |
| [BRO1305](BRO1305.md) | Type parameter names should begin with T | SA1314 | on |
| [BRO1306](BRO1306.md) | Constant, static readonly and non-private field names should be PascalCase | SA1303, SA1304, SA1307, SA1311 | on |
| [BRO1307](BRO1307.md) | Field names should not begin with a prefix | SA1308 | on |
| [BRO1308](BRO1308.md) | Field names should not contain an underscore | SA1310 | on |
| [BRO1309](BRO1309.md) | Element names should begin with an upper-case letter | SA1300 | on |
| [BRO1310](BRO1310.md) | Field names should not use Hungarian notation | SA1305 | off |
| [BRO1311](BRO1311.md) | Tuple element names should use correct casing | SA1316 | on |
| [BRO1312](BRO1312.md) | Namespace names should begin with an upper-case letter | SA1300 | off |
| [BRO1313](BRO1313.md) | Parameter names should match the base member | (none; SA1315 proposed, never implemented; SDK CA1725) | off |
| [BRO1314](BRO1314.md) | Asynchronous method names should end with Async | (none; Roslynator RCS1046, Meziantou MA0137) | off |
## Maintainability
| ID | Title | Replaces | Default |
|----|-------|----------|---------|
| [BRO1401](BRO1401.md) | Use a trailing comma in multi-line initializers | SA1413 | on |
| [BRO1402](BRO1402.md) | Attribute constructor should not use unnecessary parenthesis | SA1411 | on |
| [BRO1403](BRO1403.md) | Remove delegate parenthesis when possible | SA1410 | on |
| [BRO1404](BRO1404.md) | Access modifier should be declared | SA1400 | on |
| [BRO1405](BRO1405.md) | Statement should not use unnecessary parenthesis | SA1119 | on |
| [BRO1406](BRO1406.md) | Arithmetic expressions should declare precedence | SA1407 | on |
| [BRO1407](BRO1407.md) | Conditional expressions should declare precedence | SA1408 | on |
| [BRO1408](BRO1408.md) | Remove a redundant base type | (none; Roslynator RCS1042) | on |
| [BRO1409](BRO1409.md) | Methods of internal types should be internal, not public | (none; StyleCop issue #2981, never implemented) | off |
| [BRO1410](BRO1410.md) | Patterns should not use unnecessary parentheses | (none; SA1119 checks expressions only) | on |
## Layout
| ID | Title | Replaces | Default |
|----|-------|----------|---------|
| [BRO1501](BRO1501.md) | Opening braces should not be preceded by a blank line | SA1509 | on |
| [BRO1502](BRO1502.md) | Chained blocks should not be preceded by a blank line | SA1510 | on |
| [BRO1503](BRO1503.md) | Opening braces should not be followed by a blank line | SA1505 | on |
| [BRO1504](BRO1504.md) | Single-line comments should be preceded by a blank line | SA1515 | on |
| [BRO1505](BRO1505.md) | Elements should be separated by a blank line | SA1516 | on |
| [BRO1506](BRO1506.md) | Single-line comments should not be followed by a blank line | SA1512 | on |
| [BRO1507](BRO1507.md) | Files should not end with blank lines | SA1518 | on |
| [BRO1508](BRO1508.md) | A block should not be on a single line | SA1501 | on |
| [BRO1509](BRO1509.md) | An element should not be on a single line | SA1502 | on |
| [BRO1510](BRO1510.md) | Accessors should all be single-line or all multi-line | SA1504 | on |
| [BRO1511](BRO1511.md) | Element documentation headers should not be followed by blank line | SA1506 | on |
| [BRO1512](BRO1512.md) | While-do footer should not be preceded by blank line | SA1511 | on |
| [BRO1513](BRO1513.md) | Element documentation header should be preceded by blank line | SA1514 | on |
| [BRO1514](BRO1514.md) | Braces should not be omitted | SA1503 | on |
| [BRO1515](BRO1515.md) | Braces should not be omitted from multi-line child statement | SA1519 | on |
| [BRO1516](BRO1516.md) | Use braces consistently | SA1520 | on |
| [BRO1517](BRO1517.md) | Code should not contain multiple blank lines in a row | SA1507 | on |
| [BRO1518](BRO1518.md) | Closing braces should not be preceded by blank line | SA1508 | on |
| [BRO1519](BRO1519.md) | Closing brace should be followed by blank line | SA1513 | on |
| [BRO1520](BRO1520.md) | Place the operator consistently when an expression wraps | (none; Roslynator RCS0027/RCS0028) | on |
| [BRO1521](BRO1521.md) | Place '=>' consistently when an expression body wraps | (none; Roslynator RCS0032) | on |
| [BRO1522](BRO1522.md) | Place '=' consistently when an assignment wraps | (none; Roslynator RCS0052) | on |
| [BRO1523](BRO1523.md) | Each call of a split call chain starts its own line | (none; Roslynator RCS0054) | on |
| [BRO1524](BRO1524.md) | A split conditional expression has the condition, '?' and ':' parts on their own lines | (none; StyleCop issue #651) | on |
| [BRO1525](BRO1525.md) | Attributes should not be followed by a blank line | (none; SA1521 proposed, never implemented) | on |
| [BRO1526](BRO1526.md) | Blank line between switch sections | (none; Roslynator RCS0061) | on |
| [BRO1527](BRO1527.md) | Auto-accessors should be on one line | (none; Roslynator RCS0042) | on |
## Documentation
| ID | Title | Replaces | Default |
|----|-------|----------|---------|
| [BRO1601](BRO1601.md) | Overrides and implementations should inherit their documentation | SA1600 (these members) | on |
| [BRO1602](BRO1602.md) | Single-line comments should not use documentation style slashes | SA1626 | on |
| [BRO1603](BRO1603.md) | Documentation text should end with a period | SA1629 | on |
| [BRO1604](BRO1604.md) | Property summary documentation should match accessors | SA1623 | on |
| [BRO1605](BRO1605.md) | Property summary documentation should omit accessor with restricted access | SA1624 | on |
| [BRO1606](BRO1606.md) | Constructor summary documentation should begin with standard text | SA1642 | on |
| [BRO1607](BRO1607.md) | Destructor summary documentation should begin with standard text | SA1643 | on |
| [BRO1608](BRO1608.md) | Void return value should not be documented | SA1617 | on |
| [BRO1609](BRO1609.md) | Do not use placeholder elements | SA1651 | on |
| [BRO1610](BRO1610.md) | Documentation text should not be empty | SA1627 | on |
| [BRO1611](BRO1611.md) | Element parameter documentation should match element parameters | SA1612 | on |
| [BRO1612](BRO1612.md) | Element parameter documentation should declare parameter name | SA1613 | on |
| [BRO1613](BRO1613.md) | Generic type parameter documentation should match type parameters | SA1620 | on |
| [BRO1614](BRO1614.md) | Generic type parameter documentation should declare parameter name | SA1621 | on |
| [BRO1615](BRO1615.md) | File should have the XML copyright header (needs `stylebro_file_header_company`) | SA1633-SA1638, SA1640, SA1641 | on |
| [BRO1616](BRO1616.md) | Write the summary's tags consistently on their own lines or on the text's line | (none; Roslynator RCS1253, Meziantou MA0177/MA0211) | on |
| [BRO1617](BRO1617.md) | Write a cref's type arguments in braces | (none; StyleCop's proposed SA1653) | on |
| [BRO1618](BRO1618.md) | Write a C# keyword in documentation as `` | (none; Meziantou MA0154) | on |
| [BRO1619](BRO1619.md) | Put the documentation elements in the standard order | (none) | on |
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1001/
# BRO1001: Members should be ordered
Members of a class, struct, record or interface, and the types and namespaces of a namespace or file, should appear in
a fixed order: by kind, then accessibility, then constant/static/readonly. Replaces StyleCop **SA1201, SA1202, SA1203,
SA1204 and SA1214**.
## Example
### Before
```csharp
using System.Collections.Generic;
public class Order
{
public void Ship()
{
}
private readonly List _lines = new();
public Order(string id)
{
Id = id;
}
public string Id { get; }
}
```
### After
```csharp
using System.Collections.Generic;
public class Order
{
private readonly List _lines = new();
public Order(string id)
{
Id = id;
}
public string Id { get; }
public void Ship()
{
}
}
```
## Why
When every type follows the same order, you know where to look: state first, then construction, then the API. It
also keeps reviews focused, because nobody argues about where a new member goes.
## The order
Members are sorted by these keys, in this order:
1. **Kind**: field, constructor, finalizer, delegate, event, enum, interface, property, indexer, conversion, operator,
extension, method, struct, class
2. **Accessibility**: public, internal, protected internal, protected, private protected, private. A static
constructor counts as public, like in StyleCop.
3. **Constants** before other fields
4. **Static** before instance
5. **Readonly** fields before mutable fields
Members with equal keys keep their existing relative order. These are StyleCop's defaults.
**C# 14 extension blocks** (`extension(string s) { ... }`) are the kind `extension`, right before the methods, and
their members are sorted inside them by the same keys (a member without an access modifier is private there, like
anywhere in a class). StyleCop has no opinion on them; before, a type with an extension block wasn't sorted at all.
```csharp
public static class TextExtensions
{
private const int Limit = 10;
public static string Name => nameof(TextExtensions);
extension(string s)
{
public int Size => s.Length;
public bool IsShort() => s.Length < Limit;
}
public static int Twice(int x) => x * 2;
}
```
In a namespace (block or file-scoped) or at the top of a file, the same keys order its namespaces and types: namespaces
first, then delegates, enums, interfaces, structs and classes (records count as classes or structs). A type without an
access modifier is internal there, and a part of a partial type without one counts as what its other parts declare
(so the order doesn't change once [BRO1007](BRO1007.md) adds the modifier).
## Configuration
```ini
[*.cs]
stylebro_member_order = field, constructor, property, method
stylebro_member_access_order = public, internal, protected, private
stylebro_member_constants_first = true
stylebro_member_static_first = true
stylebro_member_readonly_first = true
stylebro_keep_overloads_together = false # true, false, same_access
```
Kinds and accessibilities you leave out keep their default relative order, after the ones you list. A configured
list written before extension blocks existed (without `extension`) puts them after every listed kind. `conversion` means
conversion operators (`implicit operator`/`explicit operator`); `operator` means all other operators.
### Overloads together
`stylebro_keep_overloads_together = true` keeps methods with the same name next to each other. The group goes where its first overload goes under the keys above (the
one that sorts first; of equal ones, the one that comes first), and the other overloads follow it in their own order:
```csharp
public void Load(int id) { } // public: the group's place
private void Load(string name) { }
public void Save() { }
```
With the default `false`, the private `Load` goes after every public method, like in StyleCop. Only methods are
grouped, within their region: constructors and indexers are kinds of their own, so they are always together, and
operators aren't grouped by their symbol (pairs like `==` and `!=` stay as written). An explicit interface
implementation groups with the other implementations of that interface's method, not with a public method of the same
name.
`stylebro_keep_overloads_together = same_access` is exactly Sonar's [S4136](https://rules.sonarsource.com/csharp/RSPEC-4136/):
only overloads with the same accessibility, both `static` or both not, and both `abstract` or both not, form a group.
The usual `public void Dispose()` and `private void Dispose(bool disposing)` may then sit apart (S4136 doesn't flag
them), each sorted by its own access, while two public `Find` overloads stay together:
```csharp
public void Dispose() { Dispose(true); }
public int Find(int id) => id;
public int Find(string name) => name.Length;
private void Dispose(bool disposing) { }
```
## How the fix works
The fix sorts the whole type, reported once per type on the first member that is out of place. The blank-line layout
stays in place, while comments, XML docs and attributes move with their member. A member with a `//` comment above it
that lands directly below another member gets a blank line above the comment, so the sort doesn't leave a comment
glued to the code above it ([BRO1504](BRO1504.md)). Nested types are sorted too, so one
`dotnet format analyzers --diagnostics BRO1001` run is enough.
Before sorting, the fix makes what [BRO1505](BRO1505.md) and [BRO1509](BRO1509.md) would: the blank lines BRO1505 wants
between the type's members, and the expansion of a type written on one line. Since blank lines stay with their place,
sorting first and adding them afterwards would give other text than the other way round. With both rules off, a type on
one line is sorted on that line.
If the project uses `TreatWarningsAsErrors`, don't exempt the rule with `BRO1001`:
`dotnet format` then skips BRO1001 entirely and fixes nothing, although the build still reports it. Set the severity in
`.editorconfig` instead (`dotnet_diagnostic.BRO1001.severity = suggestion`), or run `dotnet format` before building.
## Regions
Members are sorted within each region: every `#region` or `#endregion` line between members is a fence, and the
members between two fences are sorted on their own. The region lines, and comments above them, stay where they are, so
every region keeps its members. `#pragma` and `#nullable` lines are fences too, so every member keeps the warnings and
nullable context it had. The order across regions isn't checked (StyleCop's is): fixing it would mean moving
members into another region. A doc comment that moves right below a region line gets a blank line above it
([BRO1513](BRO1513.md)).
With [BRO1112](BRO1112.md) on, a type with regions between its members isn't reported: BRO1112's fix removes the
regions and sorts the members across them in the same step. Sorting within the regions first could end differently
(a commented member moved next to one of its kind looks like the start of a group, which the sort then leaves alone).
## Not reported
- **Types with `#if`, `#elif`, `#else` or `#endif` between members**, and namespaces or files with them between types.
In a multi-targeted project each target framework sees other members there, and the copies of the file would need
different edits. Also a doc comment above a directive, which would stay with the position instead of its member.
- **Members out of order across regions** (see Regions).
- **Types whose member order the runtime sees**, when sorting would change it: instance fields of a struct (sequential
layout is the default) or of a type with `[StructLayout]` (memory layout for interop, `unsafe` code and `Marshal`), and
fields and properties of a type with a serializer attribute on it or on a member (Json.NET, DataContract,
XmlSerializer, MessagePack, protobuf write members in declaration order). Found on Newtonsoft.Json, whose tests
compare JSON text. Other members of such a type are still sorted when that keeps the order of these. A class that is
serialized without any attribute can't be recognized and is sorted (StyleCop's fix reorders those too).
- **Containers where a member led by a group comment would move.** Comments move with the member below them, which is
wrong for a `//` comment that introduces a group: it would end up above part of the group, away from the rest. A
comment counts as a group comment when the next member is of the same kind (found in Scrutor):
```csharp
// Test interfaces and classes
public interface IService { }
public interface IOtherService { }
public class Service : IService { }
```
Sorted, the comment would sit above the interfaces only. Blank lines don't decide it: other rules add and remove
them in the same `dotnet format` run. A comment above a member whose next member is of another kind moves with that
member, and a group comment whose member keeps its place doesn't stop the sort elsewhere in the type.
- **Containers where a member led by a comment below the previous member would move.** A run of `//` lines right
below a line ending in `;`, followed by a blank line, describes the member above it (found in eShop), although the
compiler attaches it to the member below. [BRO1504](BRO1504.md) and [BRO1506](BRO1506.md) leave its blank lines
alone, so it stays put, and the sort would carry it away with the wrong member:
```csharp
private static RedisKey BasketKeyPrefix = "/basket/"u8.ToArray();
// note on UTF8 here: library limitation (to be fixed) - prefixes are more efficient as blobs
private static RedisKey GetBasketKey(string userId) => BasketKeyPrefix.Append(userId);
```
- **Files whose first type carries the file header**: with no `using` or namespace before it, the header comment
belongs to the first type and would move with it.
- **Files with top-level statements.**
- **Types where sorting would change what field initializers compute.** Initializers run in source order, so moving
`B` above `A` here (readonly first) would make `B` 1 instead of 6:
```csharp
public static class C
{
public static int A = 5;
public static readonly int B = A + 1;
}
```
A type is skipped when sorting would swap two static initializers, or two instance initializers, and either of them
mentions a member of the type (directly or through a method), or creates an instance of the type (`new()` in a
singleton). `typeof`, `nameof` and names on other objects (`text.Length`) don't count. Side effects outside the type,
such as two initializers calling the same external counter, aren't detected.
## Reported but not fixed
**A type whose members code reads by position through reflection** keeps its order when sorting would change what that
code reads: `GetFields`, `GetProperties`, `GetEvents` or `GetMembers` with an index, `First()`, `Last()` or
`ElementAt()`, directly or on a local holding the result, on `typeof(T)`, `new T()`, `this` or a variable declared as
`T` (`GetType()`), anywhere in the solution. Reflection returns members in declaration order (found on Stateless: its
tests read an event's backing field as `GetFields(...)[0]` and a list as `[1]`, and 5 failed after the sort). Only
the fix sees the other projects, so the finding stays, with no code action; `stylebro-migrate format` lists it with
where the read is ([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)). The type is matched by
its simple name. Other members are still sorted when the read ones keep their relative order (all members for
`GetMembers`). A serializer that reads members by reflection without a positional read in the code (Json.NET's
`DefaultMembersSearchFlags` on an attribute-less class) can't be recognized.
## Compared with StyleCop
StyleCop reports every misplaced member; BRO1001 reports each type (or namespace, or file) once and fixes all of it. StyleCop doesn't
skip types where sorting changes initializer results. The member order itself is the same, checked pairwise for all
14 member kinds against StyleCop 1.1.118.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1002/
# BRO1002: Single-line comments should begin with a space
A `//` comment should have a space between the slashes and the text. Replaces StyleCop **SA1005**.
## Example
### Before
```csharp
public class Invoice
{
//Total including VAT.
public decimal Total { get; set; }
public decimal Net() => Total / 1.25m; //VAT is 25%
}
```
### After
```csharp
public class Invoice
{
// Total including VAT.
public decimal Total { get; set; }
public decimal Net() => Total / 1.25m; // VAT is 25%
}
```
## Why
Comments read like prose, and `// note` is how almost all C# code writes them. Consistent spacing also makes
commented-out code (`////`) and separator lines (`//----`) stand out from real comments.
## How the fix works
The fix inserts a space. Tabs and other whitespace after `//` become a single space, and a comment with nothing but
whitespace becomes `//`.
## Not reported
- **Comments that already start with whitespace**, including `// two spaces` for indented text.
- **An empty comment** (`//`).
- **`///` and `////`**, which are used for documentation and commented-out code.
- **Separator lines starting with `//--`**, such as `//-----------`.
- **`dotnet new` template markers starting with `//-:` or `//+:`** (`//-:cnd:noEmit`), which a space would break.
- **Text in inactive `#if` blocks**, which isn't a comment to the compiler.
## Compared with StyleCop
Same results as StyleCop 1.2. StyleCop 1.1.118 also reported `// two spaces`. For a comment with only whitespace,
StyleCop's fix leaves a trailing space (`// `); BRO1002 writes `//`. StyleCop reports the template markers `//-:` and
`//+:` and its fix breaks them ([#2689](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2689), open).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1003/
# BRO1003: Property accessors should follow order
A property's or indexer's `get` accessor comes before its `set` or `init` accessor. Replaces StyleCop **SA1212**.
## Example
### Before
```csharp
public class Account
{
private decimal balance;
public decimal Balance
{
set { this.balance = value; }
get { return this.balance; }
}
public string Owner { set; get; }
}
```
### After
```csharp
public class Account
{
private decimal balance;
public decimal Balance
{
get { return this.balance; }
set { this.balance = value; }
}
public string Owner { get; set; }
}
```
## Why
Reading comes before writing everywhere else in .NET, so `get` first is what readers look for.
## How the fix works
The fix swaps the two accessors. When each has its own lines, the comments above an accessor move with it and blank
lines stay where they are. Auto-properties (`{ set; get; }`) count too, like in StyleCop.
## Not reported
- **An accessor that shares a line with other code** (for example `{ set` on the line of the opening brace), when the
accessors aren't all on one line: the fix couldn't tell what belongs to it.
## Compared with StyleCop
Same positions and the same fixed output as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1004/
# BRO1004: Event accessors should follow order
An event's `add` accessor comes before its `remove` accessor. Replaces StyleCop **SA1213**.
## Example
### Before
```csharp
using System;
public class Button
{
private EventHandler? clicked;
public event EventHandler Clicked
{
remove { this.clicked -= value; }
add { this.clicked += value; }
}
}
```
### After
```csharp
using System;
public class Button
{
private EventHandler? clicked;
public event EventHandler Clicked
{
add { this.clicked += value; }
remove { this.clicked -= value; }
}
}
```
## Why
Subscribing comes before unsubscribing, and every other event is written that way.
## How the fix works
The same as [BRO1003](BRO1003.md): the two accessors are swapped with their comments.
## Compared with StyleCop
Same positions and the same fixed output as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1005/
# BRO1005: Documentation lines should begin with single space
Each `///` documentation line has exactly one space before its text. Replaces StyleCop **SA1004**.
## Example
### Before
```csharp
///
///Creates an order.
///
/// Orders start empty.
public class Order
{
}
```
### After
```csharp
///
/// Creates an order.
///
/// Orders start empty.
public class Order
{
}
```
## Why
Documentation reads like prose; a line without the space or with extra indentation looks like a typo, and lines line up only when they all start the same way.
## How the fix works
A missing space after `///` is added. Several spaces are reduced to one before a top-level tag (`/// `), like StyleCop; indented text and nested tags inside an element are fine, and so are lines with nothing after `///` and lines inside ``, where indentation is part of the example.
## Compared with StyleCop
Same positions and the same fixed output as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1006/
# BRO1006: Preprocessor keywords should not be preceded by a space
`#if`, not `# if`. Replaces StyleCop **SA1006**.
## Example
### Before
```csharp
public class Settings
{
# if DEBUG
public bool Verbose = true;
# endif
}
```
### After
```csharp
public class Settings
{
#if DEBUG
public bool Verbose = true;
#endif
}
```
## Why
The keyword belongs to the `#`; every other directive in the codebase is written that way, and searching for `#if`
finds them all.
## How the fix works
The fix removes the whitespace between `#` and the keyword. Indentation before the `#` stays.
## Compared with StyleCop
Same positions and output as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1007/
# BRO1007: Partial elements should declare an access modifier
Every part of a partial type says its accessibility. Replaces StyleCop **SA1205**.
## Example
### Before
```csharp
public partial class Order
{
}
partial class Order
{
}
```
### After
```csharp
public partial class Order
{
}
public partial class Order
{
}
```
## Why
A part without a modifier doesn't tell the reader the type's accessibility; it has to be looked up in another file.
## How the fix works
The fix writes the type's accessibility as another part declares it, or the default when no part does. It goes before
the other modifiers (after attributes).
## Configuration
```ini
[*.cs]
# The SDK's setting, which BRO1404 and BRO1007 follow:
# for_non_interface_members (or not set): StyleCop's SA1400/SA1205, as described here (BRO1404 and BRO1007)
# always: interface members get one too ('public', C# 8 and later)
# never, omit_if_default: no modifiers are asked for (nothing is reported)
dotnet_style_require_accessibility_modifiers = for_non_interface_members
```
## Not reported
- **`file` types**, and types nested in interfaces. That includes a part without `file` of a `file` type
(`file partial class X` + `partial class X`): an access modifier there doesn't compile (CS9052).
## Compared with StyleCop
Same positions and output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1008/
# BRO1008: Using directives should be placed correctly
Using directives go inside or outside the namespace, as `csharp_using_directive_placement` says; without that
setting nothing is reported. Replaces StyleCop **SA1200**, and the SDK's IDE0065, whose fix can break the build.
## Example
```ini
[*.cs]
csharp_using_directive_placement = outside_namespace
```
### Before
```csharp
namespace Shop.Orders
{
using System;
using System.Collections.Generic;
public class Order
{
public List Changes { get; } = new();
}
}
```
### After
```csharp
using System;
using System.Collections.Generic;
namespace Shop.Orders
{
public class Order
{
public List Changes { get; } = new();
}
}
```
## Why
One place for usings in every file. Where they are also decides how names are looked up: inside the namespace, a
using's names come before the enclosing namespaces' members; outside, after them.
## How the fix works
All usings of the file move together, and only when no name in the file can be looked up differently afterwards. The
SDK's IDE0065 fix doesn't check that: in eShop it made `Order` ambiguous (CS0104), in Ocelot a type name turned into a
namespace (CS0118; reported upstream as [dotnet/roslyn#86002](https://github.com/dotnet/roslyn/issues/86002)). The check follows C#'s lookup order instead of compiling the file again:
- Inside `namespace A.B`, a name is looked up in `A.B`, then in the usings there, then in `A`, the global namespace,
and the global usings. Outside, the usings come last, together with the global usings (implicit usings too).
- So for every identifier in the file that the moved usings import, the types `A` or the global namespace have under
that name must be the same ones, and if they have none, the global usings must not import anything else under it.
- Extension methods (names after `.`, and the ones `foreach`, `await`, collection initializers, deconstruction and
queries call) that the moved usings bring must not share a name with one from the namespace, its containers or the
global usings.
- A using's own name (`using X.Y;`) must start with a member of the global namespace that no namespace of `A.B` has.
Names are compared by text, so the check is conservative. When it fails, nothing is reported.
- Comments directly above the first using move with it; the file header stays on top. When usings at the top of the
file move in, a comment right below them becomes the header and gets a blank line before the code.
- Outside: the usings go above the namespace (and the comments directly above it), with a blank line before the
namespace and none at the start of the file. Relative names get their full name, like
[BRO1126](BRO1126.md)'s fix (`using IO;` inside `namespace System` becomes `using System.IO;`).
- Inside: below `{` (indented one level), or below `namespace X;` after a blank line, with a blank line before
what follows.
- In a multi-targeted project every target framework's copy of the file must agree; otherwise the warning stays.
## Configuration
```ini
[*.cs]
# The SDK's setting (StyleCop's usingDirectivesPlacement):
# inside_namespace (StyleCop's default), outside_namespace
# not set, or anything else (preserve): nothing is reported
csharp_using_directive_placement = outside_namespace
```
The preset sets `outside_namespace`. `stylebro-migrate` writes StyleCop's choice from `stylecop.json`
(`usingDirectivesPlacement`, `inside_namespace` when it isn't set, StyleCop's default), so a migrated repository keeps
SA1200's behavior. Without the preset and the setting the rule does nothing: where usings go is a choice it doesn't make
for you.
## Not reported
- **Files where a name could be looked up differently** with the usings moved (see above).
- **Files with more than one namespace**, with types, attributes or top-level statements outside the namespace,
with usings on both levels, global usings, or extern aliases.
- **A directive** (`#if`, `#pragma`, `#nullable`, ...) anywhere before the namespace's body. `#region`/`#endregion` lines
are fine when they aren't among the usings and no region is open around the usings or where they go
(Newtonsoft.Json's `#region License` around the header, above or below the usings, which [BRO1112](BRO1112.md)
removes in the same run); a region around the usings isn't.
- **A using sharing its line** with other code.
- With `inside_namespace`, a file without a namespace (StyleCop reports its usings, but there's nowhere to move them).
## Compared with StyleCop
Same positions and output where both report (parity sets `using-placement` and `using-placement-outside`), except
that StyleCop's fix drops the blank line below `namespace X;`. StyleCop's fix moves usings without checking what they
bind to: in the parity sets it turned `Order` into another type, and produced code that doesn't compile (CS0234).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1101/
# BRO1101: Code should not contain empty statements
A stray `;` does nothing and should be removed. Replaces StyleCop **SA1106**.
## Example
### Before
```csharp
public class Counter
{
private int _count;
public void Increment()
{
_count++; ;
;
}
};
```
### After
```csharp
public class Counter
{
private int _count;
public void Increment()
{
_count++;
}
}
```
## Why
An empty statement is noise at best. At worst it's a typo that hides a bug, so it's worth keeping the code free of
them.
## What is reported
- an empty statement in a block or a `case` section: `M(); ;`, or a line with only `;`
- a `;` after the closing brace of a type or namespace: `class C { };`
## Configuration
```ini
[*.cs]
# true: the ';' after a type's or namespace's '}' stays, like Sonar's S1116 (default false)
stylebro_allow_semicolon_after_closing_brace = false
```
Sonar's S1116 reports empty statements only; `stylebro-migrate` writes `true` for a repository that enforces S1116, unless
the StyleCop setup has SA1106 on (which reports the `;` after `}` too).
## How the fix works
A line that held only the `;` is removed completely; otherwise the `;` goes with the space in front of it. A `;` at the
start of a line takes the space after it instead, so `; // done` becomes `// done` at the same indentation.
## Not reported
- **Embedded empty statements**, such as `while (Poll()) ;` or `if (x) ;`. Replacing them with `{ }` keeps the
behavior but hides what is often a bug. The compiler already warns about the suspicious ones (CS0642, "Possible
mistaken empty statement"), and an automatic fix would silence that warning.
- **Labeled empty statements** (`end: ;`). A label needs a statement, so removing the `;` can break the build when the
label is the last thing in a block.
- **A record without a body** (`record R(int X);`), where the `;` is required.
## Compared with StyleCop
StyleCop reports embedded and labeled empty statements too. Its fix turns `while (x) ;` into `{ }` and removes the `;`
of `end: ;`, which can break the build. For `; // done`, StyleCop's fix leaves the comment with one space of
indentation.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1102/
# BRO1102: Each attribute should be in its own brackets
`[A, B]` should be written as `[A]` and `[B]`. Replaces StyleCop **SA1133**.
## Example
### Before
```csharp
using System;
using System.Diagnostics;
public class Report
{
[Obsolete("Use Render"), DebuggerStepThrough]
public string Print() => "report";
}
```
### After
```csharp
using System;
using System.Diagnostics;
public class Report
{
[Obsolete("Use Render")]
[DebuggerStepThrough]
public string Print() => "report";
}
```
## Why
One attribute per line is easier to scan, and adding or removing an attribute changes exactly one line in a diff.
## How the fix works
A list on its own line becomes one line per attribute, at the same indentation. A list that shares its line with code
stays on that line: `[Obsolete, DebuggerStepThrough] void M()` becomes `[Obsolete] [DebuggerStepThrough] void M()`.
A target is repeated on every part: `[assembly: A, B]` becomes `[assembly: A]` and `[assembly: B]`. Arguments are kept
exactly as written.
## Not reported
- **Attributes on parameters and type parameters** (`void M([A, B] int x)`), like StyleCop.
- **Lists with a comment or directive between the attributes** (`[A, /* why */ B]`). Splitting would have to move or
drop the comment.
## Compared with StyleCop
StyleCop also reports lists with a comment between the attributes, and its fix drops the comment.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1103/
# BRO1103: Constants should be on the right-hand side of comparisons
`0 == count` should be written as `count == 0`. Replaces StyleCop **SA1131**.
## Example
### Before
```csharp
public class Stock
{
public bool IsEmpty(int count) => 0 == count;
public bool IsLow(int count) => 10 > count;
}
```
### After
```csharp
public class Stock
{
public bool IsEmpty(int count) => count == 0;
public bool IsLow(int count) => count < 10;
}
```
## Why
`count == 0` reads the way you'd say it: "count is zero". The "constant first" style (`0 == count`) guarded against
typing `=` for `==` in C, but in C# that mistake doesn't compile for most types anyway.
## What is reported
Comparisons (`==`, `!=`, `<`, `>`, `<=`, `>=`) with a constant on the left and a non-constant on the right. Like
StyleCop, "constant" means literals, `null`, `default`, `const` fields, enum members, `nameof(...)`, constant
expressions such as `1 + 1`, and `static readonly` fields such as `string.Empty`.
## How the fix works
The fix swaps the operands and flips relational operators (`10 > count` becomes `count < 10`). Line breaks and
comments stay where they are. `1 == 2 == b` becomes `b == (1 == 2)`, keeping its meaning. Nested comparisons are
fixed in one pass.
## Not reported
- **Comparisons that use a type's own operator**, such as `null == money` where `Money` defines `==`. A hand-written
operator isn't guaranteed to give the same result with its operands reversed, and reversing them can even pick a
different overload. Operators from the .NET core library (`string`, `decimal`, `DateTime`, `Guid`, ...) are
symmetric, so `Guid.Empty == id` is still reported.
- **Comparisons with a constant on both sides** (`1 == 2`), like StyleCop.
## Compared with StyleCop
StyleCop also swaps comparisons that use a type's own operator.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1104/
# BRO1104: Use default instead of a value type's default constructor
`new int()` creates the default value; `default(int)` says so. Replaces StyleCop **SA1129**.
## Example
### Before
```csharp
using System;
using System.Threading;
public class Scheduler
{
public DateTime Start { get; set; } = new DateTime();
public Guid Run(CancellationToken token = new CancellationToken())
{
return new Guid();
}
}
```
### After
```csharp
using System;
using System.Threading;
public class Scheduler
{
public DateTime Start { get; set; } = default(DateTime);
public Guid Run(CancellationToken token = default(CancellationToken))
{
return Guid.Empty;
}
}
```
## Why
`new DateTime()` looks like it creates something meaningful, but it's just `DateTime.MinValue`, the all-zero default.
Writing `default(DateTime)`, or `Guid.Empty`, makes that explicit.
## How the fix works
The same replacements as StyleCop:
| Before | After |
|---|---|
| `new int()`, `new Point()`, `Point p = new();` | `default(int)`, `default(Point)`, `default(Point)` |
| `new CancellationToken()` | `CancellationToken.None` |
| `new Guid()` | `Guid.Empty` |
| `new IntPtr()`, `new UIntPtr()` | `IntPtr.Zero`, `UIntPtr.Zero` |
| `new nint()`, `new nuint()` | `default(nint)`, `default(nuint)` |
| `new Color()` for an enum with a zero member `Red` | `Color.Red` |
A parameter's default value must be a constant, so there `new Guid()` becomes `default(Guid)` instead of
`Guid.Empty`, as in the example.
## Configuration
The SDK's `csharp_prefer_simple_default_expression` (IDE0034's option) is followed when it is set to `true` (any
severity suffix, such as `true:warning`): the fix writes the `default` literal instead of `default(T)`. Unset or
`false` keeps `default(T)`, like StyleCop. The named replacements (`Guid.Empty`, `CancellationToken.None`,
`IntPtr.Zero`, an enum's zero member) stay as they are.
```ini
[*.cs]
csharp_prefer_simple_default_expression = true
```
```csharp
// Before
public DateTime Start { get; set; } = new DateTime();
public void Run(CancellationToken token = new CancellationToken()) { }
var count = new int();
// After
public DateTime Start { get; set; } = default;
public void Run(CancellationToken token = default) { }
var count = default(int);
```
The literal is written only where it means the same, checked by binding the statement again with it: `var` has no
type for it, a conversion would change the value (`object o = new int()` boxes a zero, `object o = default` is null;
`int? n = default` is null), an overload may no longer be picked, and a member access on it doesn't compile. Those keep
`default(T)`. All creations and `default(T)` expressions of one statement, initializer or expression body are checked
together, since each alone may bind where all together don't (`M(default, default)` can be ambiguous). The literal
needs C# 7.1; in a multi-targeted project the lowest language version of its target frameworks counts. With IDE0034 on
as well, `dotnet format` converges in one run: before, `new T()` became `default(T)` and IDE0034 needed a second run to
simplify it.
## Not reported
- **Structs with their own parameterless constructor** (C# 10 and later). `new S()` runs that constructor;
`default(S)` doesn't. Like StyleCop.
- **`new T()` for a type parameter** (`where T : struct`). If `T` has a parameterless constructor, `new T()` runs it
and `default(T)` doesn't.
- **`new S();` as a statement on its own.** `default(S);` doesn't compile.
- **Creations with an initializer**, even an empty one (`new Point() { }`), like StyleCop.
- **Creations with a comment inside** (`new Guid(/* none */)`): the replacement would drop it.
## Compared with StyleCop
`nint`/`nuint` always become `default(nint)`/`default(nuint)`. StyleCop 1.2.0-beta.556 always writes `nint.Zero`, which
doesn't compile (CS0117) before C# 11 or on runtimes without numeric `IntPtr` (.NET Framework, .NET Standard,
.NET 6). StyleCop's `master` (unreleased) writes `nint.Zero` only where it compiles, but a multi-targeted project
analyzes each file once per target framework: for `net462;net8.0` the two copies would get different fixes, and the
net462 build would break. `default(nint)` means the same and compiles everywhere. StyleCop also replaces `new T()` for type parameters, and turns `new S();` into `default(S);`, which doesn't compile.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1105/
# BRO1105: Constructor initializers should be on their own line
`: base(...)` and `: this(...)` go on the line after the constructor's parameters, one level deeper. Replaces
StyleCop **SA1128**.
## Example
### Before
```csharp
public class Entity
{
public Entity(string id)
{
Id = id;
}
public string Id { get; }
}
public class Customer : Entity
{
public Customer(string id, string name) : base(id)
{
Name = name;
}
public string Name { get; }
}
```
### After
```csharp
public class Entity
{
public Entity(string id)
{
Id = id;
}
public string Id { get; }
}
public class Customer : Entity
{
public Customer(string id, string name)
: base(id)
{
Name = name;
}
public string Name { get; }
}
```
## Why
Constructor signatures tend to get long. With the initializer on its own line, the parameters and the call to the
other constructor are each easy to read, and changing one doesn't touch the other's line.
## How the fix works
The indentation comes from `.editorconfig` (`indent_style`, `indent_size`, `tab_width`) and defaults to four spaces.
Everything after `base`/`this` stays as it is: the arguments, even when they span several lines, and a body on the
same line (`: base() { }`). A comment before the colon stays on the constructor's line. A colon at the end of the
constructor's line (`Customer(...) :`) moves down too.
## Not reported
- **An initializer that already starts its line.**
- **A primary constructor's base list** (`class D(int x) : B(x)`), which isn't a constructor initializer.
- **A comment between the colon and `base`/`this`** (`: /* note */ base()`). The fix rewrites exactly that stretch, so
it would drop the comment.
## Configuration
```ini
[*.cs]
stylebro_constructor_initializer_placement = own_line
```
- `own_line` (default): like StyleCop's SA1128, as described above.
- `same_line`: the initializer goes on the line of the parameter list's `)`:
```csharp
// Before
public Customer(string id, string name)
: base(id)
{
}
// After
public Customer(string id, string name) : base(id)
{
}
```
With `same_line`, an initializer is only joined (and only reported) when:
- the line of the `)` with ` : base(...)` added, up to the initializer's end, fits `max_line_length`. A body after the
initializer (`{ }`, `=> ...`) isn't counted: [BRO1509](BRO1509.md) may move it. **Without `max_line_length` there is
no limit**, so set it when you use `same_line`.
- the initializer is on one line (`base(` with arguments on further lines is left alone),
- nothing but whitespace sits between `)` and `base`/`this` (no comments or directives).
When the parameter list is split, the initializer goes after the `)` on its last line. A `)` on its own line that
[BRO1110](BRO1110.md) moves to the last parameter moves in the same edit (`int b) : base(b)`), so both fixes give the
same text whichever runs first. The `)` line is measured as the other fixes in the same run leave it, when those rules
are on: [BRO1404](BRO1404.md)/[BRO1007](BRO1007.md) add an access modifier, [BRO1109](BRO1109.md) moves `(` up,
[BRO1108](BRO1108.md) puts each parameter on its own line, [BRO1110](BRO1110.md) moves `)` (its `own_line` setting
puts it on a line of its own, where the initializer then joins it). So the decision doesn't depend on which fix runs
first. Not counted: renames ([BRO1302](BRO1302.md), [BRO1309](BRO1309.md), ...) and rewrites inside the line
([BRO1115](BRO1115.md) `int?`, [BRO1144](BRO1144.md) `@`, ...), whose results depend on checks made elsewhere (a
rename can be skipped). Right at the limit, one of those can still leave a join for a second `dotnet format` run, or
a joined line a few characters too long.
## Compared with StyleCop
Same results as StyleCop. StyleCop has no `same_line` setting; teams that prefer it turn SA1128 off.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1106/
# BRO1106: Use string.Empty for empty strings
`""` should be written as `string.Empty`. Replaces StyleCop **SA1122**.
## Example
### Before
```csharp
public class Person
{
public string Name { get; set; } = "";
public bool HasName() => Name != "";
}
```
### After
```csharp
public class Person
{
public string Name { get; set; } = string.Empty;
public bool HasName() => Name != string.Empty;
}
```
## Why
`string.Empty` states the intent, while `""` can look like a string that is missing its text, or one where a space was
lost. One way of writing it also makes empty strings easy to search for.
## Not reported
`string.Empty` is not a compile-time constant, so wherever C# requires a constant the literal stays:
- `const` fields and locals, including a `""` inside a longer constant expression (`const string X = "" + "a"`)
- attribute arguments: `[Obsolete("")]`
- parameter default values: `void M(string s = "")`
- `case ""` labels and patterns: `s is ""`, `"" => ...` in a switch expression (the arm's result, `_ => ""`, is
replaced)
Interpolated (`$""`) and UTF-8 (`""u8`) literals are different kinds of literal and aren't reported either.
## Configuration
```ini
[*.cs]
# string_empty (default): string.Empty, like StyleCop
# literal: ""
stylebro_empty_string_style = string_empty
```
With `literal`, the rule reports the other way around: `string.Empty` (also written `String.Empty` or
`System.String.Empty`) becomes `""`, everywhere, since a literal is valid in every context.
```csharp
// Before (stylebro_empty_string_style = literal)
public string Name { get; set; } = string.Empty;
public bool HasName() => Name != String.Empty;
// After
public string Name { get; set; } = "";
public bool HasName() => Name != "";
```
Not reported in the literal style: a member named `Empty` on another type, `nameof(string.Empty)` (`nameof("")` doesn't
compile), and an access with a comment inside it (`string /* x */ .Empty`). A verbatim `@""` is left to
[BRO1138](BRO1138.md), which removes the `@`.
## Compared with StyleCop
Same results as StyleCop. The `literal` style is the opposite of StyleCop's rule (Roslynator's RCS1078 has the same
choice).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1107/
# BRO1107: Split parameters should start on the line after the declaration
When parameters or arguments span several lines, the first one goes on its own line too, one level deeper than the
line with the opening parenthesis. Replaces StyleCop **SA1116**.
## Example
### Before
```csharp
public class Invoice
{
public Invoice(string number,
decimal amount)
{
Number = number;
Amount = amount;
}
public string Number { get; }
public decimal Amount { get; }
public static Invoice Create() => new Invoice("2026-001",
100m);
}
```
### After
```csharp
public class Invoice
{
public Invoice(
string number,
decimal amount)
{
Number = number;
Amount = amount;
}
public string Number { get; }
public decimal Amount { get; }
public static Invoice Create() => new Invoice(
"2026-001",
100m);
}
```
## Why
With the first item next to the parenthesis, the items don't line up: the first one sits far to the right of the
others, and renaming the method moves it. On its own line, every item starts at the same column.
## How the fix works
The first item moves to a new line. The indentation comes from `.editorconfig` (`indent_style`, `indent_size`,
`tab_width`) and defaults to four spaces. The item's later lines (a lambda's block body) move with it, by the same
amount. The other items stay where they are.
amount; a multi-line string inside keeps its lines. The other items stay where they are, unless
[BRO1108](BRO1108.md) is on: once the first item starts a line, BRO1108 wants every item on its own line, so the fix
does that in the same step (moving the first item alone can indent the line where it ends deeper than the next item's
continuation lines, which a later move would then leave behind).
Checked: parameters of methods, constructors, indexers, delegates, operators, local functions, lambdas and anonymous
methods, records and primary constructors; arguments of calls, `new`, attributes, element access and a primary
constructor's base type; array sizes (`new int[2, 3]`).
## Configuration
```ini
[*.cs]
# next_line (default, like StyleCop): a split list's first item goes on the line after '('; same_line: it may stay
stylebro_split_list_first_item = next_line
```
With `same_line` (or with BRO1107 off: owner's decision, see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)), BRO1107 reports nothing: a split list may start on the `(` line or on the next one. The fix of
[BRO1108](BRO1108.md) (items all on one line or each on its own) then keeps a first item that is on the `(` line
there and puts every other item on its own line, indented like the first item of the list that already starts a line
(aligned under the first item or one level deeper, whichever the list does), else one level deeper than the line with
the `(`. Nothing is moved up to the `(` line, so aligned lines and line lengths stay as they are.
```csharp
// BRO1108 with next_line
public decimal Apply(
decimal price,
decimal discount,
decimal minimum)
// BRO1108 with same_line
public decimal Apply(decimal price,
decimal discount,
decimal minimum)
```
(Before: `public decimal Apply(decimal price,` then `decimal discount, decimal minimum)`, aligned under `decimal price`.)
`stylebro_allow_leading_multiline_lambda = true` (see [BRO1108](BRO1108.md#configuration)) leaves a call whose first
argument is a multi-line lambda, anonymous method or anonymous object on the `(` line, with the other arguments on its
closing line (`Register(ctx =>` / `{ ... }` / `}, SymbolKind.Field);`).
## Not reported
- **The first two items on the same line** (`M(a, b,` then `c)`). That's [BRO1108](BRO1108.md)'s case.
- **A single item that spans lines**, like a lambda: `Select(x =>` then `x + 1)`.
- **A comment or directive between the parenthesis and an item, or between items.** The fix rewrites exactly those
gaps, so it would have to move or drop the comment.
- **Lists with syntax errors.**
- **A list with an item that contains a multi-line string** (verbatim, raw or interpolated): see [BRO1108](BRO1108.md).
## Compared with StyleCop
StyleCop has no `same_line` or `stylebro_allow_leading_multiline_lambda` option. With the default, same results as StyleCop, except that StyleCop 1.2.0-beta.556 doesn't check record parameters, primary-constructor
parameters or a primary constructor's base arguments. StyleBro treats them like any other list.
StyleCop's fix leaves a moved lambda's block body at its old indentation ([#1620](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1620), [#3183](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3183), open); StyleBro moves it
with the lambda. Nested lists (`M(N(1,` then `2),` then `3)`) are fixed outer list first, so Fix All gives the same
result as fixing them one by one.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1108/
# BRO1108: Parameters should be on the same line or on separate lines
Parameters and arguments are either all on one line or each on its own line. Replaces StyleCop **SA1117**.
## Example
### Before
```csharp
public class Mailer
{
public void Send(string to, string subject,
string body)
{
}
public void Welcome(string to)
{
Send(
to,
"Welcome", "Thanks for signing up.");
}
}
```
### After
```csharp
public class Mailer
{
public void Send(
string to,
string subject,
string body)
{
}
public void Welcome(string to)
{
Send(
to,
"Welcome",
"Thanks for signing up.");
}
}
```
## Why
A list that is half on one line and half on separate lines is hard to scan: you can't tell at a glance how many items
there are or which is which. Either layout is fine; mixing them isn't.
## What is reported
Like StyleCop, the first two items decide which layout was meant:
- **The first two start on the same line:** every item has to start on that line. The first one that doesn't is
reported.
- **The first two start on different lines:** no item may start on the line where the previous one ends. The first
one that does is reported.
An item that spans several lines, like a lambda or a nested call, counts by the line it starts and ends on:
`M(1, 2, N(` then `3))` is fine, since `N(...)` starts on the first line. An empty `()` split over lines counts as joined
when [BRO1116](BRO1116.md) is on (its fix joins it): `N(` then `), M(5, 6)` is all on one line.
## How the fix works
Every item goes on its own line, starting on the line after the opening parenthesis, one level deeper than the line
with the parenthesis. The indentation comes from `.editorconfig` (`indent_style`, `indent_size`, `tab_width`) and
defaults to four spaces. Items that already start their line keep their indentation. A moved item's later lines (a
lambda's block body) move with it, by the same amount.
The fix never joins lines: a list that spans lines because one item does can't be put on one line, and a list that
was split on purpose is usually long.
With `stylebro_split_list_first_item = same_line` (see [BRO1107](BRO1107.md#configuration)), or with BRO1107 off
(`stylebro-migrate init` turns it off for code that mixes both forms), a first item on the opening parenthesis's line
stays there, and the moved items are indented like the first item that already starts a
line (else one level deeper than the line with the parenthesis).
The lists checked are the same as for [BRO1107](BRO1107.md).
## Configuration
```ini
[*.cs]
# false (default, like StyleCop): a call whose first argument spans lines gets one argument per line.
# true: a first argument that is a multi-line lambda, anonymous method or anonymous object, with the other arguments
# on its closing line, stays as it is (BRO1107 doesn't report it either).
stylebro_allow_leading_multiline_lambda = false
```
With `true`, this layout is fine:
```csharp
context.RegisterSymbolAction(ctx =>
{
Check(ctx);
}, SymbolKind.Field);
```
The first argument has to start on the `(` line and every other argument on its last line; any other layout of the
same call is still reported and fixed as usual. `stylebro-migrate init` writes `true` when the repository's code
writes such calls (at least 3, all of them below 10, 75 % from 10 on); the migration from StyleCop doesn't (SA1116
and SA1117 report it).
## Not reported
- **A comment or directive between the parenthesis and an item, or between items.** The fix rewrites exactly those
gaps, so it would have to move or drop the comment.
- **Lists with syntax errors.**
- **A list with an item that contains a multi-line string** (verbatim, raw or interpolated): its text can't be
reindented, and splitting `Assert.Equal(@"line 1` / `line 2", actual)` around it scatters the call (found in
spreadcheetah).
## Compared with StyleCop
StyleCop has no `stylebro_allow_leading_multiline_lambda` option. With the default, same results as StyleCop, except that StyleCop reports lists with a multi-line string (no fix), and that StyleCop 1.2.0-beta.556 doesn't check record parameters, primary-constructor
parameters or a primary constructor's base arguments. StyleCop has no working fix for SA1117; StyleBro's puts each
item on its own line, and moves a lambda's block body along with the lambda (StyleCop's fixes leave it behind:
[#1620](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1620), [#3183](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3183), open).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1109/
# BRO1109: Opening parenthesis or bracket should be on the declaration line
The opening parenthesis of a parameter or argument list goes on the line of the name it belongs to, not on the next
line. Replaces StyleCop **SA1110**.
## Example
### Before
```csharp
public class Report
{
public string Title
(int year, int month)
{
return Format
(year, month);
}
private static string Format(int year, int month) => year + "-" + month;
}
```
### After
```csharp
public class Report
{
public string Title(
int year, int month)
{
return Format(
year, month);
}
private static string Format(int year, int month) => year + "-" + month;
}
```
## Why
A name followed by `(` on the next line looks like a statement that ended, and the list like something unrelated.
Keeping `(` with the name makes the call or declaration read as one unit.
## How the fix works
The `(` or `[` moves to the end of the name's line, and what followed it stays where it was, now as the list's first
line (like StyleCop's fix). An empty list is simply joined: `new Report()`.
Checked: parameter lists of methods, constructors, delegates, local functions and indexers, and argument lists of
calls, `new`, element access, attributes and constructor initializers. A lambda's parameter list has no name before
it and isn't checked.
## Not reported
- **A comment or directive between the name and the parenthesis.**
## Compared with StyleCop
Same results as StyleCop, with the same fixed output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1110/
# BRO1110: Closing parenthesis or bracket should be on the line of the last item
A parameter or argument list ends on the line where its last item ends, not with the closing parenthesis on a line
of its own. Replaces StyleCop **SA1111**.
## Example
### Before
```csharp
public class Mailer
{
public void Send(
string to,
string subject
)
{
Log(
to,
subject
);
}
private void Log(string to, string subject)
{
}
}
```
### After
```csharp
public class Mailer
{
public void Send(
string to,
string subject)
{
Log(
to,
subject);
}
private void Log(string to, string subject)
{
}
}
```
## Why
A lone `)` is a line that says nothing, and it makes the end of a call look like the start of a block. It's also
the C# convention in the .NET runtime and most libraries.
## How the fix works
The `)` or `]` moves right after the last item. A comment at the end of the last item's line goes after it:
`string subject // required` then `)` becomes `string subject) // required`.
Checked: the same lists as [BRO1109](BRO1109.md), plus lambda parameter lists.
## Configuration
```ini
[*.cs]
# last_item (default, like StyleCop): ')' ends the last item's line; own_line: a split list's ')' gets its own line
stylebro_closing_parenthesis_placement = last_item
```
`own_line` is the layout CSharpier writes (checked on Mapperly, which is formatted with CSharpier: nothing changes).
It applies to **split** lists: lists whose last item ends on a line below the opening token (`(`, or the name's line
when [BRO1109](BRO1109.md) moves the `(` up there). Their `)` or `]` goes on a line of its own, indented like the line
with the opening token. One exception, again like CSharpier: when every item starts on the `(` line and the last one
ends with a structure the `)` can hug, the `)` follows it directly. Hugged are multi-line braces (a lambda's block
body, an initializer) and an argument list, tuple or collection expression opened on the `(` line. A list that isn't
split, or hugs, keeps its `)` at the end of the last item, as with `last_item`. A `)` that is already below the last
item is left where it is, at any indentation: CSharpier indents it deeper inside a wrapped expression, and keeps `}`
and `)` apart when a lambda's body starts below `=>`.
```csharp
// last_item
Log(
to,
subject);
var open = items.Where(x =>
x.IsOpen
&& x.Owner == user);
// own_line
Log(
to,
subject
);
var open = items.Where(x =>
x.IsOpen
&& x.Owner == user
);
// own_line, hugged
items.ForEach(x =>
{
Send(x);
});
var mapped = items.Select(x => new Mapped(
x.Id,
x.Name
));
```
A single argument that starts on the `(` line and continues below it counts as split too, also when it's a string
concatenation, so its `)` gets its own line, as CSharpier places it. Only the `)` moves; the `(` keeps its line:
```csharp
// own_line
throw new InvalidOperationException("The first part " +
"and the second part"
);
```
Only the gap before the `)` changes, so whatever follows it stays with it (`) =>`, `) : base(...)`, `).Trim()`,
`); // done`). "The line with the opening token" means that line as the other fixes leave it: [BRO1105](BRO1105.md)
moving `: base(` to its own line, an inner list's `)` that BRO1110 itself moves down to the start of the line, or an
empty `()` that BRO1109 joins to its name, so nested lists end up aligned in one run. In `own_line` mode a split list
is also not reported when a comment sits between the last item and the `)`, when the list contains a directive (each
target framework's copy of the file may have another last item), or when the last item ends in braces on one line
(`x =>` then `{ Send(x); })`: expanding them would make them hugged). A parenthesized expression is never hugged
(`(a` then `+ b))` gets the lone `)`): other rules add and remove such parentheses.
## Not reported
- **Empty lists** (`Send(` then `)`): that's spacing, not the last item's line.
- **A comment on its own line or a directive before the closing parenthesis.**
- **A comment at the end of the last item's line when code follows the parenthesis on its line** (`2 // two` then
`); var x = 1;`): the code would end up behind the comment.
## Compared with StyleCop
StyleCop has no `own_line` option. With the default, same results as StyleCop, with the same fixed output, except the last case above, which StyleCop reports, and two
kinds of lists StyleCop doesn't check (neither 1.1.118 nor 1.2): target-typed `new(...)` and constructor initializers
(`: this(...)`, `: base(...)`). StyleBro treats them like any other argument list.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1111/
# BRO1111: Generic type constraints should be on their own line
Each `where` clause starts its own line, one level deeper than the declaration. Replaces StyleCop **SA1127**.
## Example
### Before
```csharp
using System;
using System.Collections.Generic;
public class Cache where TKey : IComparable where TValue : class
{
private readonly Dictionary items = new Dictionary();
public TValue Get(TKey key) => items[key];
public static TOut Convert(TValue value) where TOut : class => value as TOut;
}
```
### After
```csharp
using System;
using System.Collections.Generic;
public class Cache
where TKey : IComparable
where TValue : class
{
private readonly Dictionary items = new Dictionary();
public TValue Get(TKey key) => items[key];
public static TOut Convert(TValue value)
where TOut : class
=> value as TOut;
}
```
## Why
Constraints are part of the signature, but a long declaration with constraints at the end of the line hides them.
On their own lines, each constraint is easy to find and to change.
## How the fix works
Each `where` gets a new line, indented one level deeper than the line with the declaration's name. The indentation
comes from `.editorconfig` (`indent_style`, `indent_size`, `tab_width`) and defaults to four spaces. For an
expression-bodied member, the `=>` after the last constraint gets its own line too, like StyleCop's fix.
Checked: classes, structs, interfaces, records, methods, delegates and local functions.
## Not reported
- **A comment right before `where`** (`class Box /* why */ where T : class`).
## Configuration
```ini
[*.cs]
stylebro_constraint_placement = own_line
```
- `own_line` (default): like StyleCop's SA1127, as described above.
- `same_line`: every `where` clause goes on the declaration's line, after one space:
```csharp
// Before
public class Cache
where TKey : IComparable
where TValue : class
{
}
// After
public class Cache where TKey : IComparable where TValue : class
{
}
```
With `same_line`, a declaration's clauses are joined all together or not at all (and only then reported). They aren't
when:
- the joined line, up to the last clause's end, would be longer than `max_line_length`. A body after the clauses
(`{ }`, `=> ...`) isn't counted. **Without `max_line_length` there is no limit**, so set it when you use `same_line`.
- a clause spans several lines,
- anything but whitespace sits before a clause (a comment, a directive).
The `=>` of an expression body stays where it is (`where T : class` then `=> ...` on the next line keeps that line;
[BRO1521](BRO1521.md) leaves an `=>` after a constraint alone). A `)` on its own line before the first clause that
[BRO1110](BRO1110.md) moves to the last parameter moves in the same edit. The joined line is measured as the other
fixes in the same run leave it, when those rules are on: [BRO1404](BRO1404.md)/[BRO1007](BRO1007.md) add an access
modifier, [BRO1109](BRO1109.md) moves `(` up, [BRO1108](BRO1108.md) puts each parameter on its own line,
[BRO1110](BRO1110.md) moves `)`. So the decision doesn't depend on which fix runs first. Not counted: renames
([BRO1304](BRO1304.md), [BRO1305](BRO1305.md), ...) and rewrites inside the line ([BRO1115](BRO1115.md) `int?`,
[BRO1123](BRO1123.md) tuple syntax, ...), whose results depend on checks made elsewhere (a rename can be skipped).
Right at the limit, one of those can still leave a join for a second `dotnet format` run, or a joined line a few
characters too long.
## Compared with StyleCop
Same results as StyleCop, with the same fixed output. StyleCop has no `same_line` setting; teams that prefer it turn
SA1127 off.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1112/
# BRO1112: Do not use regions
`#region` isn't used between members or types, or in an expression body (between the arms of a switch expression
after `=>`). Replaces StyleCop **SA1124**. Regions inside a `{ }` block are [BRO1113](BRO1113.md).
## Example
### Before
```csharp
public class Account
{
#region Fields
private decimal balance;
#endregion
#region Methods
public void Deposit(decimal amount)
{
balance += amount;
}
#endregion
}
```
### After
```csharp
public class Account
{
private decimal balance;
public void Deposit(decimal amount)
{
balance += amount;
}
}
```
## Why
Regions hide code instead of organizing it: a class that needs them to be readable is usually doing too much, and
collapsed regions make reviewers miss what's inside. Member ordering ([BRO1001](BRO1001.md)) already groups members
by kind.
StyleCop turns SA1124 on by default, but StyleBro's preset leaves BRO1112 off: removing every region in an existing
codebase is a large one-time change, so it's opt-in. Turn it on to remove regions:
```ini
[*.cs]
dotnet_diagnostic.BRO1112.severity = warning
```
## How the fix works
The `#region` and `#endregion` lines are removed. A blank line next to them goes too when it would otherwise double
another blank line, follow an opening brace or precede a closing brace. Directives inside the region (`#if`) stay.
**Removing regions lets [BRO1001](BRO1001.md) sort across them:** member ordering sorts within each region only. When
BRO1001 is on, the fix sorts the members right after removing the regions, so one `dotnet format` run is enough. It
sorts the way BRO1001's own fix does, including the blank lines [BRO1505](BRO1505.md) and
[BRO1509](BRO1509.md) want made before sorting, so the result doesn't depend on which fix runs first.
## Not reported
A region in code that an `#if` turns off: there the code around it is plain text, so neither the rule (BRO1112 or
[BRO1113](BRO1113.md)) nor the blank-line cleanup can be decided. In a multi-targeted project the region is reported
and removed through the target frameworks where the code is active.
## Compared with StyleCop
Same results as StyleCop, with the same fixed output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1113/
# BRO1113: Regions should not be placed inside code elements
`#region` isn't used inside a `{ }` block of a method, accessor, lambda or other code. Replaces StyleCop **SA1123**.
Regions between members and types, and in expression bodies (like StyleCop, which reports those as SA1124), are
[BRO1112](BRO1112.md).
## Example
### Before
```csharp
public class Importer
{
public int Import(string[] lines)
{
var count = 0;
#region Parse lines
foreach (var line in lines)
{
count += line.Length > 0 ? 1 : 0;
}
#endregion
return count;
}
}
```
### After
```csharp
public class Importer
{
public int Import(string[] lines)
{
var count = 0;
foreach (var line in lines)
{
count += line.Length > 0 ? 1 : 0;
}
return count;
}
}
```
## Why
A region inside a member hides part of its logic. If a part of a method deserves a name, extract it into a method
with that name.
## How the fix works
The `#region` and `#endregion` lines are removed, with the same blank-line cleanup as [BRO1112](BRO1112.md).
## Not reported
A region in code that an `#if` turns off, like [BRO1112](BRO1112.md#not-reported).
## Compared with StyleCop
Same results as StyleCop, with the same fixed output. StyleCop turns SA1123 on by default, and so does StyleBro's
preset (unlike [BRO1112](BRO1112.md), which the preset leaves off although StyleCop turns SA1124 on too).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1114/
# BRO1114: Do not combine fields
Every field (and event field) has a declaration of its own. Replaces StyleCop **SA1132**.
## Example
### Before
```csharp
using System;
public class Point
{
private int x, y;
[Obsolete("Use Label.")]
public string name = string.Empty, title = string.Empty;
}
```
### After
```csharp
using System;
public class Point
{
private int x;
private int y;
[Obsolete("Use Label.")]
public string name = string.Empty;
[Obsolete("Use Label.")]
public string title = string.Empty;
}
```
## Why
A combined declaration hides the second field at the end of a line, and a change to one field's type or modifiers
changes all of them.
## How the fix works
Each field gets its own declaration with the same attributes, modifiers and type, and the same documentation comment
if there is one, in the same order (initializers keep running in the same order). Event fields, and a field that spans
several lines from the next, are separated by a blank line, which [BRO1505](BRO1505.md) wants between them. A
documented field's copies are too, which [BRO1513](BRO1513.md) wants above each documentation comment. Local variables (`int a, b;` in a method) aren't reported, like
in StyleCop.
## Not reported
- **A comment inside the declaration** (`int a, /* note */ b;`).
- **A declaration that shares its first line with other code.**
## Compared with StyleCop
Same positions as StyleCop. StyleCop's fix keeps the attributes on the first field only, so the others lose them
(in the example, `title` would no longer be obsolete), and keeps the documentation on one field.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1115/
# BRO1115: Use shorthand for nullable types
`int?` instead of `Nullable`. Replaces StyleCop **SA1125**.
## Example
### Before
```csharp
using System;
using System.Collections.Generic;
public class Reading
{
public Nullable Value { get; set; }
public List> Samples { get; } = new List>();
}
```
### After
```csharp
using System;
using System.Collections.Generic;
public class Reading
{
public double? Value { get; set; }
public List Samples { get; } = new List();
}
```
## Why
`T?` is the form everyone reads at a glance, and the one the compiler and IDE show.
## How the fix works
Only `System.Nullable` itself is changed (a type of your own named `Nullable` isn't), qualified or not, also
inside other type arguments, `typeof(...)` and `default(...)`. Nested ones are done in one pass
(`Nullable>>` becomes `KeyValuePair?`).
## Not reported
- **Places where only the long form works or means something else**: `typeof(Nullable<>)`, `nameof(Nullable)`,
`Nullable.Equals(...)`, `cref`s, `using` aliases, and `is`/`as` operands.
## Compared with StyleCop
Same positions as StyleCop. StyleCop has no fix for SA1125.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1116/
# BRO1116: Closing parenthesis should be on line of opening parenthesis
An empty parameter or argument list is written `()`. Replaces StyleCop **SA1112**.
## Example
### Before
```csharp
public class Timer
{
public void Start(
)
{
this.Reset(
);
}
public void Reset()
{
}
}
```
### After
```csharp
public class Timer
{
public void Start()
{
this.Reset();
}
public void Reset()
{
}
}
```
## Why
An empty list split over two lines looks as if something was deleted from it.
## How the fix works
The line break and spaces between the parentheses are removed.
## Not reported
- **Attributes** (`[Obsolete(` + `)]`), like StyleCop; [BRO1402](BRO1402.md) removes empty attribute parentheses altogether.
- **A comment between the parentheses.**
## Compared with StyleCop
Same positions and the same fixed output as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1117/
# BRO1117: Comma should be on the same line as previous parameter
A comma in a parameter or argument list ends the line of the item before it. Replaces StyleCop **SA1113**.
## Example
### Before
```csharp
public class Mailer
{
public void Send(string to
, string subject)
{
}
}
```
### After
```csharp
public class Mailer
{
public void Send(string to,
string subject)
{
}
}
```
## Why
Commas at the start of a line are a habit from other languages; in C# code they stand out and break the usual layout.
## How the fix works
The comma moves to the end of the item before it; the next item keeps its line and indentation.
## Not reported
- **A comment next to the comma.**
## Compared with StyleCop
Same positions as StyleCop. StyleCop's fix gives the same result.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1118/
# BRO1118: Parameter list should follow declaration
No blank line between the opening parenthesis and the first parameter or argument. Replaces StyleCop **SA1114**.
## Example
### Before
```csharp
public class Mailer
{
public void Send(
string to,
string subject)
{
}
}
```
### After
```csharp
public class Mailer
{
public void Send(
string to,
string subject)
{
}
}
```
## Why
A blank line inside a parameter list separates the list from its own declaration.
## How the fix works
The blank lines are removed. Parameter lists, argument lists, attribute arguments and indexer brackets are checked.
## Not reported
- **A comment between the parenthesis and the first item**: the fix would have to move it (StyleCop reports it and has no fix).
## Compared with StyleCop
Same positions as StyleCop, except a comment before the first item. StyleCop has no fix for SA1114.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1119/
# BRO1119: Parameter should follow comma
No blank line between a comma and the next parameter or argument. Replaces StyleCop **SA1115**.
## Example
### Before
```csharp
public class Mailer
{
public void Send(
string to,
string subject)
{
}
}
```
### After
```csharp
public class Mailer
{
public void Send(
string to,
string subject)
{
}
}
```
## Why
Blank lines inside a parameter list make it look like two lists.
## How the fix works
The blank lines are removed. The same lists as [BRO1118](BRO1118.md) are checked.
## Not reported
- **A comment between the comma and the item.**
## Compared with StyleCop
Same positions as StyleCop. StyleCop has no fix for SA1115.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1120/
# BRO1120: Comments should contain text
An empty comment at the start or end of a comment (or on its own) is removed. Replaces StyleCop **SA1120**.
## Example
### Before
```csharp
public class Report
{
public int Total()
{
//
// Sums the lines.
//
// Rounding happens later.
//
return 0;
}
}
```
### After
```csharp
public class Report
{
public int Total()
{
// Sums the lines.
//
// Rounding happens later.
return 0;
}
}
```
## Why
An empty comment is noise; inside a comment, an empty line separates paragraphs and stays.
## How the fix works
Comments on consecutive lines form a group. Like StyleCop, empty `//` or `/* */` comments are reported at the start and the end of a group; the fix removes every empty comment at that end, so one run is enough (StyleCop's fix removes one per run). When the removed lines sat between two blank lines, the blank lines below them go too, so no two blank lines are left in a row ([BRO1517](BRO1517.md)). A comment after code starts a new group, and `////` (commented-out code) isn't empty.
## Not reported
- **Empty lines in the middle of a comment**, which separate paragraphs.
- **Empty comments that are all a pair of braces holds** (`catch` / `{` / `//` / `}`): they mark the block as empty on
purpose, and removing them would leave a bare `{ }`.
- **The file header, when `file_header_template` is set**: the comments the file starts with are the SDK's (IDE0073
writes them from the template, and `
Copyright ...
` puts an empty `//` line at the top and bottom of every header).
## Compared with StyleCop
Same positions as StyleCop, except a file header with `file_header_template` set and empty comments a pair of braces holds (StyleCop reports both). StyleCop's fix removes only the reported comment, so a second empty one at the same end is reported on the next run.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1121/
# BRO1121: Enum values should be on separate lines
Each enum value starts its own line. Replaces StyleCop **SA1136**.
## Example
### Before
```csharp
public enum Size
{
Small, Medium,
Large,
}
```
### After
```csharp
public enum Size
{
Small,
Medium,
Large,
}
```
## Why
One value per line makes values easy to find, document and reorder, and changes to one value show up as one line in a diff.
## How the fix works
A line break goes before each value that shares its line, at the indentation of the first value. An enum written on one line (`enum Size { Small, Medium }`) gets the same expansion as [BRO1509](BRO1509.md), including the trailing comma [BRO1401](BRO1401.md) wants, so both fixes give the same result.
## Not reported
- **A comment between two values.**
## Compared with StyleCop
Same positions as StyleCop. For an enum on one line StyleCop's fix leaves `{ Small,` and `Medium }` on the brace lines.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1122/
# BRO1122: Use literal suffix notation instead of casting
`1L` instead of `(long)1`. Replaces StyleCop **SA1139**.
## Example
### Before
```csharp
public class Limits
{
public long MaxBytes = (long)1024 * 1024 * 1024;
public float Scale = (float)0.5;
public decimal Fee = (decimal)2;
public long Offset = (long)-1;
}
```
### After
```csharp
public class Limits
{
public long MaxBytes = 1024L * 1024 * 1024;
public float Scale = 0.5F;
public decimal Fee = 2M;
public long Offset = -1L;
}
```
## Why
A suffix says the literal's type directly; a cast makes the reader check whether a conversion happens.
## How the fix works
The cast is replaced with the literal and the suffix for the type (`U`, `L`, `UL`, `F`, `D`, `M`; none for `int`).
A sign and parentheses stay where they were (`(long)(1)` becomes `(1L)`). When [BRO1135](BRO1135.md) is off (`init`
turns it off for code that writes `0u`), unsigned suffixes are written in lower case (`6u`, `1ul`); `L` stays upper case.
## Not reported
- **Casts whose literal would have a different value.** `(decimal)0.1234567890123456789` goes through `double` and
keeps 15 digits, while `0.1234567890123456789M` keeps all of them; `(decimal)1.50` is `1.5`, while `1.50M` prints
as `1.50`; `(float)` of a double literal can round differently than a float literal. The rule compares the cast's
constant value with the new literal's and only reports exact matches.
- Casts that don't change the type (`(int)1`; those are IDE0004's), casts to other types (`(short)1`), casts of
expressions (`(long)(a + 1)`), unsigned casts of negative numbers, casts with a comment inside, and a cast directly
after `-` or `+` whose result would start with a sign (`a-(long)-1` would become `a--1L`).
## Compared with StyleCop
StyleCop checks only that the type stays the same, so its fix can change a decimal's value or scale. Otherwise the
same positions and output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1123/
# BRO1123: Use tuple syntax
`(int, string)` instead of `ValueTuple`, and `(1, "a")` instead of `new ValueTuple(1, "a")`
or `ValueTuple.Create(1, "a")`. Replaces StyleCop **SA1141**.
## Example
### Before
```csharp
using System;
using System.Collections.Generic;
public class Ranges
{
public List> Spans { get; } = new List>();
public ValueTuple First() => ValueTuple.Create(0, 10);
}
```
### After
```csharp
using System;
using System.Collections.Generic;
public class Ranges
{
public List<(int, int)> Spans { get; } = new List<(int, int)>();
public (int, int) First() => (0, 10);
}
```
## Why
Tuple syntax is shorter and the form the compiler and IDE show.
## How the fix works
Types with 2 to 7 elements are rewritten wherever only a type can stand: declarations, type arguments, arrays,
nullables, `typeof`, `default`, casts. Nested ones are done in one edit
(`ValueTuple>` becomes `(int, (int, int))`). A creation becomes a tuple literal; a literal
argument that the element type converts (`new ValueTuple(1, null)`) gets a cast:
`((long)1, (string)null)`.
## Not reported
- **Creations with an argument that would name the element**: `ValueTuple.Create(x, y)` would become `(x, y)`, whose
elements are named `x` and `y`; code reading `t.Item1` would then be [BRO1124](BRO1124.md)'s on the next run.
- `new ValueTuple()` (there's no tuple literal for it; StyleCop's fix writes `()`, which doesn't compile),
creations with named arguments or an initializer, creations inside expression trees (they can't contain tuple
literals), and arguments that would need a cast but aren't literals.
- Types and creations with a comment inside (`ValueTuple`): the replacement would drop it.
- `ValueTuple` with one element, open generics (`typeof(ValueTuple<,>)`), more than 7 elements, `using` aliases.
## Compared with StyleCop
StyleCop checks the types of declarations (fields, properties, methods, parameters, delegates, lambdas) and of
creations, casts and `default`, but not locals, arrays, nullables, `typeof` or type arguments of calls; this rule
checks every place where only a type can stand. Under `dotnet format`, StyleCop's fix doesn't run ("doesn't support
Fix All in Solution"), so it leaves every tuple as it is.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1124/
# BRO1124: Refer to tuple elements by name
`item.Count` instead of `item.Item1` when the element has a name. Replaces StyleCop **SA1142**.
## Example
### Before
```csharp
public class Stock
{
public (int Count, string Sku) Lookup() => (3, "A-1");
public string Describe()
{
var item = Lookup();
return item.Item2 + ": " + item.Item1;
}
}
```
### After
```csharp
public class Stock
{
public (int Count, string Sku) Lookup() => (3, "A-1");
public string Describe()
{
var item = Lookup();
return item.Sku + ": " + item.Count;
}
}
```
## Why
The name says what the element is; `Item2` makes the reader look up the declaration.
## How the fix works
`ItemN` is replaced with the element's name, also after `?.` and for elements after the seventh (`Item8`).
## Not reported
- Elements without a name, `System.Tuple` (its `Item1` is a property, not a tuple element), and `nameof(t.Item1)`,
whose result would change.
- An element read through a receiver with the element's own name (`x.Item1` where the element is `x`): `x.x` says
nothing. Such names are usually inferred from another lambda's parameter (`.Select(x => (x, F(x)))`).
## Compared with StyleCop
Same positions, except `nameof(t.Item1)` and `x.Item1` with an element named `x`. Under `dotnet format`, StyleCop's fix throws (a null reference), so it
changes nothing.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1125/
# BRO1125: Use lambda syntax
`(s, e) => { }` instead of `delegate (object s, EventArgs e) { }`. Replaces StyleCop **SA1130**.
## Example
### Before
```csharp
using System;
using System.Collections.Generic;
public class Button
{
public event EventHandler? Clicked;
public void Wire(List log)
{
Clicked += delegate { log.Add("clicked"); };
log.RemoveAll(delegate (string entry) { return entry.Length == 0; });
}
}
```
### After
```csharp
using System;
using System.Collections.Generic;
public class Button
{
public event EventHandler? Clicked;
public void Wire(List log)
{
Clicked += (sender, e) => { log.Add("clicked"); };
log.RemoveAll(entry => { return entry.Length == 0; });
}
}
```
## Why
Lambdas are the modern form of anonymous methods; mixing both makes code look older than it is.
## How the fix works
Like StyleCop's fix: the parameter types are dropped, `async` stays, and a cast's operand gets parentheses
(`(Action)(() => { })`). A `delegate { }` without a parameter list gets the delegate's parameter names, made unique
against names in scope and in the body (`sender1`). The body isn't touched. The lambda is bound in place first: it
must convert to the same delegate type and, as an argument, call the same overload.
## Not reported
- **Calls where the lambda would pick another overload or be ambiguous**: `Run(delegate { return 1; })` with
`Run(Func)` and `Run(Expression>)` would call the expression overload.
- Anonymous methods without a target type (`var f = delegate (int x) { ... }`: a lambda without parameter types has
none), parameters with `ref`/`out`/`params`, attributes or defaults, a missing parameter list for a delegate with
`ref`/`out` parameters, and comments between `delegate` and the body.
## Compared with StyleCop
Same positions, except the cases above (StyleCop checks overloads only for arguments, so its fix for a `var` breaks
the build) and calls like `list.ForEach(delegate (int item) { ... })`, which StyleCop misses and StyleBro converts.
StyleCop's fix also leaves two spaces after `=` and pulls a body on its own line up behind `=>`.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1126/
# BRO1126: Using directives should be qualified
A using directive inside a namespace names its namespace or type fully. Replaces StyleCop **SA1135**. Only relevant
when usings go inside the namespace (StyleCop's SA1200 default; the preset puts them outside).
## Example
### Before
```csharp
namespace System.Reporting
{
using IO;
using Builder = Text.StringBuilder;
public class Export
{
public Builder Lines { get; } = new Builder();
public FileInfo? Target { get; set; }
}
}
```
### After
```csharp
namespace System.Reporting
{
using System.IO;
using Builder = System.Text.StringBuilder;
public class Export
{
public Builder Lines { get; } = new Builder();
public FileInfo? Target { get; set; }
}
}
```
## Why
Inside a namespace, `using IO;` means something different depending on the namespace around it, so moving the code or
renaming the namespace can change what it refers to.
## How the fix works
The name is replaced with its fully qualified form, type arguments included (`int` stays `int`). `using static` and
aliases are checked too. An alias's own type stays a name (`System.ValueTuple`,
`System.Nullable<...>`, `System.Int32`): `(.., ..)`, `T?` and `int` there need C# 12. Inside type arguments the
shorthand is kept, and so is the way a built-in type is written: `System.ReadOnlyMemory` stays (with
`dotnet_style_predefined_type_*` set to `false`, IDE0049 would report `char`, and `dotnet format` can't fix it in an
alias), and `List` becomes `System.Collections.Generic.List`, like StyleCop.
When usings are sorted (`dotnet_sort_system_directives_first` or `dotnet_separate_import_directive_groups` is set, so
`dotnet format` sorts them), the fix sorts them too, with the same organizer: the qualified name usually belongs
elsewhere, and `dotnet format` would otherwise move it only on its next run.
## Not reported
- Usings outside a namespace, names starting with `global::` or an alias, and a type of the namespace the directive
is in (like StyleCop).
- **A type argument that starts with an alias** (`using T2 = System.ValueTuple;` with
`using Tasks = System.Threading.Tasks;` outside): it's kept as written, like an alias at the start of the name.
- **A qualified name that an enclosing namespace would hide**: inside `namespace Lib.Inner`, with a namespace
`Lib.Inner.Lib` around, `Lib.Thing` would mean `Lib.Inner.Lib.Thing`.
## Compared with StyleCop
Same positions and output, except the hidden-name case above (StyleCop's fix then refers to the wrong namespace) and
a type argument that starts with an alias: StyleCop accepts `using T1 = Tasks.Task;` but reports
`using T2 = System.ValueTuple;` ([#3884](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3884), open).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1127/
# BRO1127: Query clause should follow previous clause
No blank line between the clauses of a query expression. Replaces StyleCop **SA1102**.
## Example
### Before
```csharp
using System.Linq;
public class Orders
{
public object Open(int[] ids) =>
from id in ids
where id > 0
select id;
}
```
### After
```csharp
using System.Linq;
public class Orders
{
public object Open(int[] ids) =>
from id in ids
where id > 0
select id;
}
```
## Why
A query is one expression; a blank line in the middle makes it look like two.
## How the fix works
The blank lines are removed; the clause keeps its indentation.
## Not reported
- Blank lines with a comment line between them (removing only the blank ones wouldn't satisfy the rule, and the
comment would have to move), and directives between the clauses (like StyleCop).
## Compared with StyleCop
Same positions and output, except the commented case above, which StyleCop reports.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1128/
# BRO1128: Query clauses should be on separate lines or all on one line
A query's clauses are all on one line or each on its own line. Replaces StyleCop **SA1103**.
## Example
### Before
```csharp
using System.Linq;
public class Orders
{
public object Large(int[] amounts)
{
var large = from a in amounts where a > 100 orderby a
select a;
return large;
}
}
```
### After
```csharp
using System.Linq;
public class Orders
{
public object Large(int[] amounts)
{
var large = from a in amounts
where a > 100
orderby a
select a;
return large;
}
}
```
## Why
A query split at some clauses but not others hides its structure; one clause per line reads top to bottom.
## How the fix works
Every clause that shares a line with the clause before it moves to its own line. The indentation is StyleCop's: the
line the `from` is on, one level deeper when the `from` isn't the first thing on that line (as in `var q = from ...`),
using `indent_style`/`indent_size` from `.editorconfig`. The diagnostic is on the `from` keyword.
## Not reported
- Queries whose clauses share a line across a comment (the fix couldn't make the layout consistent without moving it).
## Compared with StyleCop
StyleCop's fix offers two actions, all on one line or each on its own line, and under `dotnet format` applies whichever
comes first, which changes from run to run (seen while comparing the two), and the one-line action makes long
lines, pulls the query up behind `=>` and writes `( from`. StyleBro always puts each
clause on its own line, like [BRO1108](BRO1108.md) for argument lists. StyleCop doesn't report SA1103 when it reports
SA1104 or SA1105 in the same query, which leaves the rest of a mixed query to a second run; StyleBro reports
[BRO1129](BRO1129.md)/[BRO1130](BRO1130.md) and BRO1128 together, so one run fixes the query.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1129/
# BRO1129: Query clause should begin on new line when previous clause spans multiple lines
A clause after a clause that spans several lines starts on its own line. Replaces StyleCop **SA1104**.
## Example
### Before
```csharp
using System.Linq;
public class Orders
{
public object InRange(int[] amounts) =>
from a in amounts
where a > 10 &&
a < 100 select a;
}
```
### After
```csharp
using System.Linq;
public class Orders
{
public object InRange(int[] amounts) =>
from a in amounts
where a > 10 &&
a < 100
select a;
}
```
## Why
A clause starting at the end of a multi-line clause is easy to miss.
## How the fix works
The clause moves to its own line, indented like [BRO1128](BRO1128.md) indents clauses.
## Not reported
- A clause after a comment on the same line (the fix would have to move the comment).
## Compared with StyleCop
Same positions and output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1130/
# BRO1130: Query clauses spanning multiple lines should begin on own line
A clause that spans several lines starts on its own line. Replaces StyleCop **SA1105**.
## Example
### Before
```csharp
using System.Linq;
public class Orders
{
public object InRange(int[] amounts) =>
from a in amounts where a > 10 &&
a < 100
select a;
}
```
### After
```csharp
using System.Linq;
public class Orders
{
public object InRange(int[] amounts) =>
from a in amounts
where a > 10 &&
a < 100
select a;
}
```
## Why
A multi-line clause that starts at the end of another line hides where it begins.
## How the fix works
The clause moves to its own line, indented like [BRO1128](BRO1128.md) indents clauses.
## Not reported
- A clause after a comment on the same line (the fix would have to move the comment).
## Compared with StyleCop
Same positions and output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1131/
# BRO1131: Do not prefix calls with base unless local implementation exists
`this.Reset()` instead of `base.Reset()` when the type has no `Reset` of its own. Replaces StyleCop **SA1100**.
## Example
### Before
```csharp
public class Connection
{
public void Reset()
{
}
}
public class PooledConnection : Connection
{
public void Return()
{
base.Reset();
}
}
```
### After
```csharp
public class Connection
{
public void Reset()
{
}
}
public class PooledConnection : Connection
{
public void Return()
{
this.Reset();
}
}
```
## Why
`base.` tells the reader that the type overrides or hides the member and deliberately calls the inherited one. When
there is nothing to bypass, it sends them looking for an override that doesn't exist.
## How the fix works
`base` becomes `this`. The rule only reports when `this` binds to exactly the same member.
When the SDK's `dotnet_style_qualification_for_method` (or `_property`, `_field`, `_event`, for that kind of member) is
`false`, which is the preset's setting, the fix removes `base.` instead (`Reset()`), as long as the plain name means the
same member there; a parameter or local with that name keeps `this.`. `base[i]` always becomes `this[i]`. With the key
set to `true` or not set, the fix writes `this.` like StyleCop.
## Not reported
- **The type overrides or hides the member** (`base.Describe()` inside `override Describe()`): `base.` is needed.
- **A virtual, abstract or override member in a type that isn't sealed.** `this.Reset()` dispatches virtually, so a
derived type's override would run where `base.Reset()` ran the base implementation. In a sealed type (or a struct)
nothing can override it, and it is reported.
## Compared with StyleCop
StyleCop also reports the virtual case in types that aren't sealed, and its fix can change which method runs.
Otherwise the same positions and output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1132/
# BRO1132: Block statements should not contain embedded comments
A comment between a statement's header and the `{` of its block moves into the block. Replaces StyleCop **SA1108**.
## Example
### Before
```csharp
public class Cache
{
private object items;
public object Get()
{
if (items == null) // first use
{
items = new object();
}
else // already loaded
{
return items;
}
return items;
}
}
```
### After
```csharp
public class Cache
{
private object items;
public object Get()
{
if (items == null)
{
// first use
items = new object();
}
else
{
// already loaded
return items;
}
return items;
}
}
```
## Why
A comment squeezed between `if (...)` and `{` is easy to miss and sits apart from both the condition and the code it
explains. On its own line at the top of the block it reads as what it usually is: a note about the branch.
## How the fix works
Each comment goes on its own line right after `{` (below blank lines there, which are [BRO1503](BRO1503.md)'s),
indented like the block's first line (one level deeper than `{` in an empty block), in its original order and above any
comment already there. The rest of the header's line stays as it was; a line that held only the comment is removed.
Checked like StyleCop: the blocks of `if`, `else`, `for`, `foreach`, `while`, `do`, `lock`, `try`, `catch`, `finally`,
`checked`, `unchecked` and `fixed`, and a `switch` statement's braces. Both `//` and `/* */` comments count, after the
header on its line or on a line of their own before `{`.
A comment that another tool reads by its line, such as a suppression (`catch (X) when (...) // slopwatch-ignore: SW003`),
moves too, like any other: StyleCop reports it the same way, so StyleBro does as well (the owner's
[decision](https://github.com/bisforboman/stylebro/blob/main/docs/decisions/2026-10-15-bro1132-keeps-stylecops-behaviour.md)).
Where that tool needs the comment on the header's line, turn BRO1132 off (`dotnet_diagnostic.BRO1132.severity = none`)
or accept the move and suppress the finding the tool's own way.
## Not reported
- **`////` comments** (commented-out code), like StyleCop.
- **A comment after `{`** (`{ // note`), inside the condition, or above the statement.
- **Other blocks**: `using`, a deconstructing `foreach (var (a, b) in ...)`, lambda bodies, plain blocks
(StyleCop doesn't check them). Types, namespaces, members, accessors and local functions are
[BRO1134](BRO1134.md)'s.
- **A header spanning several lines** (from the keyword to the token before `{`): in a condition split over lines,
the comment at its end usually explains the last line, often as one of a column of comments, and would read as a
note about the body once moved. Each clause counts on its own: `else // c` after a multi-line `if` is reported.
- **A single-line block** (`if (x) // note` + `{ y(); }`): there's no line inside it for the comment.
- **A comment spanning lines** or **a directive** between the header and `{`.
## Compared with StyleCop
StyleCop has no fix. Same positions, except the skipped cases above (the multi-line header skip was the owner's
decision, see [docs/decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1133/
# BRO1133: Check for null in one form
Null checks are written one way in the whole codebase: `x is null` and `x is not null` (the default), or `x == null`
and `x != null`. Not a StyleCop rule.
## Example
### Before
```csharp
public class Cache
{
private object entry;
public bool IsEmpty => entry == null;
public object Get(object fallback)
{
if (entry != null)
{
return entry;
}
return fallback;
}
}
```
### After
```csharp
public class Cache
{
private object entry;
public bool IsEmpty => entry is null;
public object Get(object fallback)
{
if (entry is not null)
{
return entry;
}
return fallback;
}
}
```
## Why
Both forms are common, and a codebase that mixes them makes the reader wonder whether the difference matters (it
does when the type defines its own `==`, which this rule leaves alone). The default is the form that never calls a
user-defined `==`, so a null check means the same whatever the type; `== null` is common too (most of the reference
repositories StyleBro is tested on use it), so it's an option. See [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md).
## How the fix works
Only the operator changes: `==` becomes `is`, `!=` becomes `is not`, and back. With `pattern_matching` (the default),
`null == x` becomes `x is null` (there is no `null is x`); with `equality_operator` it is left to
[BRO1103](BRO1103.md), which puts the constant on the right.
## Configuration
```ini
[*.cs]
# pattern_matching (default): x is null, x is not null
# equality_operator: x == null, x != null
stylebro_null_check_style = pattern_matching
```
## Not reported
Checks whose meaning or validity would change:
- **A user-defined `==`** on the operand's type (a class with `operator ==`, records): `x is null` never calls it, so
the result could differ. The core library's operators (`string`, `Type`, delegates) are null-safe and are converted,
and so are nullable value types (`int?`, `S?` with a lifted operator): comparing them with null only reads `HasValue`.
- **`g is null` on a nullable value type whose `==` comes from the type** (`Guid?`, a struct with `operator ==`): `g == null`
only reads `HasValue`, but it names the type's operator, which a banned-API list may forbid (Jellyfin bans `Guid`'s
`==`). `int?` and other built-in types are converted.
- **`dynamic` operands**: their `==` is chosen at run time.
- **Inside expression trees** (`Expression>` lambdas, query clauses over `IQueryable`): patterns don't
compile there.
- **`x is not null` before C# 9 and `x is null` before C# 7** (the project's `LangVersion`). A file compiled more than
once (several target frameworks, or linked into another project) is fixed only when its LOWEST version allows it: a
multi-targeted project's net472 build uses C# 7.3 unless `LangVersion` is set (LibGit2Sharp's tests stopped
compiling before). The warning stays in the copies with a newer version.
- **Operands that aren't null-able**: non-nullable value types (`i == null` for an `int`, already a compiler warning),
type parameters constrained to `struct`. Unconstrained type parameters are converted: both forms compile and mean the
same.
- **Other comparisons**: `x == default`, `x == default(T)`, patterns other than a plain `null` (`is not (null)`,
`is null or ""`, `is { }`).
- **Where the new form would bind differently**: `x == null` with an operand containing an operator that binds looser
than `is` (`a + b == null`, `o as T == null`), and `x is null` as an operand of `==`, `<`, `+` and the like
(`b == o is null` would become `(b == o) == null`).
- **A comment or line break between `is` and `not`**, and a comment between `null` and the operator in `null == x`.
## Compared with StyleCop
StyleCop has no such rule. Roslynator's RCS1248 does the same, off by default and with its own option
(`roslynator_null_check_style`). The .NET SDK has no rule for it: IDE0041 only rewrites `ReferenceEquals(x, null)`,
IDE0150 `x is object`, IDE0083 `!(x is null)`, and IDE0078 `x == null || x == ""`, all into the pattern form (checked
with `dotnet format style`, SDK 10.0.401: none of them changes a plain `x == null`). They point the same way as the
default; with `equality_operator`, turn them off, or BRO1133 turns their output back on the next run. `stylebro-migrate` turns this rule off, so
migrating doesn't change a StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1134/
# BRO1134: Declarations should not contain embedded comments
A comment between the header of a type, namespace, member, accessor or local function and its `{` moves into the
body. [BRO1132](BRO1132.md) (StyleCop's SA1108) does the same for statements. Not a StyleCop rule: proposed in StyleCop's
issue [#605](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/605), which the maintainers favored and nobody
built.
## Example
### Before
```csharp
public class Cache
// keeps one item
{
private object item;
public object Get()
// loads on first use
{
return item ??= new object();
}
public int Count
// 0 or 1
{
get
{
return item == null ? 0 : 1;
}
}
}
```
### After
```csharp
public class Cache
{
// keeps one item
private object item;
public object Get()
{
// loads on first use
return item ??= new object();
}
public int Count
{
// 0 or 1
get
{
return item == null ? 0 : 1;
}
}
}
```
## Why
A comment on its own line between a declaration's header and `{` splits the declaration in two. At the top of the body it reads as a note about the type or member, next to the code it
describes. A note that documents the member for its callers belongs in a `///` comment above it instead.
## How the fix works
Like [BRO1132](BRO1132.md)'s: each comment goes on its own line right after `{` (below blank lines there, which are
[BRO1503](BRO1503.md)'s), indented like the body's first line (one level deeper than `{` in an empty body), in its
original order. A line that held only the comment is removed. [BRO1504](BRO1504.md) doesn't ask for a blank line above
a comment this rule moves.
Checked: classes, structs, interfaces, records and enums with a body; block-scoped namespaces; methods, constructors,
finalizers, operators and conversion operators with a block body; the accessor list of a property, indexer or event; an
accessor's block body; local functions with a block body. Both `//` and `/* */` comments count, on a line of their own before `{`, or after the
header when `{` follows on the same line.
## Not reported
- **A comment that ends the header's line, with `{` on the next line** (`public int GetHashCollissions() // legacy
spelling`): a note on the header, which often explains the name or a parameter rather than the body (the owner's
decision, see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)). A comment on a line of its own below it still moves.
- **`////` comments** (commented-out code).
- **A comment after `{`**, inside the header (between parameters), or above the declaration, and a comment after an
attribute on its own line (`[Fact] // flaky`).
- **A header spanning several lines** (parameters, a base list or constraints on further lines), like BRO1132: the
comment usually explains the last line, not the body.
- **A body on one line with its `{`** (`void M() // note` + `{ }`): there's no line inside it for the comment.
- **A comment spanning lines** or **a directive** between the header and `{`.
- **Expression bodies** (`=> ...`), file-scoped namespaces and declarations without a body: there's no `{`.
## Compared with StyleCop
StyleCop has no such rule: SA1108 checks statements only. Issue
[#605](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/605) proposed it for declarations and accessors.
`stylebro-migrate` turns this rule off, so migrating doesn't change a StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1135/
# BRO1135: Integer literal suffixes should be upper case
The suffix of an integer literal is upper case: `1L`, `2U`, `3UL`. Not a StyleCop rule (requested in StyleCop issue
[#1563](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1563); Sonar S818).
## Example
### Before
```csharp
public class Limits
{
public const long Max = 100l;
public const uint Mask = 0xFFu;
public const ulong Big = 1ul;
}
```
### After
```csharp
public class Limits
{
public const long Max = 100L;
public const uint Mask = 0xFFU;
public const ulong Big = 1UL;
}
```
## Why
A lower-case `l` looks like the digit `1`: `100l` reads as `1001`. The C# specification recommends `L`, and the
compiler warns about `l` (CS0078) but has no fix for it. `u` follows so that a suffix is written one way.
## How the fix works
Only the suffix's letters change (`ul`, `Ul` and `uL` become `UL`; `lu` becomes `LU`). Hex and binary literals work the
same way: their suffix letters can't be digits. [BRO1122](BRO1122.md), which replaces casts with suffixes, writes upper
case, so the two rules agree.
## Configuration
```ini
[*.cs]
# all (default): every integer suffix letter upper case
# l_only: only 'l' must be upper case
# lone_l: only a suffix that is just 'l' ('1l'), like Sonar's S818; '1ul' and '1lu' are fine
stylebro_upper_case_literal_suffixes = all
```
With `l_only`, only a suffix with a lower-case `l` is reported, and the fix upper-cases just the `l`; a `u` stays as
written.
```csharp
// Before (stylebro_upper_case_literal_suffixes = l_only)
public const long Max = 100l;
public const uint Mask = 0xFFu;
public const ulong Big = 1ul;
// After
public const long Max = 100L;
public const uint Mask = 0xFFu;
public const ulong Big = 1uL;
```
With `lone_l`, only a suffix that is just `l` is reported (`100l` becomes `100L`); `1ul`, `1Ul` and `1lu` stay. That is
exactly what Sonar's S818 reports, and what `stylebro-migrate` writes for a repository that enforces S818.
## Not reported
- **Real literals**: `1.5f`, `2d` and `3m` stay as they are, and so does the exponent's `e`. Lower case is the common
way to write them (most of the surveyed repositories use `f`, `d` and `m`), and none of these letters looks like a
digit.
## Compared with StyleCop
StyleCop has no such rule. Sonar's S818 reports only a suffix that is just `l`; BRO1135 also upper-cases `u` and an `l`
next to a `u` unless `stylebro_upper_case_literal_suffixes = lone_l`.
`stylebro-migrate` turns it off (see [Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1136/
# BRO1136: A lambda's single parameter should not be in parentheses
A lambda with one parameter that has no type, modifier or attribute is written `x => ...`, not `(x) => ...`. Not a
StyleCop rule (proposed in StyleCop issue [#762](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/762)).
## Example
### Before
```csharp
using System.Collections.Generic;
using System.Linq;
public class Orders
{
public IEnumerable Large(IEnumerable amounts) => amounts.Where((amount) => amount > 100);
}
```
### After
```csharp
using System.Collections.Generic;
using System.Linq;
public class Orders
{
public IEnumerable Large(IEnumerable amounts) => amounts.Where(amount => amount > 100);
}
```
## Why
The parentheses add nothing for a single untyped parameter, and most code (and the SDK's own code generation) leaves
them out. One form is easier to scan.
## How the fix works
The parameter list `(x)` is replaced with `x`; `async` and `static` in front stay.
## Not reported
- No parameter or several: `() => 1`, `(x, y) => x + y`.
- A typed parameter, a modifier or a default value: `(int x) => x`, `(ref int x) => x++`.
- Attributes on the parameter or the lambda, and an explicit return type: `([A] x) => x`, `[A] (x) => x`,
`int (x) => x` (they need the parentheses).
- A comment inside the parentheses.
## Compared with StyleCop
StyleCop has no such rule; a maintainer suggested folding it into SA1119 ([BRO1405](BRO1405.md)), which never happened.
`stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1137/
# BRO1137: Remove a redundant 'return;' or 'yield break;'
A `return;` as the last statement of a body, or a `yield break;` as the last statement of an iterator, does nothing:
the body ends there anyway. Not a StyleCop rule (proposed in StyleCop issue
[#760](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/760); Roslynator RCS1134).
## Example
### Before
```csharp
using System.Collections.Generic;
public class Log
{
private readonly List lines = new List();
public void Add(string line)
{
if (line.Length == 0)
{
return;
}
lines.Add(line);
return;
}
public IEnumerable Errors()
{
foreach (var line in lines)
{
if (line.StartsWith("error"))
{
yield return line;
}
}
yield break;
}
}
```
### After
```csharp
using System.Collections.Generic;
public class Log
{
private readonly List lines = new List();
public void Add(string line)
{
if (line.Length == 0)
{
return;
}
lines.Add(line);
}
public IEnumerable Errors()
{
foreach (var line in lines)
{
if (line.StartsWith("error"))
{
yield return line;
}
}
}
}
```
## Why
A jump that goes where the code would go anyway makes the reader look for a reason that isn't there.
## How the fix works
The statement's line is removed, with the blank lines above it (they would end up before the body's `}`, which
[BRO1518](BRO1518.md) reports). A statement that shares its line with other code is removed with the spaces before it:
`void M() { return; }` becomes `void M() { }`.
## Not reported
- A `return;` or `yield break;` in a nested block (`if (x) { ...; return; }`): code after the block would run.
- A `yield break;` that is the iterator's only `yield` (it is what makes the method an iterator).
- A statement with a comment on it or above it, or a label (`end: return;`).
- A directive right above the statement (`#endif`, `#if`): which statement is last can depend on it.
- `return x;` (a value) and expression-bodied members.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1138/
# BRO1138: A string literal should be a plain string when nothing needs more
A string literal uses `$`, `@` or raw string quotes only when it needs them: `$` for interpolations, `@` for
backslashes, quotes or line breaks, raw strings for quotes or several lines. Not a StyleCop rule (Roslynator RCS1214,
RCS1192, RCS1262).
## Example
### Before
```csharp
public class Messages
{
public string Done() => $"Done";
public string Folder() => @"logs";
public string Title() => """Report""";
public string Template() => $"Use {{name}} here";
public string Path() => $@"C:\{{temp}}";
}
```
### After
```csharp
public class Messages
{
public string Done() => "Done";
public string Folder() => "logs";
public string Title() => "Report";
public string Template() => "Use {name} here";
public string Path() => @"C:\{temp}";
}
```
## Why
`$` and `@` tell the reader to look for interpolations and escapes. When there are none, they are noise, and a `$`
without holes hides the doubled `{{`/`}}` that only an interpolated string needs.
## How the fix works
The whole simplification is one edit, so one `dotnet format` run is enough:
- `$"..."` without interpolations: the `$` goes and `{{`/`}}` become `{`/`}`. `$@"..."` (or `@$"..."`) also loses the
`@` when the text has no backslash, `""` or line break, else it stays a verbatim string.
- `@"..."` without a backslash, `""` or line break becomes `"..."` (also with `u8`).
- A single-line raw string (`"""..."""`, also `u8` and an interpolated one without holes) without a backslash or quote
becomes `"..."`. A multi-line raw string with `$` but no holes only loses the `$`s.
## Not reported
- Empty strings (`$""`, `@""`): [BRO1106](BRO1106.md) turns them into `string.Empty`.
- An interpolated string that isn't used as a `string` (or `object`): as a `FormattableString`, `IFormattable` or an
interpolated string handler (`Debug.Assert(x, $"...")`, logging handlers) the plain string would call something else.
- Interpolated strings with holes, including alignment and format specifiers.
- Multi-line raw strings without `$`.
## Compared with the SDK
The SDK has no rule for these: IDE0071 simplifies what is inside a hole (`{x.ToString()}` -> `{x}`), and nothing
removes an unneeded `$`, `@` or raw string.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1139/
# BRO1139: Write 'else if' on one line
An `if` that follows `else` on the next line, or that is the only statement of the else's block, is written as
`else if`. Not a StyleCop rule (Roslynator RCS0041, RCS1006).
## Example
### Before
```csharp
public class Grades
{
public string Name(int score)
{
if (score >= 90)
{
return "A";
}
else
{
if (score >= 75)
{
return "B";
}
else
{
return "C";
}
}
}
}
```
### After
```csharp
public class Grades
{
public string Name(int score)
{
if (score >= 90)
{
return "A";
}
else if (score >= 75)
{
return "B";
}
else
{
return "C";
}
}
}
```
## Why
An `else` that only holds another `if` is the next branch of the same decision. Written as `else if`, the branches line
up and the code doesn't drift right with each one.
## How the fix works
The gap between `else` and `if` becomes one space, the block's `}` goes, and the if's other lines move left by as much
as the `if` moved (relative to the `else`'s line). Where the whitespace formatting had left the `if`'s body at the
`if`'s column, each line goes where the joined chain wants it instead: its `else` lines and braces to the `else if`'s
column, the rest one level deeper. Nested ones are fixed together, so one run is enough.
When the joined chain mixes clauses with and without braces, which ones [BRO1514](BRO1514.md) and
[BRO1516](BRO1516.md) report changes; the fix then adds the braces they want too, so the rules converge in one
`dotnet format` run in any order.
## Not reported
- A comment or directive between `else` and `if`, or between the inner statement and the block's braces.
- A block followed by another `else` (`if (a) if (b) X(); else { if (c) Y(); } else Z();`): without the block, that
`else` would belong to the inner `if`.
- An `if` whose lines can't move left (not indented far enough), or that contains a token spanning several lines (a
multi-line string) when its lines would move.
- A block with other statements next to the `if`.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1140/
# BRO1140: A record with an empty body should end with ';'
A record with a parameter list and nothing in its body ends with `;`: `record Point(int X, int Y);`. Not a StyleCop
rule (Roslynator RCS1251, Meziantou MA0206).
## Example
### Before
```csharp
public record Point(int X, int Y)
{
}
public record struct Size(int Width, int Height) { }
```
### After
```csharp
public record Point(int X, int Y);
public record struct Size(int Width, int Height);
```
## Why
The empty braces suggest a body that isn't there. The positional record's short form says the same in one line.
## How the fix works
The text from the end of the declaration's header (parameters, base type or constraints) to the `}` becomes `;`. A
`;` after the `}` (which [BRO1101](BRO1101.md) would remove) is part of the replaced text, and an empty body that
[BRO1509](BRO1509.md) expands first is replaced the same way, so the rules converge in any order.
## Not reported
- A record without a parameter list (`record R { }`).
- A comment or directive inside or around the braces.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1141/
# BRO1141: Object creation with an initializer: parentheses in one style
An object creation with an initializer and no arguments is written without the empty parentheses
(`new List { 1 }`, the default) or with them (`new List() { 1 }`). Not a StyleCop rule (Roslynator RCS1050).
## Example
### Before
```csharp
using System.Collections.Generic;
public class Settings
{
public string Name { get; set; } = "";
public List Ports = new List() { 80, 443 };
public static Settings Default() => new Settings() { Name = "default" };
}
```
### After
```csharp
using System.Collections.Generic;
public class Settings
{
public string Name { get; set; } = "";
public List Ports = new List { 80, 443 };
public static Settings Default() => new Settings { Name = "default" };
}
```
## Why
The parentheses say nothing when there are no arguments; the initializer shows that an object is built. One way to
write it keeps creations consistent.
## How the fix works
`omit` removes the parentheses and any spaces before them; `include` inserts `()` right after the type.
## Configuration
```ini
[*.cs]
# omit (default): new List { 1 }; include: new List() { 1 }
stylebro_object_creation_parentheses = omit
```
## Not reported
- Creations with arguments (`new List(4) { 1 }`) or without an initializer (`new List()`).
- Target-typed `new() { ... }`: it needs its parentheses.
- A comment between the type and the parentheses.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1142/
# BRO1142: Do not combine local variables
Each local variable has its own declaration: `int a = 1, b;` becomes `int a = 1;` and `int b;`. Not a StyleCop rule
(Roslynator RCS1081, Sonar S1659); [BRO1114](BRO1114.md) (SA1132) does the same for fields.
## Example
### Before
```csharp
public class Totals
{
public int Sum(int[] values)
{
int total = 0, count = 0;
foreach (var value in values)
{
total += value;
count++;
}
return count == 0 ? 0 : total;
}
}
```
### After
```csharp
public class Totals
{
public int Sum(int[] values)
{
int total = 0;
int count = 0;
foreach (var value in values)
{
total += value;
count++;
}
return count == 0 ? 0 : total;
}
}
```
## Why
One declaration per line is easier to read, to change and to review: adding, removing or commenting one variable
touches one line, and every variable's type is next to its name.
## How the fix works
Every variable gets a declaration with the same modifiers (`const`, `ref`) and type, on its own line at the same
indentation. A declaration that shares its line with other code (`void M() { int a, b; }`) is split on that line
(`{ int a; int b; }`), so it converges with [BRO1509](BRO1509.md)'s expansion in either order.
**The SDK's `var` preference is followed.** Where `csharp_style_var_for_built_in_types`, `_when_type_is_apparent` or
`_elsewhere` is `true` with a severity that reports (`suggestion`, `warning`, `error`, or IDE0007 turned on by its own
severity key), the new declarations say `var` (`bool a = true, b = true;` becomes `var a = true;` and `var b = true;`),
so IDE0007 doesn't report them. Like the SDK, a built-in type (`bool`, `string`) is judged by the first setting, an
initializer that names the type (`new T()`, a cast, `as T`) by the second, anything else by the third.
## Not reported
- `using` declarations (`using Stream a = ..., b = ...;`): the dispose order is part of the code's meaning.
- `for` initializers and `fixed` statements: they aren't local declaration statements, and splitting them would change
the scope or not compile.
- A comment or directive inside the declaration.
- A declaration where the `var` preference applies but `var` can't be written with the same meaning: a variable without
an initializer, with `null`, `default` or a target-typed `new()`, or whose initializer has another type
(`long a = 1, b = 2;`). Explicit types there would break the preference, `var` would change the code (found in
spreadcheetah: 32 IDE0007 warnings after a split).
## Compared with StyleCop
StyleCop's SA1132 checks fields only. `stylebro-migrate` turns BRO1142 off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1143/
# BRO1143: No 'else' after a branch that ends in a jump
When the `if` branch ends in `return`, `throw`, `break`, `continue`, `goto` or `yield break`, the `else` is not needed:
its body can follow the `if` statement. Not a StyleCop rule: from Meziantou's
[MA0071](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0071.md) and Roslynator's
[RCS1211](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1211). **Off by default**, also in the preset.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1143.severity = warning
```
### Before
```csharp
public static class Pricing
{
public static decimal Price(decimal amount, bool member)
{
if (amount <= 0)
{
return 0;
}
else
{
var discount = member ? 0.1m : 0m;
return amount * (1 - discount);
}
}
}
```
### After
```csharp
public static class Pricing
{
public static decimal Price(decimal amount, bool member)
{
if (amount <= 0)
{
return 0;
}
var discount = member ? 0.1m : 0m;
return amount * (1 - discount);
}
}
```
## Why
The early return is a guard: after it, the normal path continues at the method's own level instead of one level
deeper. It's a matter of taste, though: some teams prefer the symmetric `if`/`else` when both branches are equally
important. That's why the rule is off by default; turn it on with `dotnet_diagnostic.BRO1143.severity = warning`.
## How the fix works
It removes the `else` (and the braces of its block) and moves the body one indentation level out, below the `if`
statement. Indentation follows `indent_style`/`indent_size`. When the `if` branch is a multi-line block,
[BRO1519](BRO1519.md) wants a blank line after its `}`, and the fix adds it. An `else` body that contains another such
`if`/`else` is fixed in the same pass: every line moves out once per `else` it leaves.
When the `if` branch has no braces and [BRO1514](BRO1514.md)-[BRO1516](BRO1516.md) want braces on it (for example
BRO1516, because the `else` block makes the chain inconsistent), the fix adds them too, so the result is the same
whichever rule `dotnet format` runs first:
```csharp
if (x)
return 1;
else
{
return 2;
}
```
becomes
```csharp
if (x)
{
return 1;
}
return 2;
```
"Ends in a jump" is decided by syntax: the branch is a jump statement, a block whose last statement ends in one, or an
`if`/`else` whose branches both end in one (so an outer and an inner `else` go in the same run). Loops, `switch` and
`try` don't count.
## Not reported
- **`else if` chains** (`if (a) return 1; else if (b) return 2; else return 3;`): removing the first `else` would leave
a new candidate for a second run. Only an `if` whose `else` is not another `if`.
- **An `if` that isn't a statement of a block** (the branch of another `if`, a `case` section's statement list, a
label's statement), or that doesn't start its line.
- **An empty `else` block.**
- **Comments or directives** around the `else`: between the `if` branch and the first statement of the body, or
between the body's last statement and its `}`. And any `#if` in the whole statement.
- **A body that starts on the `else` line but goes on below it**, a body whose lines aren't indented one level deeper
than the `if`, and strings or `/* */` comments spanning lines (their inner lines can't be moved out).
- **`using` declarations** in the body (`using var x = ...;`): moved out, they would be disposed at the end of the
enclosing block instead of the `else` block.
- **Names declared in the body** (locals, local functions, pattern variables, lambda parameters, labels) that the
enclosing block also uses: they would clash there.
- **An `if` branch without braces that a brace rule wants braces on but can't add** (a comment or directive where the
`{` would go, a string spanning lines): see How the fix works.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` leaves it off.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1144/
# BRO1144: Escape identifiers that C# 14 reads as keywords
C# 14 reads `field`, `extension` and `partial` as keywords in places where they used to be plain names. Code that used
such a name there changes meaning or stops compiling when the project moves to C# 14 (.NET 10's default). BRO1144
writes `@` before the name in exactly those places; `@field` means the same in every C# version. Not a StyleCop rule
(Sonar S8367, S8368 and S8380 report these without a fix; the compiler's CS9258 warning has no fix either).
## Example
### Before
```csharp
public class Counter
{
private int field;
public int Value
{
get => field;
set => field = value;
}
public int Read() => this.field;
}
```
### After
```csharp
public class Counter
{
private int field;
public int Value
{
get => @field;
set => @field = value;
}
public int Read() => this.field;
}
```
## Why
In C# 14, `field` inside a property accessor is the property's own backing field. `get => field;` above stops reading
the class's field `field` and reads a new, hidden one instead: the code still compiles (warning CS9258), but `Value`
and `Read()` no longer see the same storage. With `@field` it keeps its meaning in every language version, so a
multi-targeted project (`net48;net10.0`, where each target framework compiles with its own C# version) behaves the same
everywhere.
## What is reported
These are the cases in Roslyn's list of C# 14 breaking changes, each checked with the C# 14 compiler:
| Before | After | In C# 14 without `@` |
|---|---|---|
| `field` in a property's accessor or expression body (also in a lambda or local function there) that names a member, local, parameter or range variable | `@field` | the backing field (CS9258), or error CS9272/CS9273 for a local or parameter declared there |
| a type, delegate, type parameter or `using` alias named `extension` | `@extension` | error CS9306 |
| a member of a type whose declaration starts with the type name `extension` (`extension M()`, `extension[] items;`, a constructor of a type named `extension`) | `@extension M()` | read as an extension block: syntax errors |
| a method or local function returning a type named `partial` (`partial F()`) | `@partial F()` | read as a partial member: errors |
In code that already compiles as C# 14, a `field` keyword is reported only when a member, local or parameter named
`field` of the property's type is in scope (the CS9258 case). Next to a member of another type (a `string` property in
a class with an `int` field named `field`) the keyword can only mean the backing field and stays.
## Not reported
- `this.field`, `x.field`, `x?.field`, named arguments (`M(field: 1)`) and anonymous object members
(`new { field = 1 }`): they stay names in C# 14.
- `field` in an indexer or event accessor, or anywhere outside a property: not a keyword there.
- `extension` used as a type anywhere else (a parameter, a local, `new extension()`), and `partial` as the type of a
field, property or parameter, or in an array return type (`partial[] F()`): C# 14 compiles these unchanged.
- `scoped` as a type name of a lambda parameter (`(scoped s) => ...`, Sonar S8381): a narrower case, not covered.
- Code with syntax errors around the name.
## Renaming rules and C# 14
When a member named `field` is also renamed ([BRO1303](BRO1303.md), [BRO1306](BRO1306.md), ...), the rename takes the
`field` keywords that mean the member along with it (`get => field;` becomes `get => _field;`), so the escape and the
rename give the same result in one `dotnet format` run, in either order (checked with a C# 14 project and the SDK 10
compiler, 2026-10-06).
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1145/
# BRO1145: A class, struct or interface with an empty body should end with ';'
A class, struct or interface with nothing in its body ends with `;`: `class Marker;` (C# 12). Records have their own
rule, [BRO1140](BRO1140.md). Not a StyleCop rule: from Meziantou's
[MA0206](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0206.md). **Off by default**, also in
the preset.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1145.severity = warning
```
### Before
```csharp
public class Marker
{
}
public interface ITag { }
public sealed class Point(int x, int y) : Marker, ITag { }
```
### After
```csharp
public class Marker;
public interface ITag;
public sealed class Point(int x, int y) : Marker, ITag;
```
## Why
The empty braces suggest a body that isn't there. It's a matter of taste, and the short form needs C# 12, which is why
the rule is off by default; turn it on with `dotnet_diagnostic.BRO1145.severity = warning`.
## How the fix works
The text from the end of the declaration's header (name, primary constructor, base types or constraints) to the `}`
becomes `;`. A `;` after the `}` (which [BRO1101](BRO1101.md) would remove) is part of the replaced text, and an empty
body that [BRO1509](BRO1509.md) expands first is replaced the same way, so the rules converge in any order.
## Not reported
- A file compiled as C# 11 or older (`class C;` doesn't compile there).
- A project with several target frameworks, unless every framework defaults to C# 12 or later (.NET 8 and later), or
the project sets `LangVersion` (then every framework uses it). Without `LangVersion`, `net48` and `netstandard2.0`
compile as C# 7.3, and `dotnet format` writes a fix from the newer framework's compilation into the file every
framework shares. The package's build targets pass `TargetFrameworks`, `LangVersion` and the SDK's default for the
framework (`MaxSupportedLangVersion`) to the analyzer: when they're equal, the project didn't set `LangVersion`. A
`LangVersion` that the project sets differently per framework (a condition on `TargetFramework`) isn't detected; set
the rule to `none` in such a project. Without the package (an analyzer reference), only the file's version counts.
- Records ([BRO1140](BRO1140.md)) and enums (`enum E;` doesn't exist).
- A comment or directive inside or around the braces.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` leaves it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1146/
# BRO1146: Write 'record' without 'class'
A record is a class unless it says `struct`, so `record class R` is written `record R`. A `record struct` keeps its
keyword. Not a StyleCop rule: from Meziantou's
[MA0174](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0174.md) (MA0175 is the opposite
style).
## Example
### Before
```csharp
public record class Point(int X, int Y);
public record struct Size(int Width, int Height);
```
### After
```csharp
public record Point(int X, int Y);
public record struct Size(int Width, int Height);
```
## Why
`record` alone has meant a reference type since C# 9; `record class` (C# 10) says the same with one more word. One form
for the same thing.
## How the fix works
The `class` keyword and the whitespace before it are removed.
## Not reported
- A comment between `record` and `class`.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1147/
# BRO1147: Remove a redundant null-forgiving operator
A null-forgiving `!` on a value the compiler already knows isn't null hides nothing: removing it changes no warning.
Not a StyleCop rule: from Sonar's [S8969](https://rules.sonarsource.com/csharp/RSPEC-8969/); the SDK's IDE0370 is the
same idea (see below). **Off by default**, also in the preset.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1147.severity = warning
```
### Before
```csharp
public static class Greeting
{
public static string For(string? name)
{
if (name is null)
{
return "Hello!";
}
return "Hello, " + name!.Trim() + "!";
}
}
```
### After
```csharp
public static class Greeting
{
public static string For(string? name)
{
if (name is null)
{
return "Hello!";
}
return "Hello, " + name.Trim() + "!";
}
}
```
## Why
A `!` says "I know better than the compiler". Where the compiler already agrees, it's noise, and worse: when the code
above changes so that the value can be null again, the stale `!` silences the warning that would have pointed at it.
## How the fix works
It removes the `!`. A `!` is reported when its operand's nullable flow state, without the `!`, is not-null: the compiler
analyzes the enclosing statement (or initializer, or expression body) again with the `!` removed, from the state the
original had there.
## Not reported
- **`null!` and `default!`** (`default(T)!` too): they say "this is set later" on purpose.
- **Operands whose type has type arguments** (`List`, `(string, int)`), **arrays and type parameters**: the `!` also
hides nested mismatches (`List` passed as `List`) and flow states that depend on the type argument,
which the operand's own state doesn't show. Type arguments that are plain value types are fine (`int?`, `(int, int)`).
- **`ref` and `out` arguments** (`N(ref s!)`): their declared type must match the parameter's, whatever the state.
- **Nullable analysis off, or `#nullable` changing the project's setting** where the `!` is (a `#nullable enable` file in
a project without `enable`, a `#nullable disable annotations` region).
- **Projects with several target frameworks.** Nullable annotations of the base library differ per framework: after
`string.IsNullOrEmpty(s)`, `s` is not null on .NET Core 3.0 and later but unknown on .NET Framework and
netstandard2.0, so the same `!` is redundant in one compilation and needed in the other, and `dotnet format` writes a
fix into the file every framework shares. The package's build targets pass the frameworks as
`build_property.StyleBroTargetFrameworks` (see [modernizing.md](../modernizing.md)); a project that references
StyleBro's DLLs directly, without the package, doesn't get the guard.
- **A comment or line break between the operand and the `!`.**
- Constructor initializers and attribute arguments (no statement around them to analyze again).
## Configuration
```ini
[*.cs]
# true: a '!' on a value declared without nullable annotations stays, like Sonar's S8969 (default false)
stylebro_allow_null_forgiving_on_unannotated = false
```
A property, field or method declared under `#nullable disable` or in a library without nullable annotations has no
annotation, and the compiler treats its value as not null without knowing it: `legacy.Name!` is redundant to the compiler,
but the `!` documents that it was checked. With `true` it stays, also on a `var` local initialized from such a value
(`var name = legacy.Name;` ... `name!`); explicitly typed locals and parameters are still checked, as in S8969.
`stylebro-migrate` writes it for a repository that enforces S8969.
## Compared with the SDK's IDE0370
IDE0370 (SDK 10) removes the `!`s too. It reports in a build and `dotnet format` fixes it, but only when its severity is
set explicitly (`dotnet_diagnostic.IDE0370.severity = warning`; it is `EnforceOnBuild.WhenExplicitlyEnabled`, which is
why a first probe saw nothing). It decides differently: it removes every `!` in a member, compiles the file again and
reports them all when the member has no new warning, or none of them. So it also removes a `!` on a maybe-null value
that goes where null is allowed (`N(t!)` with a `string?` parameter), keeps every `!` of a member where one is needed,
and needs SDK 10. In a multi-targeted project its fix leaves the `!` (it checks every framework's copy) but the build
keeps reporting it. BRO1147 decides per `!` from the flow state, runs with older SDKs and stays quiet in multi-targeted
projects. Turning both on is harmless: they remove the same `!`.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` leaves it off.
## Compared with Sonar's S8969
With `stylebro_allow_null_forgiving_on_unannotated = true` (what `stylebro-migrate` writes for S8969), BRO1147 removes no
`!` that S8969 accepts.
Sonar also reports a `!` that hides a nested nullability mismatch, which BRO1147 keeps (its type has type arguments):
Buildalyzer passes `GetEffectiveEnvironmentVariables(env)!`, a `Dictionary`, to a
`Dictionary` parameter; without the `!` the compiler warns (CS8620), so Sonar's S8969 there stays for a
person (that project also targets two frameworks, which BRO1147 skips anyway).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1148/
# BRO1148: Check a nullable value type for null instead of calling HasValue
`x.HasValue` becomes `x is not null` and `!x.HasValue` becomes `x is null`, or `x != null` and `x == null`, whichever
form [BRO1133](BRO1133.md) asks for. Not a StyleCop rule: from Meziantou's
[MA0171](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0171.md). **Off by default**, also in
the preset.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1148.severity = warning
```
### Before
```csharp
using System;
public class Booking
{
public DateTime? CheckedIn { get; set; }
public DateTime? CheckedOut { get; set; }
public bool IsActive => CheckedIn.HasValue && !CheckedOut.HasValue;
}
```
### After
```csharp
using System;
public class Booking
{
public DateTime? CheckedIn { get; set; }
public DateTime? CheckedOut { get; set; }
public bool IsActive => CheckedIn is not null && CheckedOut is null;
}
```
## Why
`HasValue` and a null check mean the same on a nullable value type (the compiler emits the same `HasValue` call for
both), so a codebase that writes null checks one way can write them that way for `int?` too, and a reader doesn't have
to know whether `x` is a reference or a nullable value. It's a matter of taste, though: many teams prefer `HasValue`,
which says "nullable value type" at a glance. That's why the rule is off by default.
## Configuration
The form comes from BRO1133's option, so the two rules never disagree:
```ini
[*.cs]
# pattern_matching (default): x is not null / x is null; equality_operator: x != null / x == null
stylebro_null_check_style = pattern_matching
```
## How the fix works
It replaces `.HasValue` with ` is not null` (and removes the `!` before a negated one), leaving the expression in front
as it is. The result is already BRO1133's preferred form, so `dotnet format` needs one run with both rules on.
## Not reported
- **Places where the null check would bind differently**: as an operand of an operator that binds as tightly as `==` or
tighter (`b == x.HasValue`, `!!x.HasValue`), or as the receiver of a member (`x.HasValue.ToString()`). The same
places BRO1133 leaves alone. While [BRO1405](BRO1405.md) is on, parentheses around `x.HasValue` don't count (it
removes them).
- **Patterns where they don't compile**: `is not null` before C# 9, `is null` before C# 7, and patterns
in expression trees (`Expression> f = n => n.HasValue;`; `n != null` is fine there).
- **`!= null` that would call a user-defined operator**: on `Guid?`, `DateTime?` or `DateTimeOffset?`, `x != null` names the
underlying type's operator, lifted (harmless at run time, but a banned-API list may forbid it; BRO1133 skips the
same).
- **Other `HasValue` properties** (only `System.Nullable.HasValue`), `nameof(x.HasValue)`, and comments, line breaks
or spaces around the `.` or after the `!`.
## Compared with StyleCop
StyleCop has no such rule (its issue [#1455](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1455) asked for
`HasValue` -> `!= null`; never implemented). `stylebro-migrate` leaves it off.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1149/
# BRO1149: Merge an `if` into the enclosing `if`
An `if` that is the only statement of another `if`, where neither has an `else`, joins it: `if (a) { if (b) { ... } }`
becomes `if (a && b) { ... }`. Not a StyleCop rule: Sonar's [S1066](https://rules.sonarsource.com/csharp/RSPEC-1066/)
reports the same pattern, and StyleBro also fixes it.
## Example
### Before
```csharp
public static class Orders
{
public static void Ship(Order order, bool expressAllowed)
{
if (order.IsPaid)
{
if (expressAllowed || order.Total > 100)
{
order.Express = true;
}
}
}
}
public sealed class Order
{
public bool IsPaid { get; set; }
public decimal Total { get; set; }
public bool Express { get; set; }
}
```
### After
```csharp
public static class Orders
{
public static void Ship(Order order, bool expressAllowed)
{
if (order.IsPaid && (expressAllowed || order.Total > 100))
{
order.Express = true;
}
}
}
public sealed class Order
{
public bool IsPaid { get; set; }
public decimal Total { get; set; }
public bool Express { get; set; }
}
```
## Why
Two nested conditions with nothing between them are one condition written twice as deep. Merged, the body loses a level
of indentation and the reader sees at once that both must hold.
## How the fix works
- The conditions are joined with `&&`, in order; one that binds looser than `&&` (`||`, `??`, `?:`, an assignment) gets
parentheses. `&&` evaluates left to right and stops early, like the nested `if`s, and a pattern variable from the first
condition is still definitely assigned in the second (`if (o is string s && s.Length > 0)`).
- The inner body is kept with its braces (or without them) and moves left by as much as the inner `if` was indented
deeper than the outer one; a condition spread over several lines moves the same way. The outer `if`'s `{`, `}` and
the inner `if (`...`)` go. Once the outer condition spans lines, the inner one starts a line of its own, aligned with
the outer condition, `&&` where `dotnet_style_operator_placement_when_wrapping` puts it (at the beginning by default)
when [BRO1520](BRO1520.md) enforces that key. When BRO1520 is off (always with CSharpier), it goes at the end of the
outer condition's line if the conditions already wrap after an operator there (`a &&` + `b`), and where the key says
otherwise; its own later lines keep their indentation relative to it.
- Deeper nests become one condition (`if (a && b && c)`), and nests inside the merged body are merged in the same fix,
so one `dotnet format` run is enough.
- When BRO1514-BRO1516 (braces) are on, the merged `if` gets the braces they want in the same edit: merging can move a
statement into an if/else chain (`else if (a)` + `if (b) X();`), where another brace rule reports it.
## Not reported
- An `else` on either `if`.
- An outer block with more than the inner `if` (a statement, a local, a comment).
- A variable declared in an inner condition (`out var x`, `is string s`) whose name is used elsewhere in the block around the
outer `if`: a variable declared in an `if` condition is in scope in the whole enclosing block, so moving it up would
collide with a local of the same name in a sibling block (CS0136).
- A comment or directive in the text that goes (`) {`, `if (`, the inner `)` and the closing `}`), or an `#if`,
`#elif`, `#else` or `#endif` anywhere in the outer `if` or right after it (an `else` inside `#if`): each target
framework's copy could see other code. A `#region` or `#pragma` line in the body or after the `if` doesn't keep it:
BRO1112/BRO1113 remove regions, and the merge would otherwise come only on the next run.
- A condition with a part of type `dynamic` (`d == 1`, `d.IsReady`) in any of the merged `if`s, with
`stylebro_allow_nested_dynamic_ifs = true` (see Configuration).
- A body that would move left but holds a string spanning lines (moving its lines would change the string), or a line
that isn't indented far enough to move.
## Configuration
```ini
[*.cs]
# true: nested ifs with a condition that has a part of type 'dynamic' aren't merged, like Sonar's S1066 (default false)
stylebro_allow_nested_dynamic_ifs = false
```
Sonar's S1066 skips an inner condition with a `dynamic` part; `stylebro-migrate` writes `true` for a repository that
enforces S1066. With it, any merged condition with a `dynamic` part (the inner or the outer one) keeps the nest as it is.
It is an option because finding out needs the compiler's types for the whole method, which costs time in every build.
## Compared with other analyzers
Sonar's S1066 reports the same pattern (on the inner `if`) and has no fix. The SDK has no such rule. With `stylebro_allow_nested_dynamic_ifs = true`,
BRO1149 reports no `if` S1066 accepts, so `stylebro-migrate` turns it on with that setting for a repository that enforces
S1066. S1066 reports a few more: an `if`
inside nested blocks (`if (a) { { if (b) ... } }`), a plain `dynamic` variable as the condition (`if (d)`; S1066 only
looks at the parts of a condition), and the inner pair of a nest whose middle condition is dynamic.
**Also fixes Sonar's S1066** when the SonarAnalyzer.CSharp package is in the project: the fix is registered for S1066 too (the inner `if` keyword, where Sonar reports it) and applies at the places this rule would fix, so `dotnet format` clears Sonar's warnings there even with this rule off. Where this rule skips the code (see Not reported), no fix is offered and Sonar's warning stays.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` leaves it off.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1150/
# BRO1150: Pass the predicate to the LINQ call instead of calling `Where` first
`source.Where(p).Count()` becomes `source.Count(p)`, and the same for `Any`, `LongCount`, `First`, `FirstOrDefault`,
`Last`, `LastOrDefault`, `Single` and `SingleOrDefault`. Not a StyleCop rule: Sonar's
[S2971](https://rules.sonarsource.com/csharp/RSPEC-2971/) reports this pattern (among other LINQ simplifications), and
StyleBro also fixes it.
## Example
### Before
```csharp
using System.Collections.Generic;
using System.Linq;
public static class Stock
{
public static bool AnyMissing(List counts) => counts.Where(c => c == 0).Any();
public static int Low(List counts) =>
counts
.Where(c => c < 5)
.Count();
}
```
### After
```csharp
using System.Collections.Generic;
using System.Linq;
public static class Stock
{
public static bool AnyMissing(List counts) => counts.Any(c => c == 0);
public static int Low(List counts) =>
counts
.Count(c => c < 5);
}
```
## Why
Every one of these methods takes the predicate itself. The `Where` call adds a step to read (and an iterator to
allocate) without saying anything the call after it can't.
## How the fix works
`Where` is renamed to the call after it, and that call (`.Count()`) goes; the predicate and the line breaks before it
stay, so a call chain split over lines keeps its layout. A `Where` inside the predicate is fixed in the same run.
## Lists and arrays when Sonar wants their own methods
Where Sonar's [S6605](https://rules.sonarsource.com/csharp/RSPEC-6605/) ("Exists" instead of "Any") or
[S6602](https://rules.sonarsource.com/csharp/RSPEC-6602/) ("Find" instead of "FirstOrDefault") is on, the fix writes
the collection's own method instead of the LINQ call Sonar would report next:
| Before | S6605/S6602 off | S6605/S6602 on |
| --- | --- | --- |
| `list.Where(p).Any()` | `list.Any(p)` | `list.Exists(p)` |
| `list.Where(p).FirstOrDefault()` | `list.FirstOrDefault(p)` | `list.Find(p)` |
| `array.Where(p).Any()` | `array.Any(p)` | `Array.Exists(array, p)` |
| `array.Where(p).FirstOrDefault()` | `array.FirstOrDefault(p)` | `Array.Find(array, p)` |
"On" means a severity set for the Sonar id (`dotnet_diagnostic.S6605.severity` in `.editorconfig`, a global config or a
ruleset), or, when none is set, the default of the SonarAnalyzer.CSharp package in the project: on before version 10
(9.x), off from 10 on. The package's version reaches StyleBro through its build targets (`build_property.StyleBroSonar`);
an analyzer referenced without them only sees configured severities. Where the collection's method wouldn't bind (a
`Func` variable isn't a `Predicate`, `Array` without `using System;`), the LINQ call is written as usual.
## Not reported
- Methods other than System.Linq's `Enumerable` and `Queryable` (a type's own `Where` or `Count`), and calls where the
version with the predicate would bind to something else (an instance method `Any(Func)` on the source, the
index overload `Where((x, i) => ...)`).
- The two calls from different classes (`Queryable.Where` then `Enumerable.Count`).
- A call with arguments (`FirstOrDefault(defaultValue)`), explicit type arguments, `?.`, a `ref` argument.
- Expression trees (`Expression>` lambdas, query clauses over `IQueryable`): a query provider would see
another tree.
- A comment or directive in the text that goes.
## Compared with other analyzers
Sonar's S2971 reports this (on `Where`) and more (`Select(x => x as T)` instead of `OfType()`, ...); it has no fix.
Roslynator's RCS1077 covers it too, with a fix.
**Also fixes Sonar's S2971** when the SonarAnalyzer.CSharp package is in the project: the fix is registered for S2971 too (the `Where` name, where Sonar reports it) and applies at the places this rule would fix, so `dotnet format` clears Sonar's warnings there even with this rule off. Where this rule skips the code (see Not reported), no fix is offered and Sonar's warning stays. Sonar's other S2971 cases (`OfType()`, the `Count` property, ...) aren't fixed.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` leaves it off.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1151/
# BRO1151: Pass the elements, not an array, to a `params` parameter
An array created in the call for a `params` parameter is noise: `Sum(1, new[] { 2, 3 })` becomes `Sum(1, 2, 3)`, and
the compiler creates the array. Not a StyleCop rule: Sonar's [S3878](https://rules.sonarsource.com/csharp/RSPEC-3878/)
reports the same pattern, and StyleBro also fixes it.
## Example
### Before
```csharp
public static class Paths
{
public static string Log(string root) => Join(new[] { root, "logs", "app.log" });
public static int Total() => Sum(1, new int[] { 2, 3 });
private static string Join(params string[] parts) => string.Join("/", parts);
private static int Sum(int first, params int[] rest) => first + rest.Length;
}
```
### After
```csharp
public static class Paths
{
public static string Log(string root) => Join(root, "logs", "app.log");
public static int Total() => Sum(1, 2, 3);
private static string Join(params string[] parts) => string.Join("/", parts);
private static int Sum(int first, params int[] rest) => first + rest.Length;
}
```
## Why
`params` exists so callers don't have to build the array. Writing it out anyway hides the arguments behind `new[] { }`.
## How the fix works
The text before the first element and after the last goes (`new[] { ` and ` }`, a trailing comma too); an empty array
goes with the comma before it (`Sum(1, new int[] { })` becomes `Sum(1)`). Arrays (`new T[] { }`, `new[] { }`) and
collection expressions (`[a, b]`) are fixed; an array in one of the elements is fixed in the same run.
## Not reported
- A call that would bind to another method with the elements: another overload (`Sum(int, int)`), other type
arguments (`M(params T[])` with `new object[] { "a" }` infers `string` from the elements), a `params
ReadOnlySpan` overload on newer frameworks.
- A `params` collection (`params ReadOnlySpan`, `params IEnumerable`, ...) in a project with a target framework
before .NET 9, which has none: the framework that has it reports the call, but the fix goes into the file every
framework compiles (AngleSharp: `SearchValues.Create(['<', '&'])` in a `net8.0;net10.0` project). Needs the
package's build targets, which pass the project's target frameworks to the analyzers.
- An array of another type than the parameter (`new string[] { }` for `params object[]`): the callee could rely on
the array's type.
- One element that converts to the array itself (`new object[] { null }`, `new object[] { otherArray }`,
`new Item[] { default! }`): the call would pass it as the array. The compiler decides (the call without the array
must bind each element as a `params` element), so parentheses, `!` or casts around the element don't hide it.
- An element whose meaning comes from the type it converts to: `new()`, `default`, `null`, lambdas, anonymous methods,
method groups, collection expressions, switch expressions, conditionals without a type of their own. Out of the
array it converts to whatever the call wants there: `M([new() { Name = "a" }])` would become `M(new() { ... })`,
a `new()` of the array type (CS8752).
- Named arguments, arrays with a size (`new int[2]`), spread elements (`[.. items]`), constructor initializers and
attribute arguments.
- An array spread over several lines, or one with two or more elements that doesn't start on the first argument's line
(`Sum(1,` + new line + `new[] { 2, 3 })`): the elements would share a line in a list that BRO1108 (SA1117) wants one
per line.
- A comment or directive in the text that goes (comments between the elements stay).
## Configuration
```ini
[*.cs]
# all (default): every params array
# except_object_and_jagged: not for 'params object[]', 'params Array[]' or a jagged 'params T[][]', like Sonar's S3878
stylebro_params_arrays = all
```
With `except_object_and_jagged`, `Log(new object[] { id, name })` stays: Sonar's S3878 accepts arrays for these parameters,
and `stylebro-migrate` writes the setting for a repository that enforces S3878.
## Compared with other analyzers
Sonar's S3878 reports the same pattern and has no fix.
**Also fixes Sonar's S3878** when the SonarAnalyzer.CSharp package is in the project: the fix is registered for S3878 too (the array, where Sonar reports it) and applies at the places this rule would fix, so `dotnet format` clears Sonar's warnings there even with this rule off. Where this rule skips the code (see Not reported), no fix is offered and Sonar's warning stays: for example a collection expression spread over several lines (`Create(path, null, [` + one element per line + `])`, Buildalyzer's tests). Sonar also reports an array of a derived type (`new string[] { ... }` for `params object[]`), which stays.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` leaves it off.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1301/
# BRO1301: Variable names should begin with a lower-case letter
Local variables and query range variables are camelCase: `total`, not `Total` or `_total`. Replaces StyleCop
**SA1312**.
## Example
### Before
```csharp
using System.Collections.Generic;
using System.Linq;
public class Basket
{
public decimal Total(IEnumerable prices)
{
decimal Sum = 0;
foreach (var Price in prices)
{
Sum += Price;
}
var _discounted = from P in prices where P > 100 select P * 0.9m;
return Sum - prices.Where(x => x > 100).Sum() + _discounted.Sum();
}
}
```
### After
```csharp
using System.Collections.Generic;
using System.Linq;
public class Basket
{
public decimal Total(IEnumerable prices)
{
decimal sum = 0;
foreach (var price in prices)
{
sum += price;
}
var discounted = from p in prices where p > 100 select p * 0.9m;
return sum - prices.Where(x => x > 100).Sum() + discounted.Sum();
}
}
```
## Why
C# names locals and parameters in camelCase and everything else in PascalCase, so the case tells you at a glance
whether a name is local to the method. `_name` is the convention for private fields, not locals.
## How the fix works
The variable and every use of it are renamed. The new name drops leading underscores and lowers the leading
capitals: `Total` becomes `total`, `_total` becomes `total`, `URL` becomes `url`, and `HTMLParser` becomes
`htmlParser` (the capital that starts the next word stays). Words separated by underscores are joined like
[BRO1308](BRO1308.md) joins a field's: `RANGE_REP_REG` becomes `rangeRepReg`, `Max_Value` becomes `maxValue`.
Checked: local variables (including `using`, `for`, `foreach`, `catch`, `out var`, pattern and deconstruction
variables) and query range variables (`from`, `let`, `join`, `into`). Local constants are PascalCase and aren't
reported.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
The fix only renames when the new name can't change what any code refers to. Checked by syntax over the member that
contains the variable, so it errs on the side of skipping:
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **The new name is already used in the member**, for anything: another variable, a field, a property of another
object (`var Value = value;` in a setter).
- **Two names in the member would get the same new name** (`Total` and `_total`).
- **The variable names an anonymous type member or tuple element by inference** (`new { Total }`, `(Total, 2)`). The
rename would rename the member or element too.
- **The member contains `#if`.** Code that isn't compiled for the current target can't be renamed reliably.
- **Names without a camelCase form:** only underscores (`_`, `___`), a digit after the underscores (`_1`), a
keyword (`Class` would become `class`), or `field` (`Field`): in C# 14 `field` is a keyword inside property
accessors, where a local named `field` doesn't compile (CS9273).
- **Names in an expression tree**: range variables of a query, and parameters of a lambda, that end up in a
`System.Linq.Expressions` tree (a query over `IQueryable`, an `Expression>` lambda, or a lambda nested in
one). The names are data at run time: they show in the expression's `ToString()` and query providers may use them
(Mapster's tests compare a query's text, and `from Param_0 in ...` -> `param0` failed one). StyleCop's SA1312/SA1313
report them.
- **`value` inside a property, indexer or event** (`var Value = 2;` in a setter): the accessor's implicit `value`
parameter already has that name.
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code, a name that keeps
IDE0042 (deconstruct variable declaration, a warning or an error in your settings) from reporting a tuple variable next to it. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
## Compared with StyleCop
Same results as StyleCop, except for the skipped cases above (names in expression trees among them). StyleCop's fix for SA1312 only lowers the first letter,
can't fix names that start with `_`, and under `dotnet format` applies only some of the renames per run.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1302/
# BRO1302: Parameter names should begin with a lower-case letter
Parameters are camelCase: `count`, not `Count` or `_count`. Replaces StyleCop **SA1313**.
## Example
### Before
```csharp
internal abstract class Shape
{
/// Scales the shape.
/// How much to scale by.
public abstract void Scale(double Factor);
}
internal class Circle : Shape
{
public double Radius { get; private set; } = 1;
public override void Scale(double Factor) => Radius *= Factor;
public static Circle Create() => new Circle().Resized(Factor: 2);
private Circle Resized(double Factor)
{
Scale(Factor);
return this;
}
}
```
### After
```csharp
internal abstract class Shape
{
/// Scales the shape.
/// How much to scale by.
public abstract void Scale(double factor);
}
internal class Circle : Shape
{
public double Radius { get; private set; } = 1;
public override void Scale(double factor) => Radius *= factor;
public static Circle Create() => new Circle().Resized(factor: 2);
private Circle Resized(double factor)
{
Scale(factor);
return this;
}
}
```
## Why
C# names locals and parameters in camelCase and everything else in PascalCase, so the case tells you at a glance
whether a name belongs to the method. For parameters it's also part of the API: callers see the names in
IntelliSense and use them in named arguments.
## Configuration
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The parameter and every use of it are renamed, anywhere in the solution: the body, `nameof`, named arguments
(`Resized(Factor: 2)`) and the ``/`` docs. The new name is built like for
[BRO1301](BRO1301.md): `Count` becomes `count`, `_count` becomes `count`, `URL` becomes `url`, `MAX_COUNT` becomes `maxCount`.
A parameter of an abstract, virtual or interface member takes the parameters of its overrides and implementations
along when they have the same name, so they stay in sync. Those parameters aren't reported themselves (their name
comes from the base, like in StyleCop); one that has a different name is reported on its own.
Checked: parameters of methods, constructors (including primary constructors of classes and structs), indexers,
operators, delegates, local functions, lambdas and anonymous methods.
**Renaming a public parameter is a source-breaking change** for callers outside the solution that use it as a named
argument. Code inside the solution is updated.
**The name can reach run time.** `nameof(Path)` becomes `nameof(path)`, and `ArgumentNullException.ThrowIfNull(Path)`
reads the name through `[CallerArgumentExpression]`, so an exception's `ParamName` changes from `"Path"` to `"path"`.
Like StyleCop's SA1313 fix, the rename goes ahead: the new name is what the parameter is called now, and a test that
compares `ParamName` with the old spelling needs the same update. (BRO1313 is different: it renames a parameter that
already has a consistent name only to match its base, so it keeps such names; see [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md).)
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
- **Public API** (unless `stylebro_rename_public_api = true`): Parameters of methods, constructors, indexers and delegates that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution (named arguments use parameter names). Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **Record parameters** (`record Person(string Name)`): they become properties, which are PascalCase.
- **Parameters that keep the name of the parameter they override or implement.**
- **Partial methods.** Both declarations have to be renamed together.
- **Constructor parameters named exactly like a property or field of their type** (`Point(int X) { this.X = X; }`).
Serializers bind constructor parameters to members by name, and some (CsvHelper) case-sensitively, so `X` -> `x`
would compile and then fail to load data. Found through CsvHelper's tests.
- **Names in an expression tree**: parameters of a lambda that end up in a `System.Linq.Expressions` tree (an
`Expression>` lambda, a query method on `IQueryable`, or a lambda nested in one). The names are data at run time: they show in the expression's `ToString()` and query providers may use them
(Mapster's tests compare a query's text). StyleCop's SA1313 reports them.
- The same unsafe cases as [BRO1301](BRO1301.md): the new name is already used in the member, two names would get
the same new name, the parameter names an anonymous type member or tuple element by inference, the member contains
`#if`, or the name has no camelCase form (`_`, `_1`, `Class`, `Field`), or would be `value` in an indexer.
When an override can't be renamed safely (its member already uses the new name), the base and the other overrides
are renamed and that override keeps its name.
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
## Compared with StyleCop
Same results as StyleCop, except for the skipped cases above; StyleCop reports `_` and `__` parameters. StyleCop's fix
for SA1313 only lowers the first letter, can't fix names that start with `_`, leaves overrides and implementations
behind (they are then reported), and under `dotnet format` applies only some of the renames per run.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1303/
# BRO1303: Private field names should be camelCase
Private fields are named `count`, or `_count` if you configure that style. Replaces StyleCop **SA1306** and
**SA1309** for private fields.
## Example
### Before
```csharp
public class Account
{
private decimal _balance;
private readonly string Owner;
public Account(string owner, decimal balance)
{
Owner = owner;
_balance = balance;
}
public string Describe() => Owner + ": " + _balance;
}
```
### After
```csharp
public class Account
{
private decimal balance;
private readonly string owner;
public Account(string owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
}
public string Describe() => owner + ": " + balance;
}
```
## Why
One naming style for private fields makes them recognizable at a glance. The default, `count`, is StyleCop's; many
.NET codebases (including the .NET runtime) use `_count` instead, which keeps fields apart from parameters without
`this.`. Pick one with `.editorconfig`.
## Configuration
```ini
[*.cs]
# 'camelCase' (default): count. '_camelCase': _count.
stylebro_private_field_naming = _camelCase
```
With `_camelCase`, `count`, `Count` and `__count` all become `_count`. The style covers private static fields that
aren't `readonly` too (`private static int _instances;`), so it is StyleCop's SX1309 and SX1309S together.
Those static fields can have a style of their own (StyleCop's SX1309S alone: `_` on static fields only):
```ini
[*.cs]
# Private static fields that aren't readonly or const. Not set: a naming rule for private fields that requires just
# 'static' decides (one with a style StyleBro doesn't write, like an 's_' prefix, leaves the names), else
# stylebro_private_field_naming's style.
stylebro_private_mutable_static_field_naming = _camelCase
```
`private int count;` stays, `private static int instances;` becomes `_instances`. Constants and `static readonly`
fields are [BRO1306](BRO1306.md)'s (`stylebro_private_static_field_naming`).
Without `stylebro_private_field_naming`, the SDK's naming rules decide: a `dotnet_naming_rule` whose symbols cover
private fields (kind `field` or `*`, accessibility `private` or `*`, no required modifiers) with a camel-case style picks
`camelCase` (no prefix) or `_camelCase` (`required_prefix = _`); the lowest `priority` wins. With neither, the default is
`camelCase`. The preset doesn't set the key, so a naming rule in your `.editorconfig` takes effect;
`stylebro-migrate` writes it from your StyleCop setup. `stylebro-migrate init` counts your private fields and writes
the key when no enforced naming rule decides: a rule nothing enforces (IDE1006 at `none` or `silent`) leaves the style to
your code. Naming rules for public, internal and protected fields count only when they are enforced (see
[BRO1306](BRO1306.md#configuration)).
```ini
[*.cs]
dotnet_naming_rule.private_fields.symbols = private_fields
dotnet_naming_rule.private_fields.style = underscore_camel
dotnet_naming_rule.private_fields.severity = warning
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.underscore_camel.required_prefix = _
dotnet_naming_style.underscore_camel.capitalization = camel_case
```
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The field is renamed everywhere, in every part of a partial type. Where a local or parameter with the new name would
hide the field, the reference is qualified: `this.balance = balance`, or `Account.instances` for a static field. The
other way round, a `this.` that only stepped around a parameter with the field's old name is dropped once the new name
no longer clashes (`this.pet = pet` -> `_pet = pet`), unless `dotnet_style_qualification_for_field = true`. A
field of a nested type is renamed even when an enclosing type has a member with the new name (the field hides it),
unless the nested type uses that member by name. The
new name drops leading underscores and lowers the leading capitals, like [BRO1301](BRO1301.md).
Checked: private fields, instance or static, readonly or not. Constants and `static readonly` fields are PascalCase
(StyleCop's SA1303 and SA1311), and internal and public fields are [BRO1306](BRO1306.md)'s.
Protected (and private protected) fields follow StyleCop's defaults: one that isn't readonly is camelCase (SA1306),
and a readonly one's casing isn't checked (StyleCop leaves it unchecked: SA1306 skips it and SA1304 defers to SA1307,
which checks public and internal fields only). A leading underscore (SA1309) is removed in the `camelCase` style and
kept in the `_camelCase` style, which doesn't add one to protected fields either.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
The fix only renames when that can't change what the code does:
- **Public API** (unless `stylebro_rename_public_api = true`): Protected fields that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **The type or a base type already has a member with the new name.**
- **Two fields would get the same new name** (`_count` and `Count`).
- **The field has attributes, or the type is `[Serializable]` or has a serializer attribute** (`[JsonObject]`,
`[DataContract]`, `[MessagePackObject]`, `[ProtoContract]`, ...). Serializers can depend on field names.
- **Another member's name contains the field's name**, like `ShouldSerialize_count` or `_countSpecified`: conventions
that pair members by name (Json.NET, XML serialization, data binding) would break.
- **The old name appears in a string in the type**, alone or inside a longer string: reflection (`GetField("_count")`),
`[DebuggerDisplay("{_count}")]`.
- **The old name appears in code excluded by `#if`**, which can't be renamed reliably.
- **The field names an anonymous type member or tuple element by inference** (`new { _count }`).
- **One-letter prefixes** (`m_count`, `s_count`): StyleCop's SA1308, and `s_` is the .NET runtime's style for
static fields.
- **Names without a camelCase form:** only underscores, or a keyword (`Class`).
- **Fields in a class whose name ends in `NativeMethods`** (or a type nested in one), like StyleCop: they hold Win32
and COM names.
- **Interop names**: SCREAMING_CASE constants and `static readonly` fields (`S_OK`), COM GUID fields (`IID_...`), and
the fields of interop-shaped types (`[StructLayout]`, `[ComImport]`, `[DllImport]` methods); see [BRO1308](BRO1308.md).
- **`_field` and `Field` in the camelCase style:** in C# 14 `field` inside a property accessor is the property's
backing field, so `get => field;` would stop reading this field (CS9258). `_field` is fine in the `_camelCase` style.
- **A part of the type is generated**, like a Razor component's code-behind class (`List.razor.cs`): the markup uses
the fields, and the generated half can't be renamed (eShopOnWeb's admin pages stopped compiling before).
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
**A field whose old name is a string where code reaches a member by name, anywhere in the solution,** keeps its name,
and the warning stays. That's reflection from another project, like a test reading a private field
(`GetField("_blockedUntil", ...)`): the rename would still compile but fail at run time. Counted: an argument of
`GetField`/`GetProperty`/`GetMember`/`GetMethod`/`GetEvent` and their `GetRuntime*`/`GetDeclared*` forms, `Type.GetType`,
`InvokeMember`, `Activator.CreateInstance`, any method whose name contains Field, Property, Member, Method or Event
(`Expression.Field`, `AccessTools.Field`, EF Core's `HasField`, a test's `GetPrivateField`), `[MemberData]`,
`[DynamicData]`, `[TestCaseSource]` and `[ValueSource]`, and any attribute's `Name = "..."`. Any other string (an
exception's parameter name, a message, a JSON key) doesn't keep it (owner's decision, docs/decisions.md: in Orleans
common words like `"options"` kept 692 renames). Rename such a field by hand
and update the string, or suppress the warning for it. (Found in Polly, whose tests read two private fields this
way.) Strings in source generators' output count too: Mapperly maps private fields through
`[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "intValue")]`, and a renamed field broke its mapping.
**A field that generated code uses** (a source generator's output, a `.g.cs` file) keeps its name too: the tool would
write the old name again.
## Compared with StyleCop
Same results as StyleCop for private and protected fields, except for the skipped cases above. SA1309 also covers
constants and public fields, which are [BRO1306](BRO1306.md)'s.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1304/
# BRO1304: Interface names should begin with I
Interfaces are named `IShape`, not `Shape`. Replaces StyleCop **SA1302**.
## Example
### Before
```csharp
internal interface Shape
{
double Area();
}
internal class Square : Shape
{
public double Side { get; set; }
public double Area() => Side * Side;
public static double Total(Shape[] shapes)
{
double total = 0;
foreach (Shape shape in shapes)
{
total += shape.Area();
}
return total;
}
}
```
### After
```csharp
internal interface IShape
{
double Area();
}
internal class Square : IShape
{
public double Side { get; set; }
public double Area() => Side * Side;
public static double Total(IShape[] shapes)
{
double total = 0;
foreach (IShape shape in shapes)
{
total += shape.Area();
}
return total;
}
}
```
## Why
The `I` prefix is the .NET convention: it tells you at a glance that a type is a contract, not a class you can
create.
## Configuration
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The interface is renamed everywhere in the solution: declarations (every part of a partial interface), base lists,
types of variables and parameters, `typeof`, `nameof` and `cref` references. The new name is `I` plus the name with its
first letter capitalized: `Shape` becomes `IShape`, `iShape` becomes `IShape`, `_Shape` becomes `IShape`.
Like StyleCop, only the first letter is checked, so `Item` is a fine interface name.
**The file isn't renamed.** `dotnet format` only changes file contents, so `Shape.cs` keeps its name; rename it by
hand if you keep file names in sync with type names. **Renaming a public interface is a breaking change** for code
outside the solution.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
- **The name appears in code excluded by `#if` anywhere in the project**: disabled code isn't compiled, so the rename can't follow it (and `stylebro-migrate format` formats one target framework at a time).
- **Public API** (unless `stylebro_rename_public_api = true`): Interfaces that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **The new name already means something:** a type with that name anywhere in the solution, or a member, type
parameter or imported type with that name where the interface is declared.
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
- **The interface's name appears in a string** anywhere in the solution, alone or qualified (`"App.Shape"`,
`"App.Shape, App"`): code can load types by name (`Type.GetType`, dependency injection, serializers), and the rename
would still compile but fail at run time.
- **A reference somewhere else would see a different type with the new name**, for example a file with
`using System.Collections;` that uses an interface `Enumerable` (it would become `IEnumerable`).
Rename these by hand, or suppress the warning.
## Compared with StyleCop
Same results as StyleCop. StyleCop's own fix changes nothing under `dotnet format`.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1305/
# BRO1305: Type parameter names should begin with T
Type parameters are named `T` or `TItem`, not `Item`. Replaces StyleCop **SA1314**.
## Example
### Before
```csharp
using System;
using System.Collections.Generic;
internal class Cache
{
private readonly Dictionary items = new Dictionary();
public Value Get(Key key, Func create)
{
if (!items.TryGetValue(key, out var value))
{
value = create(key);
items[key] = value;
}
return value;
}
}
```
### After
```csharp
using System;
using System.Collections.Generic;
internal class Cache
{
private readonly Dictionary items = new Dictionary();
public TValue Get(TKey key, Func create)
{
if (!items.TryGetValue(key, out var value))
{
value = create(key);
items[key] = value;
}
return value;
}
}
```
## Why
The `T` prefix is the .NET convention: in `Cache` it's clear at a glance which names are type
parameters and which are real types.
## Configuration
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The type parameter is renamed everywhere it's used, including `` docs. The new name is `T` plus the name
with its first letter capitalized: `Item` becomes `TItem`, `_x` becomes `TX`. A lower-case `t` that starts a word just
gets capitalized: `t` becomes `T`, `tKey` becomes `TKey`.
Like StyleCop, only the first letter is checked, so `Type` is a fine type parameter name.
A type parameter of an abstract, virtual or interface method takes the type parameters of its overrides and
implementations along when they have the same name. Those aren't reported themselves (their name comes from the
base).
Checked: type parameters of classes, structs, interfaces, records, delegates, methods and local functions.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
- **The name appears in code excluded by `#if` inside the method or type that declares it**: disabled code isn't compiled, so the rename would leave it behind and break the target frameworks that compile it (Bogus: `V` renamed to `TV`, an `#if STANDARD` branch kept `Action`).
- **Public API** (unless `stylebro_rename_public_api = true`): Type parameters of types and methods that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **The new name already means something:** a type with that name anywhere in the solution, or a member, type
parameter or imported type with that name in scope.
- **Partial methods.** Both declarations have to be renamed together.
- **Type parameters of a type with a generated part**, like a Razor component (`@typeparam`): its generated half
declares them too and can't be renamed.
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
## Compared with StyleCop
Same results as StyleCop. StyleCop's own fix changes nothing under `dotnet format`.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1306/
# BRO1306: Constant, static readonly and non-private field names should be PascalCase
Constants, `static readonly` fields and public or internal fields are named `MaxCount`, not `maxCount` or
`_maxCount`. Replaces StyleCop **SA1303**, **SA1311**, **SA1307** and **SA1304**.
## Example
### Before
```csharp
internal class Retry
{
public const int maxAttempts = 3;
private static readonly int[] delays = { 100, 500, 2000 };
public int attempts;
public int NextDelay() => attempts < maxAttempts ? delays[attempts++] : -1;
}
```
### After
```csharp
internal class Retry
{
public const int MaxAttempts = 3;
private static readonly int[] Delays = { 100, 500, 2000 };
public int Attempts;
public int NextDelay() => Attempts < MaxAttempts ? Delays[Attempts++] : -1;
}
```
## Why
Constants and `static readonly` fields act like fixed values of the type, and public fields are part of its surface
like properties, so they are named like properties. Private instance fields are camelCase instead
([BRO1303](BRO1303.md)).
## Configuration
Private constants and private `static readonly` fields can be camelCase instead (StyleCop issues
[#2641](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2641) and
[#3793](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3793)), when the SDK's naming rules ask for it, like
[BRO1303](BRO1303.md) follows them for private fields. The rule that counts is a `dotnet_naming_rule` whose symbols are
fields (kind `field` or `*`), private (accessibility `private` or `*`) and singled out by their modifiers: `const` (for
constants; `static` may be listed too), or `static` and/or `readonly` (for `static readonly` fields). Among those, the
lowest `priority` wins, then the rule with more required modifiers, then the name. If that rule's style is camel case
without a prefix, the fields are renamed to `maxAttempts`; with `required_prefix = _`, to `_maxAttempts`. A PascalCase
style and no such rule leave the default, PascalCase. Any other style (an `s_` prefix, all upper case) leaves the names
as they are: StyleBro doesn't write it and never renames against it. A naming rule without required modifiers (all
private fields) doesn't count: StyleCop and most teams name constants differently from fields.
```ini
[*.cs]
dotnet_naming_rule.private_constants.symbols = private_constants
dotnet_naming_rule.private_constants.style = camel
dotnet_naming_rule.private_constants.severity = warning
dotnet_naming_symbols.private_constants.applicable_kinds = field
dotnet_naming_symbols.private_constants.applicable_accessibilities = private
dotnet_naming_symbols.private_constants.required_modifiers = const
dotnet_naming_style.camel.capitalization = camel_case
```
Public, internal and protected fields follow the SDK's naming rules too: the first `dotnet_naming_rule` that covers the
field (by its accessibility and its `const`/`static`/`readonly` modifiers, in Roslyn's order: `priority`, then the more
specific modifiers, accessibilities and symbol kinds, then the name; a rule without a `severity` doesn't count, a rule
turned off leaves StyleCop's default) decides. A camel-case style renames them to camelCase ([BRO1303](BRO1303.md)), a
PascalCase style to PascalCase (BRO1306), any other style leaves them as they are. YamlDotNet's
`instance_fields_should_be_camel_case` (protected, internal, ... instance fields) keeps its `internal readonly
YamlAttributeOverrides overrides`. Without a rule, StyleCop's defaults apply: PascalCase for public and internal
fields, camelCase for protected ones. The same defaults apply when nothing enforces the naming rules: a rule counts
for these fields only when IDE1006's severity, or without one the rule's own, is `suggestion`, `warning` or `error`
(`dotnet_diagnostic.IDE1006.severity = none` turns them all off; a `silent` rule needs IDE1006 at `suggestion` or
higher). To have an unenforced rule followed, raise its `severity` to `suggestion`.
`stylebro_private_static_field_naming` sets the style of both directly and wins over the naming rules: `PascalCase`
(StyleCop's), `camelCase` or `_camelCase`. `stylebro-migrate --write` sets it to `PascalCase`, because StyleCop
enforced that whatever the naming rules said: a repository with a camel-case naming rule for static fields and IDE1006
turned off ("StyleCop handles these") keeps its `ProtocolVersion` fields.
```ini
[*.cs]
stylebro_private_static_field_naming = PascalCase
```
`stylebro_allow_leading_underscore_in_pascal_fields = true` leaves every field whose name starts with `_` alone
(`_minCount` stays): StyleCop's SA1303, SA1304, SA1307 and SA1311 only check that the first character isn't
lower-case, and the underscore is SA1309's. `stylebro-migrate --write` sets it to `true` when SA1309 is off, `false`
otherwise. Default `false`.
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The field is renamed everywhere in the solution. The new name drops leading underscores and capitalizes the first
letter: `maxAttempts` becomes `MaxAttempts`, `_minCount` becomes `MinCount`. Where a local or parameter with the new
name would hide the field, the reference is qualified (`Retry.MaxAttempts`, `this.Attempts`). A field may get the
name of its own type (`static readonly SimulatorEvent _simulatorEvent` becomes `SimulatorEvent SimulatorEvent`): C#
allows it, and `SimulatorEvent.Create()` still means the type. A member of an enclosing type with the new name doesn't
stop a nested type's field either, unless the nested type uses that member by name.
Checked: constants and `static readonly` fields of any accessibility, and public, internal and protected internal
fields. Protected fields are [BRO1303](BRO1303.md)'s, and enum members are left out (StyleCop's SA1300)
and one-letter prefixes like `s_count` (SA1308).
**Renaming a public field is a breaking change** for code outside the solution, and a serializer may write a public
field under its name (see below).
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **Fields in a class whose name ends in `NativeMethods`** (or a type nested in one), like StyleCop's SA1303, SA1304
and SA1307: they hold Win32 and COM names. A `static readonly` field starting lower-case is still reported, like
SA1311, which doesn't skip them.
- **Interop names**: SCREAMING_CASE constants and `static readonly` fields (`S_OK`), COM GUID fields (`IID_...`), and
the fields of interop-shaped types (`[StructLayout]`, `[ComImport]`, `[DllImport]` methods); see [BRO1308](BRO1308.md).
- **A non-private field whose name appears in code excluded by `#if` anywhere in the project** (private fields: in their type, see below): disabled code isn't compiled, so the rename can't follow it.
The same unsafe cases as [BRO1303](BRO1303.md): a member with the new name in the type or a base type, two fields that
would get the same name, attributes or a `[Serializable]` type, the old name in a string in the type (for public
instance fields, anywhere inside a string, like a JSON example), in `#if`-excluded code, or as an inferred anonymous
type member or tuple element name, and fields of a type with a generated part (a Razor component's code-behind class).
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
- **Public API** (unless `stylebro_rename_public_api = true`): Fields that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **The old name appears in a string elsewhere in the solution.** For a public or internal instance field any string,
also inside a longer one, like test JSON (`"{\"attempts\": 1}"`): serializers such as Json.NET write public fields
under their name, so the rename would change the output. For other fields only a string where code reaches a member
by name (`GetField("MaxAttempts")`, see [BRO1303](BRO1303.md)).
- **Some reference would see another member with the new name**, for example a property `MaxAttempts` in a derived
class, which would hide the renamed field there.
- **Generated code uses the field** (a source generator's output, a `.g.cs` file), or its name is a string there.
Rename these by hand, or suppress the warning.
## Compared with StyleCop
Same fields as StyleCop, with one diagnostic per field where StyleCop can report two (SA1307 with SA1311 or SA1304).
A constant or `static readonly` field that starts with `_` is renamed too (`_minCount` becomes `MinCount`); StyleCop
leaves that one to SA1309. With `stylebro_allow_leading_underscore_in_pascal_fields = true` (what migrating sets when
SA1309 is off) it isn't, like StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1307/
# BRO1307: Field names should not begin with a prefix
Fields aren't named `m_count`, `s_count` or `t_count`. Replaces StyleCop **SA1308**.
## Example
### Before
```csharp
public class Session
{
private static int s_opened;
private readonly string m_user;
public Session(string user)
{
m_user = user;
s_opened++;
}
public string Describe() => m_user + " (" + s_opened + " open)";
}
```
### After
```csharp
public class Session
{
private static int opened;
private readonly string user;
public Session(string user)
{
this.user = user;
opened++;
}
public string Describe() => user + " (" + opened + " open)";
}
```
## Why
Hungarian-style prefixes repeat what the declaration already says (member, static, thread-static) and make names
harder to read.
The .NET runtime's own style does use `s_` for static fields and `t_` for thread-static fields. If you follow that
style, turn this rule off:
```ini
[*.cs]
dotnet_diagnostic.BRO1307.severity = none
```
## Configuration
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The field is renamed everywhere, and the new name is the complete correct name in one step: the prefix goes, any
underscores inside the name go ([BRO1308](BRO1308.md)), and the casing follows the field's kind: camelCase for private
fields (`_camelCase` if `stylebro_private_field_naming` says so, see [BRO1303](BRO1303.md)), PascalCase for constants,
`static readonly` and non-private fields ([BRO1306](BRO1306.md)), camelCase for protected fields. So `m_with_more`
becomes `withMore`, and `m_Upper` becomes `upper`. References that a local or parameter would hide are qualified
(`this.user`).
Only the lower-case prefixes `m_`, `s_` and `t_` count, like in StyleCop; other `x_` names are [BRO1308](BRO1308.md)'s.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **Public API** (unless `stylebro_rename_public_api = true`): Fields that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **A prefix your naming rules ask for**: when an active `dotnet_naming_rule` for fields uses a style with
`required_prefix = s_` (the .NET runtime's convention for static fields), `s_` names are left alone.
- **Fields in a class whose name ends in `NativeMethods`** (or a type nested in one), like StyleCop: they hold Win32
and COM names.
- **Interop names**: SCREAMING_CASE constants and `static readonly` fields (`S_OK`), COM GUID fields (`IID_...`), and
the fields of interop-shaped types (`[StructLayout]`, `[ComImport]`, `[DllImport]` methods); see [BRO1308](BRO1308.md).
- **Names that would be empty, a keyword or `field`** (`m_`, `s_static`, `m_field`; see [BRO1303](BRO1303.md)).
- The same unsafe cases as [BRO1303](BRO1303.md): a member with the new name, two fields getting the same name,
attributes (a `t_` field usually has `[ThreadStatic]`, so it's left alone) or a `[Serializable]` type, the name in a
string, in `#if`-excluded code, or as an inferred anonymous type member or tuple element name.
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
## Compared with StyleCop
Same fields, except the skipped cases above (StyleCop turns `s_static` into `@static`). StyleCop's fix only removes
the prefix (`m_Upper` becomes `Upper`, which then breaks SA1306), and `m_with_more` needs a second run for SA1310.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1308/
# BRO1308: Field names should not contain an underscore
Fields are named `maxValue` or `MaxValue`, not `max_value` or `Max_Value`. Replaces StyleCop **SA1310**.
## Example
### Before
```csharp
internal class Throttle
{
public const int Max_Requests = 100;
private int request_count;
public bool TryEnter()
{
if (request_count >= Max_Requests)
{
return false;
}
request_count++;
return true;
}
}
```
### After
```csharp
internal class Throttle
{
public const int MaxRequests = 100;
private int requestCount;
public bool TryEnter()
{
if (requestCount >= MaxRequests)
{
return false;
}
requestCount++;
return true;
}
}
```
## Why
C# separates words by capital letters, not underscores. `Max_Requests` and `request_count` are habits from C and
other languages.
## Configuration
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The field is renamed everywhere. The words between the underscores are joined, and the casing follows the field's
kind: PascalCase for constants, `static readonly` and non-private fields ([BRO1306](BRO1306.md)), camelCase for private
fields (or `_camelCase`, see [BRO1303](BRO1303.md)) and protected fields. A word in all capitals counts as a word, not
an acronym: `Max_HTTP_Requests` becomes `MaxHttpRequests`, `request_count` becomes `requestCount`, `trailing_` becomes
`trailing`.
A leading underscore isn't this rule's business (it's [BRO1303](BRO1303.md)'s or [BRO1306](BRO1306.md)'s), and a
`m_`/`s_`/`t_` prefix is [BRO1307](BRO1307.md)'s, which also removes any underscores after it.
A name starting with `_` is only joined where the underscore goes too (the `camelCase` style: `_signature_size` becomes
`signatureSize` in one rename). In the `_camelCase` style it's left alone, like StyleCop's SA1310, which doesn't check a
name starting with `_`: `_signature_size` stays (SSH.NET, StyleCop-clean, kept such names).
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **Public API** (unless `stylebro_rename_public_api = true`): Fields that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **Names that would become a keyword.**
- **Fields in a class whose name ends in `NativeMethods`** (or a type nested in one), like StyleCop: they hold Win32
and COM names.
- **Interop names** (owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)):
a SCREAMING_CASE constant or `static readonly` field (`S_OK`, `MAX_PATH`), a COM GUID field (`IID_IContextMenu`,
`CLSID_ShellLink`), and every field of a type that looks like interop, or of a type nested in one: `[StructLayout]`,
`[ComImport]`, `[UnmanagedFunctionPointer]`, or `[DllImport]`/`[LibraryImport]` methods. The same goes for
[BRO1303](BRO1303.md), [BRO1306](BRO1306.md), [BRO1307](BRO1307.md) and [BRO1310](BRO1310.md).
- The same unsafe cases as [BRO1303](BRO1303.md): a member with the new name, two fields getting the same name,
attributes or a `[Serializable]` type, the name in a string (for public instance fields, anywhere inside a string,
since serializers write them by name), in `#if`-excluded code, or as an inferred anonymous type member or tuple
element name.
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
## Compared with StyleCop
Same fields as StyleCop, minus the interop names above (StyleCop reports `MAX_PATH`). StyleCop's fix only deletes the
underscores: `MAX_VALUE` becomes `MAXVALUE`.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1309/
# BRO1309: Element names should begin with an upper-case letter
Types, methods, properties, events, enum members and local functions are PascalCase: `GetTotal`, not `getTotal`.
Replaces StyleCop **SA1300** (except for namespaces).
## Example
### Before
```csharp
internal enum status
{
open,
closed,
}
internal class order
{
public order(status initial)
{
state = initial;
}
public status state { get; private set; }
public void close()
{
if (isOpen())
{
state = status.closed;
}
}
private bool isOpen() => state == status.open;
}
```
### After
```csharp
internal enum Status
{
Open,
Closed,
}
internal class Order
{
public Order(Status initial)
{
State = initial;
}
public Status State { get; private set; }
public void Close()
{
if (IsOpen())
{
State = Status.Closed;
}
}
private bool IsOpen() => State == Status.Open;
}
```
## Why
PascalCase for types and members is the .NET convention: everything in the base class library and in almost every
library you use is named that way, so code that follows it reads the same as the APIs it calls.
## Configuration
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The name is renamed everywhere in the solution, with its first letter capitalized (and leading underscores dropped):
`close` becomes `Close`, `_helper` becomes `Helper`. For a type, its constructors and finalizer are renamed too.
A member of an interface or an abstract or virtual member takes its implementations and overrides along
(explicit implementations included); without them the code wouldn't compile. Those aren't reported themselves (their
name comes from the base, like in StyleCop).
Checked: classes, structs, records, enums, delegates, methods, properties, events, enum members and local functions.
Interfaces are [BRO1304](BRO1304.md)'s, fields [BRO1303](BRO1303.md) and [BRO1306](BRO1306.md)-[BRO1308](BRO1308.md)'s.
**Renaming public types and members is a breaking change** for code outside the solution, and **files aren't
renamed** (`dotnet format` only changes file contents). Names used from non-C# files (XAML, Razor, configuration)
aren't seen by the fix.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
- **The name appears in code excluded by `#if` anywhere in the project** (a local function: `#if` in its member): disabled code isn't compiled, so the rename can't follow it.
- **Public API** (unless `stylebro_rename_public_api = true`): Types, methods, properties, events and enum members that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **Namespaces.** Renaming a namespace also changes embedded resource names and breaks folder conventions.
- **snake_case names** (an underscore after the first letter: `device_id`, `load_all`): usually a wire name (JSON, a
protocol), and `Device_id` wouldn't be PascalCase either. A leading underscore alone is fine (`_helper` -> `Helper`).
- **External methods** (`extern`, `[DllImport]`, `[LibraryImport]`): a P/Invoke method's name is the native entry
point.
- **Properties and enum members with attributes**, usually serialization settings, and **properties of types with a
serializer attribute** (`[JsonObject]`, `[DataContract]`, ...).
- **Members whose name is part of another member's name**, like `items` next to `ShouldSerializeitems()` or
`itemsSpecified`: Json.NET and XML serialization pair them by name.
- **Partial methods** (both declarations have to change together), indexers and operators.
- **Types with a generated part and their members**, like a Razor component's code-behind class (`List.razor.cs`): the
markup uses those names and its generated half can't be renamed (found in eShopOnWeb).
- **Members that keep the name of what they override or implement**, including members that implement an interface
from a library.
- **The new name already means something** where the element is declared: another member of the type or a base type
(`run()` next to `Run(int)`), or a type or namespace in scope; for types, a type with that name anywhere in the
solution. A local function is checked like a local variable ([BRO1301](BRO1301.md)).
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
- **The name appears in a string** anywhere in the solution. For instance properties and events, and enum members, that
includes appearing inside a longer string, like test JSON: serializers write them by name. Static properties and
events aren't written by serializers: only a string where code reaches a member by name counts for them
(`GetProperty("nbc")`, a member-source attribute), not a URL or file name that contains the word (AngleSharp's test
assets). For types, a qualified name counts too (`"App.order"`).
- **Another property of the type keeps its name because of a string.** Then all of the type's properties do: renaming
only some would mix casings and change only part of what a serializer reads and writes (a JSON DTO with `document`
in test JSON keeps `percentage` too).
- **A derived type already has a member with the new name**, or some reference would see something else under it.
- **Generated code uses the name**: a Razor page, or a source generator's output (also a name in one of its strings).
Generated code is written again by its tool, so the rename can't change it.
Rename these by hand, or suppress the warning.
## Compared with StyleCop
Same results as StyleCop, except namespaces and snake_case names (StyleCop reports `device_id`; its fix would
write `Device_id`). StyleCop's own fix changes nothing under `dotnet format`.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1310/
# BRO1310: Field names should not use Hungarian notation
Variable, parameter and field names don't start with a type prefix: `count`, not `iCount`. Replaces StyleCop
**SA1305**. Off by default, like StyleCop's.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1310.severity = warning
```
### Before
```csharp
internal class Order
{
private int nItems;
public string Describe(string szCustomer, bool isPaid)
{
var strTotal = nItems.ToString();
return szCustomer + ": " + strTotal + (isPaid ? " (paid)" : string.Empty);
}
}
```
### After
```csharp
internal class Order
{
private int items;
public string Describe(string customer, bool isPaid)
{
var strTotal = items.ToString();
return customer + ": " + strTotal + (isPaid ? " (paid)" : string.Empty);
}
}
```
## Why
A prefix repeats the type, which the compiler and the editor already know, and goes stale when the type changes.
## Configuration
```ini
# Prefixes that are fine (StyleCop's allowedHungarianPrefixes).
stylebro_allowed_hungarian_prefixes = db, ui
# StyleCop's list of common words that look like prefixes (as, at, by, do, go, if, in, is, it, no, of, on, or, to)
# is allowed unless this is false (allowCommonHungarianPrefixes).
stylebro_allow_common_hungarian_prefixes = true
```
`stylebro-migrate` writes both from `stylecop.json`.
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
A prefix is what StyleCop counts as one: one or two lower-case letters followed by an upper-case letter (`iCount`,
`szName`; `strName` has three letters and isn't one). The fix removes it and lowers the rest like
[BRO1301](BRO1301.md): `pURL` becomes `url`. It's the same rename as the other naming rules, with the same safety
checks, so `_iCount` gets one rename to `count` (or `_count` with `stylebro_private_field_naming = _camelCase`),
reported as BRO1310.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
- **Public API** (unless `stylebro_rename_public_api = true`): Parameters and protected fields that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution (named arguments use parameter names). Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- **Classes whose name ends in `NativeMethods`**, like StyleCop: Win32 names (`lpBuffer`, `dwFlags`) stay.
- **Interop names**: SCREAMING_CASE constants and `static readonly` fields (`S_OK`), COM GUID fields (`IID_...`), and
the fields of interop-shaped types (`[StructLayout]`, `[ComImport]`, `[DllImport]` methods); see [BRO1308](BRO1308.md).
- **Parameters of `extern`, `[DllImport]` and `[LibraryImport]` methods and local functions**: they keep the native
API's names (`hWnd`, `dwFlags`), like [BRO1309](BRO1309.md) leaves the methods' names alone.
- **Parameters that keep a base method's or interface's name, record parameters, partial methods**, like
[BRO1302](BRO1302.md).
- **Constants, `static readonly`, public and internal fields:** they are PascalCase ([BRO1306](BRO1306.md)), so
`xPos` becomes `XPos` there and no longer has a prefix.
- **Another name in the member would get the same new name:** `xItem` and `yItem` in one query, or a parameter
`nSize` in a method named `Size`. Renaming one would collide with the other, or take two runs.
- **Any case the other naming rules skip** (the new name is taken, the name appears in a string, ...).
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
## Compared with StyleCop
StyleCop has no fix for SA1305. It reports constant, static readonly and public fields too; StyleBro reports those
as [BRO1306](BRO1306.md) and renames them to PascalCase, which also removes the prefix's lower-case form. StyleCop
doesn't look past a leading underscore (`_iCount`), but once SA1309 removes it SA1305 reports `iCount`; StyleBro
reports `_iCount` right away and renames it once. StyleCop reports the parameters of P/Invoke methods outside a
`NativeMethods` class ([#2859](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2859), open); StyleBro doesn't.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1311/
# BRO1311: Tuple element names should use correct casing
Element names in tuple types are PascalCase: `(int Count, string Name)`, not `(int count, string name)`. Replaces
StyleCop **SA1316**.
## Example
### Before
```csharp
internal class Inventory
{
public (int count, string name) Largest() => (count: 3, name: "bolts");
public string Describe()
{
var item = Largest();
return item.name + ": " + item.count;
}
}
```
### After
```csharp
internal class Inventory
{
public (int Count, string Name) Largest() => (Count: 3, Name: "bolts");
public string Describe()
{
var item = Largest();
return item.Name + ": " + item.Count;
}
}
```
## Why
Tuple elements are public fields of the tuple, so they read like members: `item.Name`, as for a property. Consistent
casing also keeps a tuple-returning method and a record or class returning the same data interchangeable.
## Configuration
```ini
[*.cs]
# PascalCase (default) or camelCase, like StyleCop's tupleElementNameCasing.
stylebro_tuple_element_name_casing = camelCase
```
`stylebro-migrate` writes it from `stylecop.json`.
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
Tuple element names belong to the tuple type where they're written, and a use like `item.count` binds to the element
of whichever tuple type `item` has. So the fix renames the name everywhere at once, in the whole solution:
- every tuple type element with that name (a method's return type, a field, a local's type, a type argument);
- every name in a tuple literal converted to one of them (`(count: 3, ...)` returned from the method above), so the
compiler doesn't warn that the literal's name is ignored (CS8123);
- every use of those elements: `item.count`, `item?.count`, `nameof(item.count)`, `item is (count: 1, _)`;
- overrides and implementations together with what they override: their names have to match (CS8139).
A tuple or anonymous object that takes its element name from a renamed use (`(item.count, 1)`, `new { item.count }`)
gets the old name written out (`(count: item.Count, 1)`), so it and its own uses keep working unchanged; not when the
literal is converted to a tuple type (`return (true, item.Count);` from a method returning `(bool Found, int Total)`),
whose names win anyway.
The names of one tuple type are renamed all or none: when one of them has to keep its name (see below), the others in
the same tuple types keep theirs too, rather than leaving `(int Total, int max)`. `stylebro-migrate format` lists the
element that keeps its name with its reason, and each of the others as kept with it.
Deconstruction (`var (count, name) = item;`) declares variables, not element names, and isn't touched.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **The name appears in code excluded by `#if` anywhere in the project**: disabled code isn't compiled, so the rename can't follow it.
- **Public API** (unless `stylebro_rename_public_api = true`): Tuple element names in the types of a member's signature (return, parameter, property, field or event types) that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **`_`** and names that don't start with a letter.
- **Names a member has to take from a library member it overrides or implements**, like StyleCop: the library can't be
renamed. An override or implementation of a member in the solution is reported, and renamed together with it.
- **Names in tuple literals and names a literal infers** (`(count, name)` with variables of those names). StyleCop
checks inferred names only with `includeInferredTupleElementNames`, off by default; StyleBro doesn't check them.
- **A new name that would be a keyword** (`Var` in camelCase).
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
A rename that would add compile errors (or CS8123 warnings) anywhere in the solution is skipped, and the warning stays:
for example a tuple that already has the new name (`(int count, int Count)`), or an override of a library member whose
own signature names the elements. When such an element is in another project, each project's elements with that name
are judged on their own: one project's tuple that can't be renamed doesn't keep an unrelated one elsewhere. So is a rename that would let IDE0042 (deconstruct variable declaration) report a
variable where your settings make IDE0042 a warning or an error: IDE0042 leaves `var result = Get();` alone while an
element shares its name with something in scope (a local `pointer` next to `result.pointer`), and the new name would end
that.
## Compared with StyleCop
Same reports as StyleCop 1.2.0-beta.556 (checked with the parity set `tuple-element-casing`). StyleCop's `master`
(unreleased, d1b6183d) no longer reports names in the signature of an override or interface implementation; StyleBro
still does and renames them together with the base (master's fix renames only the base, which then fails with
CS8139 on the override). StyleCop's fix renames only the declaration, so every use (`item.count`) and every override breaks the
build; StyleBro renames them together.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1312/
# BRO1312: Namespace names should begin with an upper-case letter
Every part of a namespace name is PascalCase: `MyCompany.Billing`, not `myCompany.billing`. Replaces StyleCop
**SA1300** for namespaces ([BRO1309](BRO1309.md) covers the other elements). **Off by default**: renaming a namespace
renames every type in it.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1312.severity = warning
```
### Before
```csharp
namespace myCompany.billing
{
internal class Invoice
{
}
}
namespace myCompany.web
{
using myCompany.billing;
internal class InvoicePage
{
public Invoice Current { get; } = new Invoice();
}
}
```
### After
```csharp
namespace MyCompany.Billing
{
internal class Invoice
{
}
}
namespace MyCompany.Web
{
using MyCompany.Billing;
internal class InvoicePage
{
public Invoice Current { get; } = new Invoice();
}
}
```
## Why
Namespaces are PascalCase everywhere in .NET, and a lower-case part stands out in every `using` directive.
## How the fix works
Like StyleCop, each part of every namespace declaration is reported. The fix renames the part everywhere in the
solution at once: every declaration (block and file-scoped), `using` directives of every kind (`global using`,
`using static`, aliases), qualified names (`global::` included) and crefs. It's all or nothing: when one place can't be
renamed safely, nothing is.
**Renaming a namespace changes the full name of every type in it.** That's a breaking change for code outside the
solution, and for anything that stored a type name: `Type.GetType("myCompany.billing.Invoice")`, Json.NET's `$type`,
configuration files that name types. The fix leaves the namespace alone when a string in the solution or an
additional file (Razor, XAML) contains its name, but it can't see stored data or other repositories. Files and folders
aren't renamed (`dotnet format` only changes file contents).
## Configuration
```ini
[*.cs]
# Parts that keep their casing, like StyleCop's allowedNamespaceComponents.
stylebro_allowed_namespace_components = eBay, iOS
```
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
- **Public API** (unless `stylebro_rename_public_api = true`): Namespaces that hold such a type (directly or in a namespace inside them) that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **Allowed components** (`stylebro_allowed_namespace_components`): brand names like `iText`, `iOS`, `eBay`.
- **The project's root namespace or a part of it** (`RootNamespace`, by default the project name). Embedded resource
names start with the root namespace, not with the declared one, so `GetManifestResourceStream(typeof(T).Namespace +
".data.json")` would stop finding its resource, and new files and regenerated designer files would get the old
casing again. Rename the project (or set `RootNamespace`) first; the declarations then follow on the next run.
- **A namespace another assembly also declares**, inside it included (a package's `myCompany.common` next to your
`myCompany.data`): the other assembly keeps its name. A namespace that two projects of the solution share is reported
in the project that doesn't see the other's part, and the fix renames both.
- **A namespace declared in generated code** (`.designer.cs`, `.g.cs`, ``): the generator writes it
again.
- **The new name already exists** in the containing namespace (`MyCompany` next to `myCompany`, or a type named
`Billing`).
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
- **The full name appears in a string** anywhere in the solution, or in an additional file.
- **The part appears in disabled `#if` code.**
- **A reference is in generated code**, like a Razor component's `@using`.
- **Another project gets part of the namespace from a library** outside the solution.
- **The new name already means something**: in another project's containing namespace, where a reference looks the
part up by its simple name, or for code inside the containing namespace that uses the new name for a type or alias
(it would find the renamed namespace first).
Rename these by hand, or suppress the warning.
## Compared with StyleCop
Same positions as StyleCop, minus the cases under "Not reported" (StyleCop has no such skips; its
`allowedNamespaceComponents` is `stylebro_allowed_namespace_components`, and `stylebro-migrate` carries it over).
StyleCop's fix renames only the one declaration it's on, which leaves every other file's
declarations and `using` directives behind (the code doesn't compile until all are renamed).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1313/
# BRO1313: Parameter names should match the base member
A parameter of an override or an interface implementation has the name of the parameter it overrides or implements.
Proposed for StyleCop as SA1315 ([#1949](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1949), never
shipped); the SDK's CA1725 reports the same thing but has no fix `dotnet format` can apply. Off by default.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1313.severity = warning
```
### Before
```csharp
internal interface IStore
{
void Save(int id, string name);
}
internal class Store : IStore
{
public void Save(int key, string name) => System.Console.WriteLine(key + name);
public override bool Equals(object other) => other is Store;
public override int GetHashCode() => 0;
}
internal static class Caller
{
public static void Run(Store store) => store.Save(key: 1, name: "a");
}
```
### After
```csharp
internal interface IStore
{
void Save(int id, string name);
}
internal class Store : IStore
{
public void Save(int id, string name) => System.Console.WriteLine(id + name);
public override bool Equals(object obj) => obj is Store;
public override int GetHashCode() => 0;
}
internal static class Caller
{
public static void Run(Store store) => store.Save(id: 1, name: "a");
}
```
## Why
A caller using named arguments sees different names depending on the type it calls through (`store.Save(key: 1)` but
`((IStore)store).Save(id: 1)`), and the documentation of the base member describes parameters the override doesn't
have.
## Configuration
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
`stylebro_match_base_parameter_names = non_generic_methods` limits the rule to what Sonar's S927 reports (stylebro-migrate
writes it for a repository that enforces S927): methods only (not indexers), and not a parameter whose type in the base is a
type parameter the member fills in, or a generic type whose type arguments differ in kind. MediatR's
`Handle(TNotification notification, ...)` implemented as `Handle(OrderPlaced @event, ...)` keeps `@event`, and
`All(IEnumerable items)` implemented as `All(IEnumerable orders)` keeps `orders`. Default `all`.
## How the fix works
The parameter is renamed like [BRO1302](BRO1302.md)'s renames: the declaration, every use in the method, named
arguments anywhere in the solution and ``/`` tags. The rename continues to overrides of the method
that had the same old name. Bases in libraries count too (`Equals(object obj)`): matching them is the point.
A base parameter in your code counts with the name it is about to get: when the base's own parameter is renamed by
BRO1302 (`Count` -> `count`) or matches its own base, the override takes that name in the same run, so a chain of
overrides converges in one `dotnet format` run. With BRO1313 on, it decides the name of such a parameter instead of
BRO1302 and [BRO1310](BRO1310.md) (one rename per parameter).
**Named arguments outside the solution break**: a caller in another repository using `key:` no longer compiles. That
is why the rule is off by default.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
- **Public API** (unless `stylebro_rename_public_api = true`): Parameters that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution (named arguments use parameter names). Internal code is renamed, also with `InternalsVisibleTo`.
- Discards (`_`, `__`): an unused parameter named on purpose.
- Bases that disagree: a method implementing two interfaces whose parameters have different names.
- A name that would clash in the method (a local or another parameter already has it), like BRO1302.
- A name that reaches run time: `nameof(key)` in the method, or the parameter passed to a `[CallerArgumentExpression]`
parameter (`ArgumentNullException.ThrowIfNull(key)`). Both usually become an exception's `ParamName`, which callers
and tests compare (Newtonsoft.Json's `JObject.ContainsKey(null)` test expects `propertyName`). An override renamed
along with its base keeps its name for the same reason.
- Partial methods, constructors, operators, local functions, lambdas and record parameters (none of them override).
- A base name that is a C# keyword, or `field` (a keyword inside C# 14 property accessors).
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
## Compared with StyleCop
StyleCop never shipped the rule. Its name checks (SA1313) skip a parameter that has its base's name, and so does
BRO1302.
## Real world (2026-10-06)
Run with every other rule and the repositories' tests (`Invoke-RealWorld.ps1 -Enable BRO1313 -Tests`): Newtonsoft.Json 13
renames (`JObject`'s `IDictionary` members, converters, `Equals(object obj)`), FFMpegCore 6, CsvHelper 3, Polly 2,
Serilog 2, FluentValidation 1, Jellyfin 1. All compile, converge in one run, and no test that passed before fails. The
first run renamed `JObject.ContainsKey(propertyName)` to `key`, which changed its `ArgumentNullException`'s ParamName
and failed a test; such names are now kept (see Not reported).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1314/
# BRO1314: Asynchronous method names should end with Async
A method that returns `Task`, `Task`, `ValueTask`, `ValueTask` or `IAsyncEnumerable` has a name ending in
`Async`, as in .NET's own APIs. Like Roslynator's RCS1046 and Meziantou's MA0137. Off by default.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1314.severity = warning
```
### Before
```csharp
using System.Threading.Tasks;
internal interface IStore
{
Task Save(int id);
}
internal class Store : IStore
{
public Task Save(int id) => Task.CompletedTask;
public async Task Copy(int id)
{
await Save(id);
return id;
}
}
```
### After
```csharp
using System.Threading.Tasks;
internal interface IStore
{
Task SaveAsync(int id);
}
internal class Store : IStore
{
public Task SaveAsync(int id) => Task.CompletedTask;
public async Task CopyAsync(int id)
{
await SaveAsync(id);
return id;
}
}
```
## Why
The suffix tells a caller the method has to be awaited, and keeps room for a synchronous `Save` next to `SaveAsync`.
## Configuration
`stylebro_rename_public_api = true` also renames names other assemblies see (default `false`, see Not reported; the owner's decision in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## How the fix works
The method is renamed everywhere in the solution, with its overrides and implementations (explicit ones too), like
[BRO1309](BRO1309.md)'s renames. A method whose name also starts with a lower-case letter gets both changes in one
rename (`load` -> `LoadAsync`) while BRO1309 is on.
"Asynchronous" means the declared return type: `Task`, `Task`, `ValueTask`, `ValueTask` (System.Threading.Tasks)
or `IAsyncEnumerable`. Not the `async` keyword (a method can return a `Task` without it), and not other awaitable
types (`GetAwaiter()`): a custom awaitable says little about how callers use it. `async void` methods aren't reported.
**This renames public API**: callers in other repositories break, so the rule is off by default.
## Not reported
- **Where IDE1006 is suppressed**: after `#pragma warning disable IDE1006` (up to its `restore`), or with `[SuppressMessage("Style", "IDE1006")]` on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names.
- **A new name your SDK naming rule rejects**: when the first `dotnet_naming_rule` that covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like `_camelCase` for private properties, the name stays (renaming it would only trade this warning for IDE1006).
- **The name appears in code excluded by `#if` anywhere in the project**: disabled code isn't compiled, so the rename can't follow it.
- **Public API** (unless `stylebro_rename_public_api = true`): Methods that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also with `InternalsVisibleTo`.
- **A name that `nameof(...)` produces**: the rename would change the string (Ocelot built an HTTP header name with `nameof(X_RateLimit_Limit)`, and a test read a private field with `GetField(nameof(_components))`). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one.
- Names that contain `Async` anywhere (`ReadAsyncCore`, `WithAsyncLock`), and `Main`.
- Overrides and implementations: they keep the base member's name (a library's `ExecuteAsync`, `Dispose`); the base is
reported when it is in your code.
- Methods with attributes, except a few harmless ones (`[Obsolete]`, `[MethodImpl]`, `[DebuggerStepThrough]`,
`[DebuggerHidden]`, `[DebuggerNonUserCode]`, `[EditorBrowsable]`, `[SuppressMessage]`, `[ExcludeFromCodeCoverage]`,
`[Pure]`): test methods (`[Fact]`, `[Test]`, `[TestMethod]`), `[HttpGet]`, `[JSInvokable]`, `[OperationContract]`
and the like are found through their attribute, and some frameworks use the name.
- Methods of ASP.NET controllers (a type named `...Controller`, deriving from `Controller`/`ControllerBase`, or with
`[ApiController]`) and SignalR hubs (deriving from `Hub`): MVC trims `Async` from action names, and hub clients call
methods by name.
- Event handlers: `(object sender, EventArgs e)` or a type derived from `EventArgs`.
- Local functions, and a new name that is already taken (`Load` next to an existing `LoadAsync`).
- The same rename guards as BRO1309: `extern` and partial methods, members paired by name (`ShouldSerializeX`).
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
- **The name appears as a string anywhere in the solution** (`GetMethod("Save")`): reflection would break.
- **An implementation also implements another interface's member or a library's** with the old name: renaming it would
break that.
- **A derived type already has a member with the new name.**
## Not checked
The reverse (a method ending in `Async` that isn't asynchronous) isn't reported: such names are usually deliberate
(`IAsyncDisposable`-style wrappers, a synchronous method kept next to its old async name).
## Compared with StyleCop
StyleCop has no such rule. The SDK can report it with a naming rule (`required_modifiers = async`), but `dotnet format`
can't fix IDE1006.
## Real world (2026-10-06)
Run with every other rule and the repositories' tests (`Invoke-RealWorld.ps1 -Enable BRO1314 -Tests`): Jellyfin 496
findings (483 renamed, 13 kept by the string and interface guards, e.g. `Stop`), Polly 96 (95; `PredicateResult.True`
kept), FFMpegCore 7, Newtonsoft.Json 3, FluentValidation 2, Serilog 1, CsvHelper 0. All compile, converge in one run,
and no test that passed before fails.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1401/
# BRO1401: Use a trailing comma in multi-line initializers
The last item of a multi-line list should be followed by a comma. Replaces StyleCop **SA1413**.
## Example
### Before
```csharp
public enum Status
{
Active,
Closed
}
public class Settings
{
public string[] Hosts { get; } = new[]
{
"alpha",
"beta"
};
}
```
### After
```csharp
public enum Status
{
Active,
Closed,
}
public class Settings
{
public string[] Hosts { get; } = new[]
{
"alpha",
"beta",
};
}
```
## Why
With a trailing comma, adding an item at the end changes one line instead of two, and moving items around never
leaves a missing comma behind.
## What is reported
Like StyleCop, these lists, when their braces are on different lines:
- array, object and collection initializers (`new[] { ... }`, `new Point { ... }`, `new List { ... }`)
- `with { ... }` expressions and anonymous objects
- enums and switch expressions
## Configuration
```ini
[*.cs]
# include (default): a trailing comma after the last item of a multi-line list (StyleCop's SA1413).
# omit: no trailing comma after the last item of any list, single-line ones included.
stylebro_trailing_comma = include
```
With `omit` the rule reports the trailing comma instead, in the same kinds of lists, and the fix removes it:
```csharp
public enum Status
{
Active,
Closed, // last
}
var hosts = new[] { "alpha", "beta", };
```
becomes
```csharp
public enum Status
{
Active,
Closed // last
}
var hosts = new[] { "alpha", "beta" };
```
Unlike `include`, `omit` checks single-line lists too (like Roslynator's RCS1260 with `omit`): a comma there never
saves a diff line. Spaces before the removed comma go with it, and a trailing comment keeps its place. Lists with a
directive between their braces are still skipped. [BRO1509](BRO1509.md) and [BRO1121](BRO1121.md), which put a one-line
enum's values on their own lines, then don't add the trailing comma either. Not a StyleCop setting (StyleCop issue
[#2260](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2260) questions the rule); `stylebro-migrate`
doesn't write it.
## How the fix works
The comma goes right after the last item's code, so before a trailing comment: `"beta" // last` becomes
`"beta", // last`. When code follows the item directly (`"beta"};`), the fix inserts `, ` so the comma gets the space
after it that the formatter expects. Nested lists get all their commas in one `dotnet format` run.
## Not reported
- **Lists with a preprocessor directive between their braces**, such as an item inside `#if`. Which item is last then
depends on the build configuration, and in a multi-targeted project each target framework would need a different
edit to the same file.
- **Collection expressions `[ ... ]`, list patterns, property patterns** (`o is Point {` ... `}`, nested ones too), and
`{ key, value }` pairs in a dictionary initializer, like StyleCop 1.2.0-beta.556.
## Compared with StyleCop
Property patterns aren't checked, like StyleCop 1.2.0-beta.556; StyleCop's unreleased `master` reports multi-line
ones (94671b70), which put a comma after every closing `}` of a nested pattern (`{ Identifier.ValueText: "Requires" },`
then `},` and `},` in microsoft/vs-validation). The owner chose the release (decisions, 2026-10-11). StyleCop also reports lists with `#if` inside. Its fix writes `"beta",};` without a space, and needs a second run for
nested lists.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1402/
# BRO1402: Attribute constructor should not use unnecessary parenthesis
`[Obsolete]` instead of `[Obsolete()]`. Replaces StyleCop **SA1411**.
## Example
### Before
```csharp
using System;
[Serializable()]
public class Settings
{
[Obsolete()]
public void Reset()
{
}
}
```
### After
```csharp
using System;
[Serializable]
public class Settings
{
[Obsolete]
public void Reset()
{
}
}
```
## Why
Empty parentheses on an attribute add nothing; without them the attribute reads like the marker it is.
## How the fix works
The fix removes the empty parentheses and any space before or inside them.
## Not reported
- **A comment inside the parentheses** (`[Obsolete(/* later */)]`).
## Compared with StyleCop
Same positions as StyleCop. StyleCop's fix leaves the space of `( )` behind (`[Obsolete ]`).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1403/
# BRO1403: Remove delegate parenthesis when possible
`delegate { }` instead of `delegate() { }`. Replaces StyleCop **SA1410**.
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1125.severity = none
```
### Before
```csharp
using System;
public class Timer
{
public Action OnTick = delegate() { };
}
```
### After
```csharp
using System;
public class Timer
{
public Action OnTick = delegate { };
}
```
## Why
Empty parentheses add nothing: an anonymous method without a parameter list already fits any delegate.
## How the fix works
The parentheses are removed. Where [BRO1125](BRO1125.md) is on (the preset), it turns the anonymous method into a
lambda instead, so this rule only reports what BRO1125 leaves alone; the example turns BRO1125 off.
## Not reported
- Calls where the parentheses pick the overload (`Run(Action)` and `Run(Action)`), like StyleCop.
- Anonymous methods that BRO1125 turns into lambdas.
## Compared with StyleCop
StyleCop reports both SA1130 and SA1410 on `delegate() { }`; StyleBro reports only BRO1125 there, so the two fixes
don't edit the same code.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1404/
# BRO1404: Access modifier should be declared
Types and members say their accessibility instead of relying on the default. Replaces StyleCop **SA1400**.
## Example
### Before
```csharp
class Cache
{
int hits;
void Reset()
{
hits = 0;
}
}
```
### After
```csharp
internal class Cache
{
private int hits;
private void Reset()
{
hits = 0;
}
}
```
## Why
The default accessibility differs by place (`internal` for a top-level type, `private` for a member, `public` in an
interface), so a declaration without a modifier makes the reader work it out.
## How the fix works
The fix writes the accessibility the declaration has now, so nothing changes in what can use it. It goes before the
other modifiers (after attributes), where IDE0036's modifier order wants it.
This rule replaces the SDK's IDE0040, whose fix writes merge conflict markers in multi-targeted projects under
`dotnet format` (reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000); see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Configuration
```ini
[*.cs]
# The SDK's setting, which BRO1404 and BRO1007 follow:
# for_non_interface_members (or not set): StyleCop's SA1400/SA1205, as described here
# always: interface members get one too ('public', C# 8 and later)
# never, omit_if_default: no modifiers are asked for (nothing is reported)
dotnet_style_require_accessibility_modifiers = for_non_interface_members
```
## Not reported
- **Interface members** and types nested in interfaces. With `always` they are, from C# 8; a file also compiled with
an older version (a multi-targeted project's net472 build: C# 7.3) keeps the warning but isn't fixed.
- **Explicit interface implementations**, **static constructors**, **operators**, **finalizers** and **`file`
types**, which can't or needn't have one.
- **Partial members** (another part may declare it); partial types are [BRO1007](BRO1007.md)'s.
## Compared with StyleCop
Same positions and output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1405/
# BRO1405: Statement should not use unnecessary parenthesis
Parentheses around a single value, or around a whole expression where nothing binds to it, are removed. Replaces
StyleCop **SA1119**.
## Example
### Before
```csharp
public class Totals
{
private int count = (1 + 2);
public int Sum(int a, int b)
{
var total = (a + b);
return (total);
}
}
```
### After
```csharp
public class Totals
{
private int count = 1 + 2;
public int Sum(int a, int b)
{
var total = a + b;
return total;
}
}
```
## Why
Parentheses that don't group anything make the reader look for a precedence question that isn't there.
## How the fix works
The fix removes `(` and `)` and any spaces just inside them; where the tokens would run together (`return(x)`), a
space is left. Before removing, the statement (or member) is parsed again without the parentheses, and they're only
removed when the result means the same: parentheses can turn `F(a < b, (c > (d + 1)))` into a generic method call
`a(d + 1)` when they go.
Like StyleCop, parentheses inside an operator expression (`a + (b * c)`, `a ?? (b ?? c)`) are left alone: they say how
the expression groups, or make it clearer. Adding parentheses for clarity is SA1407/SA1408 (the SDK's IDE0048).
In an IDE the parentheses are also faded, like StyleCop's SA1119_p: a hidden companion diagnostic, `BRO1405_p`, is
reported on the `(` and `)` of every finding with the `Unnecessary` tag. It can't be configured, has no fix of its own
(the build and `dotnet format` ignore hidden diagnostics), and is only reported where BRO1405 is on. Parentheses
around patterns are [BRO1410](BRO1410.md)'s (with `BRO1410_p`).
This rule replaces the SDK's IDE0047, whose fix writes merge conflict markers in multi-targeted projects under
`dotnet format` (reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000)) and which also removes `a ?? (b ?? c)` (see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Not reported
StyleCop's exceptions: operator expressions inside other expressions, a conditional in an interpolation
(`$"{(f ? 1 : 2)}"`), a conditional access before `.`/`[]` (`(s?.Length).ToString()`), a `switch` or `with`
expression that is called, cast or accessed, `stackalloc` in an initializer, the left side of a `ref` conditional
assignment. Also: parentheses with a comment and a line break inside them (`(` / `x + 1 // one` / `)`: removing them
would leave the `;` on a line of its own), and ones whose removal would change how the code parses. A comment on one
line with the code is kept: `(x + 1 /* one */)` becomes `x + 1 /* one */`.
## Configuration
The SDK's `dotnet_style_parentheses_*` settings don't change this rule: it removes what StyleCop's SA1119 removes. With
`never_if_unnecessary` the SDK would also remove parentheses inside operator expressions (`a ?? (b ?? c)`); StyleBro
leaves those, so a StyleCop-clean codebase stays unchanged after migrating. `never_if_unnecessary` does turn off adding
parentheses ([BRO1406](BRO1406.md), [BRO1407](BRO1407.md)).
## Compared with StyleCop
Same positions, except the parse-changing case above, where StyleCop's fix doesn't compile. StyleCop's fix leaves the
spaces of `( b )` behind.
StyleCop also reports a hidden SA1119_p on the same parentheses' `(` and `)`, only so the IDE greys them out; StyleBro
does the same with `BRO1405_p` (see above). StyleCop doesn't check parentheses in patterns (`x is (> 0)`); StyleBro's
[BRO1410](BRO1410.md) does.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1406/
# BRO1406: Arithmetic expressions should declare precedence
An operation inside an arithmetic operation of another kind is in parentheses. Replaces StyleCop **SA1407**.
## Example
### Before
```csharp
public class Prices
{
public int Total(int price, int count, int fee, int mask) =>
price * count + fee >> mask % 4;
}
```
### After
```csharp
public class Prices
{
public int Total(int price, int count, int fee, int mask) =>
((price * count) + fee) >> (mask % 4);
}
```
## Why
Precedence between `+`, `*`, `%` and `<<` is easy to get wrong when reading; parentheses say what the code does.
## How the fix works
The operand gets parentheses. The families are `+ -`, `* /` and `<< >>`; `%` is a family of its own, so `a * b % c`
becomes `(a * b) % c`. Operations of the same family (`a + b - c`) are left alone. Nested operands get all their
parentheses in one pass.
This rule replaces the SDK's IDE0048, whose fix writes merge conflict markers in multi-targeted projects under
`dotnet format` (reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000)) and needed a second run (see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Configuration
```ini
[*.cs]
# The SDK's setting: always_for_clarity (or not set) keeps this rule; never_if_unnecessary turns it off.
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
```
## Compared with StyleCop
Same positions and output.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1407/
# BRO1407: Conditional expressions should declare precedence
`&&` and `||` aren't mixed without parentheses, and neither are the `and` and `or` patterns. Replaces StyleCop
**SA1408**.
## Example
### Before
```csharp
public class Access
{
public bool CanEdit(bool admin, bool owner, bool locked, int level) =>
admin || owner && !locked || level is > 5 and < 9 or 10;
}
```
### After
```csharp
public class Access
{
public bool CanEdit(bool admin, bool owner, bool locked, int level) =>
admin || (owner && !locked) || level is (> 5 and < 9) or 10;
}
```
## Why
`&&` binds tighter than `||`, but readers often don't remember that, and a misread condition is a bug.
## How the fix works
The `&&` (or `and`) operand of an `||` (or `or`) gets parentheses. Nested operands get all their parentheses in one
pass.
This rule replaces the SDK's IDE0048, whose fix writes merge conflict markers in multi-targeted projects under
`dotnet format` (reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000); see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Configuration
```ini
[*.cs]
# The SDK's setting: always_for_clarity (or not set) keeps this rule; never_if_unnecessary turns it off.
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
```
## Compared with StyleCop
Same positions and output for `&&`/`||`. The released StyleCop (1.2.0-beta.556) doesn't check patterns; its current
source does, and so does this rule.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1408/
# BRO1408: Remove a redundant base type
`enum E : int` and `class C : object` name what the declaration means anyway: an enum's underlying type is `int` and a
class derives from `object` unless they say otherwise. Not a StyleCop rule (Roslynator RCS1042, Sonar S1939).
## Example
### Before
```csharp
using System;
public enum Color : int
{
Red,
Green,
}
public class Shape : object, IDisposable
{
public void Dispose()
{
}
}
```
### After
```csharp
using System;
public enum Color
{
Red,
Green,
}
public class Shape : IDisposable
{
public void Dispose()
{
}
}
```
## Why
A base type the compiler adds anyway suggests a choice that wasn't made: `enum E : int` reads like the size matters
(for interop or serialization) when it is just the default.
## How the fix works
The base type is removed with its `:` when it is the only one, or with the `,` after it when interfaces follow.
## Not reported
- Enums with another underlying type (`byte`, `long`, ...). The check is semantic: `int`, `Int32`, `System.Int32` and
an alias of it are all reported.
- A base type named `Object` that isn't `System.Object`.
- A comment or directive in the removed text.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1409/
# BRO1409: Methods of internal types should be internal, not public
An ordinary method declared `public` in a type that can't be seen outside the assembly (the type, or a type it is nested
in, is `internal`, `private`, `private protected` or `file`) is internal in effect; `internal` says so. Not a StyleCop
rule: proposed in StyleCop issue [#2981](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2981), never
implemented. **Off by default**, also in the preset. Deliberately narrow: only methods, and only where nothing outside
the compiler's own rules could depend on `public` (see [Why](#why) and [Not reported](#not-reported)).
## Example
```ini
root = true
[*.cs]
dotnet_diagnostic.BRO1409.severity = warning
```
### Before
```csharp
internal sealed class PriceCalculator
{
public decimal Total(decimal net, decimal rate) => net * (1 + rate);
}
```
### After
```csharp
internal sealed class PriceCalculator
{
internal decimal Total(decimal net, decimal rate) => net * (1 + rate);
}
```
## Why
`public` on a member of an internal type promises more than it gives: the member is reachable only inside the assembly
(and its `InternalsVisibleTo` friends) either way. `internal` tells the reader that, and making the type public later
doesn't silently publish the member.
But the compiler is not the only one that reads `public`. Reflection's defaults look at public members only, and so do
most libraries built on it. On a member of an internal type, `public` vs `internal` changes what they see:
```csharp
internal class Order
{
public int Id { get; set; } // internal: System.Text.Json and Newtonsoft.Json skip it, "Id" vanishes from the JSON
public void Ship() { } // internal: typeof(Order).GetMethods() no longer lists it
}
public class OrderTests
{
[Fact]
public void Ships() { } // internal: xUnit, NUnit and MSTest don't discover the test
}
```
The same goes for XmlSerializer and data contracts (properties and fields), WPF/WinForms data binding and ASP.NET model
binding (properties), AutoMapper and Dapper (properties), `Activator.CreateInstance` and
Microsoft.Extensions.DependencyInjection (public constructors), MVC actions, SignalR hubs and Blazor's `[JSInvokable]`
(methods). So the rule leaves out every kind of member and every method such code finds, and keeps the ordinary
methods your own code calls.
## How the fix works
It replaces `public` with `internal` where `public` was (`static public` becomes `static internal`; an explicit modifier
stays, because [BRO1404](BRO1404.md) wants one). The method's name is then looked up in every string literal and every
`nameof(...)` of the solution (`GetMethod("Ship")`, `GetMethod(nameof(Order.Ship))`): when it appears, the warning stays
and the fix leaves the method alone, like the naming rules' reflection guard. The same when another project (an
`InternalsVisibleTo` friend) derives from the type: its class could implement an interface with the inherited method.
[BRO1001](BRO1001.md) sorts public members before internal ones, so a method that becomes internal may now be out of
order: when BRO1001 is on, the fix sorts the type too, so one `dotnet format` run is enough.
## Not reported
- **Anything but ordinary methods**: properties, fields, events, indexers (serializers, binding, mappers), constructors
(dependency injection, `Activator`), operators and conversion operators (must be public), finalizers, nested types.
- **Methods others implement or override with**: `override`, `virtual`, `abstract`, implicit and explicit interface
implementations (also an inherited method that a derived class uses to implement an interface), methods of
interfaces.
- **Methods with any attribute** (tests, benchmarks, `[JSInvokable]`, MVC, `[OnDeserialized]` callbacks, ...), and
methods of a type that has an attribute or is nested in one that has.
- **Methods of a type deriving from a type of another assembly** other than `object`/`ValueType` (ASP.NET `Controller`
and `Hub`, test base classes, Unity's `MonoBehaviour` messages, `Exception`, `List`): frameworks call public
methods of such types by convention.
- **Names found by convention**: `Main`, `Dispose`, `DisposeAsync`, `GetEnumerator`, `GetAsyncEnumerator`, `MoveNext`,
`MoveNextAsync`, `GetAwaiter`, `GetResult`, `Add` (collection initializers), `Deconstruct`, `GetPinnableReference`,
`Configure`, `ConfigureServices`, `ConfigureContainer` (startup classes), `Invoke`, `InvokeAsync` (middleware), and the
names records synthesize (`PrintMembers`, `Equals`, `GetHashCode`, `ToString`).
- **`extern` and `partial` methods.**
- **`#if`** in the method or in the header of an enclosing type (the base list can differ per target framework).
Extension methods are reported: `internal` works for them everywhere the internal type does.
## Reported but not fixed
**If `dotnet format` leaves a finding, StyleBro kept it on purpose.** The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a `nameof` (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and `stylebro-migrate format` lists each one with its reason
([Findings kept on purpose](../getting-started.md#findings-kept-on-purpose)); its `--verify-no-changes` doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the [baseline](../baseline.md).
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` leaves it off.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1410/
# BRO1410: Patterns should not use unnecessary parentheses
Parentheses around a pattern that means the same without them are removed: `x is (> 0)`, `o is (string s)`,
`case (int i):`, a pair around another pair. Not a StyleCop rule: SA1119 ([BRO1405](BRO1405.md)) only looks at
parenthesized expressions.
## Example
### Before
```csharp
public static class Shapes
{
public static bool IsPositive(int x) => x is (> 0);
public static int Length(object o) => o is (string s) ? s.Length : 0;
public static bool IsSmall(int x) => x is ((1 or 2)) or 3;
}
```
### After
```csharp
public static class Shapes
{
public static bool IsPositive(int x) => x is > 0;
public static int Length(object o) => o is string s ? s.Length : 0;
public static bool IsSmall(int x) => x is 1 or 2 or 3;
}
```
## Why
Like BRO1405 for expressions: parentheses that don't group anything make the reader look for a precedence question
that isn't there.
## How the fix works
The fix removes `(` and `)` and any spaces just inside them; where the tokens would run together (`is(string s)`), a
space is left. Like BRO1405, the statement (or member) is parsed again without the parentheses, and they're only
removed when the result is the same pattern.
In an IDE the parentheses are also faded: a hidden companion diagnostic, `BRO1410_p`, is reported on the `(` and `)`
of every finding, with the `Unnecessary` tag (like StyleCop's SA1119_p for SA1119; BRO1405 has `BRO1405_p`). It can't
be configured, has no fix of its own, and is only reported where BRO1410 is on.
## Not reported
- Parentheses that change the meaning: `not (A or B)`, `(A or B) and C`, `A or (B or C)` (patterns group from the left,
so `A or B or C` is a different tree).
- The parentheses [BRO1407](BRO1407.md) (SA1408) wants between `and` and `or`: `A or (B and C)`, `(A and B) or C`. A
second pair around them (`A or ((B and C))`) is reported. With BRO1407 off, or
`dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary`, these are removed too.
- A parenthesized constant (`x is (null)`, `x is (1)`): C# parses it as a parenthesized expression, which is BRO1405's.
- Parentheses with a comment and a line break inside them. A comment on one line with the pattern is kept:
`(> 1 /* low */)` becomes `> 1 /* low */`.
## Compared with StyleCop
StyleCop has no such rule. `stylebro-migrate` turns it off (see
[Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
In the 12 reference repositories (2026-10-09) there are 32 parenthesized patterns and none is reported: 24 put an `and`
inside an `or` (`c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z')`, which BRO1407 asks for) and 8 follow `not`. With
BRO1407 off, 12 of those 24 are reported (OpenTelemetry; the others are in `#if` code the survey didn't compile).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1501/
# BRO1501: Opening braces should not be preceded by a blank line
An opening brace on its own line directly follows the line it belongs to. Replaces StyleCop **SA1509**.
## Example
### Before
```csharp
public class Parser
{
public int Parse(string text)
{
return int.Parse(text);
}
}
```
### After
```csharp
public class Parser
{
public int Parse(string text)
{
return int.Parse(text);
}
}
```
## Why
A blank line between a declaration and its brace visually separates things that belong together. This covers every
kind of opening brace: types, members, statements, initializers and `case` blocks.
## How the fix works
The fix removes the blank lines directly above the brace. A comment directly above the brace is kept; blank lines
above that comment are left alone.
## Not reported
- **A brace whose previous token is a `}`**, such as a block following another block, even with a comment in between.
A standalone block after an ordinary statement is reported:
```csharp
var path = GetPath();
{ // BRO1501, like SA1509
Use(path);
}
{ // not reported: follows a '}'
}
```
- **A comment or directive directly above the brace.**
- **The brace of an entry in an initializer** (`{ "ssh", 22 }` in a dictionary initializer, an inner `{ 1, 2 }` of a
multi-dimensional array): a blank line between entries groups them.
```csharp
var ports = new Dictionary
{
{ "http", 80 },
{ "https", 443 },
{ "ssh", 22 }, // not reported: the blank line above groups the entries
};
```
- **Braces inside strings**, such as `{x}` in a multi-line interpolated string. Only whitespace outside code is
checked, never string contents.
## Compared with StyleCop
Same results as StyleCop, including the exception for a brace after a `}`, except the brace of an initializer's entry:
SA1509 reports it and its fix removes the grouping blank line ([#2832](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2832), open).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1502/
# BRO1502: Chained blocks should not be preceded by a blank line
`else`, `catch` and `finally` directly follow the block they continue. Replaces StyleCop **SA1510**.
## Example
### Before
```csharp
public class Gate
{
public string Open(bool allowed)
{
if (allowed)
{
return "open";
}
else
{
return "closed";
}
}
}
```
### After
```csharp
public class Gate
{
public string Open(bool allowed)
{
if (allowed)
{
return "open";
}
else
{
return "closed";
}
}
}
```
## Why
A blank line before `else` makes it look like the start of a new statement instead of part of the `if`. The same goes
for `catch` and `finally` after `try`.
## How the fix works
The fix removes the blank lines directly above the keyword. It also applies when the `if` has no braces.
## Not reported
- **A comment or directive directly above the keyword**, for example a comment explaining the `else` branch.
- **The `while` of a `do … while` loop**, like StyleCop.
## Compared with StyleCop
Same results as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1503/
# BRO1503: Opening braces should not be followed by a blank line
The first line inside a block directly follows its opening brace. Replaces StyleCop **SA1505**.
## Example
### Before
```csharp
public class Parser
{
public int Parse(string text)
{
return int.Parse(text);
}
}
```
### After
```csharp
public class Parser
{
public int Parse(string text)
{
return int.Parse(text);
}
}
```
## Why
A blank line right after `{` adds height without separating anything; the brace already marks where the block
starts. This is the counterpart of [BRO1501](BRO1501.md), which removes blank lines before the brace.
## How the fix works
The fix removes the blank lines directly below the brace, for every kind of opening brace: namespaces, types, members,
statements and initializers.
## Not reported
- **A brace followed by a comment on the same line** (`{ // note`).
- **Braces inside strings**, such as a `{` in a raw string literal. Only whitespace outside code is checked, never
string contents.
## Compared with StyleCop
Same results as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1504/
# BRO1504: Single-line comments should be preceded by a blank line
A `//` comment that follows code on the line above gets a blank line before it. Replaces StyleCop **SA1515**.
## Example
### Before
```csharp
public class Totals
{
public decimal Sum(decimal[] amounts)
{
var sum = 0m;
// Round each amount first, like the invoice does.
foreach (var amount in amounts)
{
sum += decimal.Round(amount, 2);
}
// The caller adds VAT.
return sum;
}
}
```
### After
```csharp
public class Totals
{
public decimal Sum(decimal[] amounts)
{
var sum = 0m;
// Round each amount first, like the invoice does.
foreach (var amount in amounts)
{
sum += decimal.Round(amount, 2);
}
// The caller adds VAT.
return sum;
}
}
```
## Why
A comment usually introduces the code below it. A blank line above makes clear which code it belongs to, and splits a
long method into readable steps.
## How the fix works
The fix inserts a blank line above the comment.
## Configuration
```ini
# Comments whose text starts with one of these (comma-separated, case-sensitive) need no blank line above.
# Default: none.
stylebro_comment_blank_line_exempt_prefixes = ReSharper, @formatter
```
Tool markers such as `// ReSharper disable once UnusedVariable` or `// @formatter:off` belong to the line below them
and aren't the start of a new step, so a blank line above them only pushes them away from the code they mark (StyleCop
issues [#3546](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3546) and
[#1490](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1490), where the only workaround is `////`). The
prefix is matched against the comment's text after `//` and any spaces, so `ReSharper` covers `// ReSharper disable`
and `//ReSharper restore`.
## Not reported
Like StyleCop:
- **The line above is blank, another comment, or a preprocessor directive.** A block of consecutive comment lines
only needs one blank line, above the first.
- **The comment directly follows an opening brace** (the first line of a block), **the `[` of a collection expression,
or a `case`/`default` label.**
- **The comment directly follows `=>`** (a switch expression arm, a lambda or an expression body): it starts what
comes after the arrow, like a comment after `{`.
- **`////` commented-out code** and `///` comments.
- **Trailing comments** (`x++; // note`), which don't start their line, and **a comment that continues a trailing
comment** in the line below, starting in the same column: a blank line would split the sentence.
- **A comment above a switch section** (`return;`, `// note`, `case 2:`) when `stylebro_blank_line_between_switch_sections`
([BRO1526](BRO1526.md)) wants no blank line between the sections.
Unlike StyleCop:
- **A comment below code**: a run of `//` lines right below a line that ends with `;`, followed by a blank line. It
describes the code above it (eShop: a note below a field, then a blank line, then a method); moving the blank line
above it would attach it to the code below. StyleCop reports it.
- **A comment between the links of a call chain**: the next token is the `.` or `?.` of a member access, as in
`builder.Services.AddHealthChecks()`, `// Add a default liveness check`, `.AddCheck(...)` (davidfowl/TodoApi). The
comment describes the next link, and a blank line would split the chain. StyleCop reports it.
- **A comment between the items of a list**: right after the list's `(`, `[`, `{` or a `,`, or right before a `,` or the
list's closing token, in argument, indexer argument, attribute argument, parameter, initializer (object, collection,
array), collection expression and tuple lists (`Path.Join(dir, "game.json"),`, `// options for ...`,
`new JsonSerializerOptions()`). The comment describes the next item, and a blank line would split the list. StyleCop
reports it.
A comment after an ordinary `label:` is reported, like in StyleCop.
## Compared with StyleCop
Same results as StyleCop 1.2.0-beta.556, except a comment right after a collection expression's `[`: beta.556
reports it, StyleBro treats `[` like `{` (as StyleCop's unreleased fix for
[#3766](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3766) does), and a comment right after `=>`:
StyleCop reports it and its fix puts a blank line between the arrow and the comment ([#3392](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3392), open; for expression
bodies [#3550](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3550)), and a comment between call chain
links or list items (above). Both only make StyleBro quieter, so a StyleCop-clean repository stays clean.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1505/
# BRO1505: Elements should be separated by a blank line
Members, types and namespaces are separated by a blank line; consecutive fields may stay together, unless the first
spans several lines (a multi-line initializer), and so may consecutive single-line properties. Replaces StyleCop **SA1516**.
## Example
### Before
```csharp
using System;
namespace Shop
{
public class Cart
{
private readonly decimal _vat = 0.25m;
private decimal _net;
public event EventHandler? Changed;
public decimal Total => _net * (1 + _vat);
public void Add(decimal price)
{
_net += price;
Changed?.Invoke(this, EventArgs.Empty);
}
/// Empties the cart.
public void Clear() => _net = 0;
}
}
```
### After
```csharp
using System;
namespace Shop
{
public class Cart
{
private readonly decimal _vat = 0.25m;
private decimal _net;
public event EventHandler? Changed;
public decimal Total => _net * (1 + _vat);
public void Add(decimal price)
{
_net += price;
Changed?.Invoke(this, EventArgs.Empty);
}
/// Empties the cart.
public void Clear() => _net = 0;
}
}
```
## Why
Blank lines between members make the structure of a type visible at a glance, and let each member's comments and
attributes clearly belong to it. Fields are usually read together, so they may stay in one block.
## What is reported
An element that directly follows another element without a blank line in between:
- members of classes, structs, records and interfaces
- types and namespaces, and the first element after the using directives, assembly attributes or a file-scoped
`namespace X;`
- accessors, when either of two neighbouring accessors spans several lines
A blank line anywhere between the two counts, also one between an element's comment or `#if` and its code. Like StyleCop,
these may stay together: two fields, two using directives, two extern aliases, two attribute lists,
and single-line accessors (`get { ... }` above `set { ... }`). Like StyleCop's unreleased `master`, two properties may
too when neither spans several lines (`int A { get; set; }` directly above `int B { get; set; }`) and neither has a
`///` documentation comment. A documented property is set apart like any other member: with
[BRO1601](BRO1601.md) adding `/// ` to some properties of a list, which of them ended up separated
depended on the order `dotnet format` ran the fixes in (found in Ocelot); BRO1601's fix adds the blank line below the
property it documents too.
## How the fix works
The blank line goes above the element's first line: above its comments, XML docs and attributes, or above an `#if`
around it. Two members on one line are split, with a blank line between and the same indentation.
## Not reported
- **Two elements on one line with a comment between them.** The fix would have to move the comment.
- **Enum members**, which aren't elements in this sense.
- **Single-line members without a block body** next to each other, with `stylebro_allow_adjacent_single_line_members`
(see Configuration).
A field or property counts as spanning several lines from the line its attributes end on, like in StyleCop:
`[Obsolete]` on its own line above `private int a;` doesn't make it multi-line.
While [BRO1527](BRO1527.md) is on, an auto-property it puts on one line (`{ get; set; }` spread over several lines)
already counts as one line, so the blank line next to it doesn't depend on which fix `dotnet format` runs first.
## Configuration
```ini
[*.cs]
stylebro_allow_adjacent_single_line_members = false
```
- `false` (default): like StyleCop, only the pairs listed under What is reported may sit together.
- `true`: two neighbouring single-line members without a block body may sit together too: methods, operators and
conversion operators that are bodiless or expression-bodied, properties, indexers and events whose accessors have no
block body, and field-like events, in any combination (StyleCop issue #2441). For example:
```csharp
public abstract void Open();
public int Count() => items.Count;
public int this[int i] => items[i];
public event EventHandler? Changed;
```
Still separated with `true`: a member with a block body, even `{ }` on one line ([BRO1509](BRO1509.md) expands it); a
method with `where` clauses ([BRO1111](BRO1111.md) may put them on their own lines); a member that spans several lines;
constructors, finalizers, fields, types and everything else next to them. [BRO1001](BRO1001.md)'s sort,
[BRO1114](BRO1114.md)'s split of event fields and BRO1509's expansion of a one-line type follow the setting, so they
add the blank line only where this rule wants one.
## Compared with StyleCop
Same positions as StyleCop 1.2.0-beta.556, except two single-line undocumented properties: they may sit together, like in StyleCop's
`master` (unreleased, 2aeb4e3d; the owner's decision, see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)), where beta.556 wants a blank
line between them. (`master` also wants one between two fields when the second spans several lines; StyleBro, like
beta.556, only looks at the first.) StyleCop's fix leaves out the blank line above a `///` doc comment, and when it splits two
members on one line it leaves a trailing space and no indentation.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1506/
# BRO1506: Single-line comments should not be followed by a blank line
A `//` comment sits directly on the code it describes. Replaces StyleCop **SA1512**.
## Example
### Before
```csharp
public class Shipping
{
public decimal Cost(decimal weight)
{
// Parcels up to 2 kg ship at a flat rate.
if (weight <= 2)
{
return 5m;
}
return 5m + (weight - 2) * 1.5m;
}
}
```
### After
```csharp
public class Shipping
{
public decimal Cost(decimal weight)
{
// Parcels up to 2 kg ship at a flat rate.
if (weight <= 2)
{
return 5m;
}
return 5m + (weight - 2) * 1.5m;
}
}
```
## Why
A blank line below a comment makes it unclear what the comment belongs to. Without it, the comment clearly describes
the code right below it. Together with [BRO1504](BRO1504.md) (a blank line *above* such a comment), comments end up
attached to their code and separated from what came before.
Not every team wants this: of the three StyleCop repos surveyed for StyleBro, two turn SA1512 off. Set
`dotnet_diagnostic.BRO1506.severity = none` in `.editorconfig` if you use comments as free-standing section notes.
`stylebro-migrate init` does that when your code mostly keeps a blank line below its comments, or mixes both.
## How the fix works
The fix removes the blank lines directly below the comment.
## Configuration
```ini
[*.cs]
# allow (default): a standalone comment keeps the blank line below it; remove: it goes too, like StyleCop's SA1512
stylebro_blank_line_below_standalone_comments = allow
```
A standalone comment has a blank line right above it and right below it (`#region` lines next to it don't count): a
note on the code above, or a section header. Removing the blank line below glues it onto whatever comes next.
`stylebro-migrate` writes `remove` for StyleCop setups with SA1512 on, so a StyleCop-clean repo stays clean.
```csharp
public void Send()
{
Connect();
// Retries are handled by the transport.
Flush();
}
```
## Not reported
Like StyleCop:
- **The file header**: the comments at the very top of the file, before any code.
- **A comment followed by another comment** (single-line or block) after the blank line, such as two paragraphs of a
longer note.
- **`////` commented-out code** and `///` comments.
- **Trailing comments** (`x++; // note`).
Unlike StyleCop:
- **A comment below code**: a run of `//` lines right below a line that ends with `;`, followed by a blank line. It
describes the code above it (eShop: a note below a field, then a blank line, then a method); moving the blank line
above it would attach it to the code below. StyleCop reports it.
- **A standalone comment** (a blank line above and below it, not right before a `}`), unless
`stylebro_blank_line_below_standalone_comments = remove`. StyleCop reports it.
## Compared with StyleCop
Same results as StyleCop with `stylebro_blank_line_below_standalone_comments = remove` (what `stylebro-migrate` writes
when SA1512 is on), apart from the comment below code above.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1507/
# BRO1507: Files should not end with blank lines
A file ends with its last line of code, followed by at most one line break. Replaces StyleCop **SA1518**.
## Example
### Before
```csharp
public class Order
{
public int Id { get; set; }
}
```
### After
```csharp
public class Order
{
public int Id { get; set; }
}
```
## Why
Blank lines at the end of a file are invisible noise, and they show up as changes in diffs when an editor adds or
trims them.
## How the fix works
The fix removes everything after the last line's line break: extra blank lines, and lines containing only spaces.
## Configuration
Like StyleCop's default (`"newlineAtEndOfFile": "allow"`), a file may end with one line break or without one. To
require a final line break, use the SDK's own setting, which `dotnet format` applies:
```ini
[*.cs]
insert_final_newline = true
```
## Compared with StyleCop
Same results as StyleCop's default setting. StyleCop's `require` and `omit` settings correspond to
`insert_final_newline = true` and `false` in `.editorconfig`.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1508/
# BRO1508: A block should not be on a single line
A statement's block (`if`, `else`, loops, `using`, `lock`, `try`/`catch`/`finally`, a standalone `{ }`) puts its braces
and each statement on their own lines. Replaces StyleCop **SA1501**.
## Example
### Before
```csharp
public class Account
{
public decimal Balance { get; private set; }
public void Withdraw(decimal amount)
{
if (amount <= 0) { return; }
if (amount > this.Balance) { throw new System.InvalidOperationException(); } else { this.Balance -= amount; }
}
}
```
### After
```csharp
public class Account
{
public decimal Balance { get; private set; }
public void Withdraw(decimal amount)
{
if (amount <= 0)
{
return;
}
if (amount > this.Balance)
{
throw new System.InvalidOperationException();
}
else
{
this.Balance -= amount;
}
}
}
```
## Why
A one-line block hides control flow at the end of a line, and adding a second statement means reformatting the whole
block. Blocks laid out the same way everywhere are faster to scan.
## How the fix works
The braces get their own lines at the indentation of the statement, the statements inside go one level deeper (from
`indent_style`/`indent_size` in `.editorconfig`), and an empty block becomes `{` and `}` on two lines. `else`, `catch`
and `finally` that followed the `}` move to their own line. The fix only rewrites the spaces between those tokens, so
the code itself and the file's line endings stay as they are. Nested one-line blocks are
expanded in one pass. When [BRO1519](BRO1519.md) is on, a statement after the expanded block gets its blank line in
the same pass, and so does a statement inside that [BRO1514](BRO1514.md), [BRO1515](BRO1515.md) or
[BRO1516](BRO1516.md) wants braces on (`if (a) { if (b) return; }`): those rules leave the statements of a one-line
block to this fix, so one `dotnet format` run is enough. A block inside a one-line method body
(`void M(bool x) { if (x) { return; } }`) expands the body too when [BRO1509](BRO1509.md) is on: expanding only the
inner block would leave `void M(bool x) { if (x)` on a line, which no rule reports any more. With `csharp_preserve_single_line_statements = false` a statement without braces (`if (x) y;`, loops, `else`) gets its embedded statement on its own line, one level deeper, as the SDK's whitespace formatting would do once the block isn't on one line (it runs before the fixes, so otherwise a second run). A braced one (`if (x) { y; } else z;`) keeps its block on one line, with the `{` and `else` on their own lines as `csharp_new_line_before_open_brace` and `csharp_new_line_before_else` say.
The SDK's settings are respected: with `csharp_new_line_before_open_brace` not including `control_blocks`, the `{`
stays at the end of the statement's line (`if (x) {`), and `csharp_new_line_before_else = false` keeps `} else {`.
`do { ... } while (x);` keeps `while` after the `}`.
## Configuration
```ini
[*.cs]
# false (default, like StyleCop): an empty block on one line is expanded too.
# true: an empty block on one line ('catch { }', 'while (Next()) { }'; whitespace only) stays.
stylebro_allow_empty_single_line_blocks = false
```
The key is shared with [BRO1509](BRO1509.md#configuration), which then leaves empty member bodies and types on one
line too. With `true`, `try { Run(); } catch { }` becomes:
```csharp
try
{
Run();
}
catch { }
```
## Not reported
- **Lambda and anonymous-method bodies** (`() => { Run(); }`), like StyleCop.
- **A comment inside the braces** (`{ /* note */ return; }`): the fix would have to move it.
- **A comment after the `}` on its line** (`catch (IOException) { } // slopwatch-ignore: SW003 ...`): expanded, the
comment would end up on the `}` line, away from the statement a line-based suppression marks. A one-line block inside
such a block stays too.
- **A block that shares its line with code that stays there**, like `case 1: { ... }` inside a
`switch (x) { ... }` written on one line: there's no line to indent it from.
- Method and local function bodies are [BRO1509](BRO1509.md)'s.
## Compared with StyleCop
Same positions as StyleCop, except the cases under "Not reported" above. StyleCop's fix misindents nested blocks (it
writes `{` at the start of the line), moves `while` of `do ... while` to its own line, and writes Windows line endings
into files that use `\n`.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1509/
# BRO1509: An element should not be on a single line
Types, namespaces, method bodies (including constructors, operators and local functions) and accessor lists with bodies
put their braces and contents on separate lines. Replaces StyleCop **SA1502**.
## Example
### Before
```csharp
namespace Shop
{
public class Empty { }
public class Price { private decimal amount; public decimal Amount { get { return this.amount; } } public void Clear() { this.amount = 0; } }
}
```
### After
```csharp
namespace Shop
{
public class Empty
{
}
public class Price
{
private decimal amount;
public decimal Amount
{
get { return this.amount; }
}
public void Clear()
{
this.amount = 0;
}
}
}
```
## Why
Declarations that always look the same are easy to scan, and a member written on one line has to be reformatted as
soon as it grows.
## How the fix works
The braces get their own lines at the declaration's indentation and each member, statement or accessor inside goes on
its own line one level deeper (from `indent_style`/`indent_size` in `.editorconfig`). Members that
[BRO1505](BRO1505.md) wants separated get their blank line right away, and a statement in a method body that
[BRO1514](BRO1514.md)-[BRO1516](BRO1516.md) want braces on gets them (`void M(bool x) { if (x) return; }`), so one
`dotnet format` run is enough. That includes a property whose one-line accessor list is expanded: it spans several lines
then, so it's separated from a neighbouring property by a blank line (two one-line properties may sit together). A statement after a local function's `}` gets [BRO1519](BRO1519.md)'s blank line. Enum values
each get their own line with a trailing comma (none with `stylebro_trailing_comma = omit`), which [BRO1121](BRO1121.md) and [BRO1401](BRO1401.md) want (StyleCop's fix
keeps them on one line, which its own SA1136 then reports); accessors stay on one line each
(`get { return this.amount; }`), which StyleCop allows. With `csharp_new_line_before_open_brace` not including the
element's kind (`types`, `methods`, `local_functions`, `properties`, `indexers`, `events`), the `{` stays at the end of
the declaration's line. With `csharp_preserve_single_line_statements = false` a statement without braces (`if (x) y;`, loops, `else`) gets its embedded statement on its own line, one level deeper, as the SDK's whitespace formatting would do once the block isn't on one line (it runs before the fixes, so otherwise a second run). Nested ones (`for (...) if (x) y;`) go one level deeper each, a `using` right inside a `using` stays at its level, and a one-line `switch` gets the formatter's layout: labels and statements on their own lines (per `csharp_indent_switch_labels` and `csharp_indent_case_contents`), with [BRO1526](BRO1526.md)'s blank line between sections. A braced one (`if (x) { y; } else z;`) keeps its block on one line, with the `{` and `else` on their own lines as `csharp_new_line_before_open_brace` and `csharp_new_line_before_else` say.
## Configuration
```ini
[*.cs]
# false (default, like StyleCop): empty braces on one line are expanded too.
# true: empty braces on one line ('{ }', whitespace only) stay, for every kind this rule and BRO1508 check.
stylebro_allow_empty_single_line_blocks = false
```
With `true`, `public Foo() { }`, `public void OnStart() { }`, `public class Marker { }`, `enum None { }` and
statement blocks like `catch { }` ([BRO1508](BRO1508.md) reads the same key) stay on one line; anything with code
inside is still expanded:
```csharp
public class Hooks { public Hooks() { } public void Stop() { this.Stopped = true; } }
```
becomes
```csharp
public class Hooks
{
public Hooks() { }
public void Stop()
{
this.Stopped = true;
}
}
```
Expanding an enclosing block never touches an allowed empty one inside it. [BRO1140](BRO1140.md) and
[BRO1145](BRO1145.md), when on, still turn an empty record or type body into `;`. Not a StyleCop setting (StyleCop
issues [#3093](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3093) and
[#3564](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3564) ask for it); `stylebro-migrate` doesn't write
it. The SDK's `csharp_preserve_single_line_blocks` only keeps blocks as they are, so it can't enforce either layout.
## Not reported
- **Auto-properties and expression-bodied accessors** (`{ get; set; }`, `{ get => x; }`), like StyleCop: only an
accessor list where an accessor has a body counts.
- **Single-line accessors inside a multi-line property**, like StyleCop.
- **A comment inside the braces**: the fix would have to move it.
- **A comment after the `}` on its line** (`public void Reset() { } // NOSONAR`): expanded, the comment would end up on
the `}` line, away from the declaration a line-based suppression marks.
A C# 14 extension block on one line (`extension(long v) { public long Half => v / 2; }`) is expanded like a type.
## Compared with StyleCop
Same positions as StyleCop, except a block with a comment after its `}`. For a local function StyleCop reports both SA1501 and SA1502 at the same brace; StyleBro
reports BRO1509 once. StyleCop's fix leaves accessors and nested declarations side by side on one line, misindents
nested ones, and writes Windows line endings into files that use `\n`.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1510/
# BRO1510: Accessors should all be single-line or all multi-line
When every accessor of a property, indexer or event has a block body, they are laid out the same way: each on one
line, or each over several lines. Replaces StyleCop **SA1504**.
## Example
### Before
```csharp
public class Temperature
{
private double celsius;
public double Celsius
{
get { return this.celsius; }
set
{
this.celsius = value;
}
}
}
```
### After
```csharp
public class Temperature
{
private double celsius;
public double Celsius
{
get { return this.celsius; }
set { this.celsius = value; }
}
}
```
## Why
A property whose accessors are written differently looks like the two halves were changed separately, and the eye
has to switch styles in the middle of a declaration.
## How the fix works
Like StyleCop's fix, it goes the short way when it can: if every multi-line accessor holds at most one statement on a
single line, those accessors are put on one line (`set { this.celsius = value; }`). Otherwise the single-line
accessors are expanded, with their braces on their own lines (or kept on the accessor's line when
`csharp_new_line_before_open_brace` doesn't include `accessors`) and the statements one level deeper. A body with a
comment in it is never collapsed. The blank line [BRO1505](BRO1505.md) wants between two block-bodied accessors is
added at the same time, so one `dotnet format` run is enough.
## Not reported
- **An accessor without a block body**, like StyleCop: `get => this.x;` or `get;` next to a multi-line `set { ... }`
is fine.
- **Accessor lists on one line** (`{ get { return x; } set { x = value; } }`): that's [BRO1509](BRO1509.md)'s.
- **An accessor that shares its line with the previous one**, or a comment in a gap the fix rewrites.
An accessor counts as single-line when its keyword (or first modifier) and its body are on one line; an attribute on
its own line above it doesn't count.
## Compared with StyleCop
Same positions as StyleCop, except accessors with an attribute on its own line: StyleCop counts the attribute line, so
it reports `[DebuggerStepThrough]` + `get { return x; }` next to `set { x = value; }` (both on one line) and misses
the same `get` next to a multi-line `set`
([#3434](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3434)). StyleBro does the opposite. StyleCop's fix offers "single line" and "multiple lines" separately, and under
`dotnet format` one of them is applied to the whole project, whichever the first diagnostic offers; it also deletes
comments in a body it collapses. StyleBro decides per property and keeps comments.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1511/
# BRO1511: Element documentation headers should not be followed by blank line
A documentation comment sits directly on the element it documents. Replaces StyleCop **SA1506**.
## Example
### Before
```csharp
public class Order
{
/// Gets the total.
public decimal Total { get; set; }
}
```
### After
```csharp
public class Order
{
/// Gets the total.
public decimal Total { get; set; }
}
```
## Why
A blank line after documentation detaches it from its element; readers can't tell what it belongs to.
## How the fix works
The blank lines between the documentation and the element (or its first attribute) are removed.
## Not reported
- **A comment or directive between the documentation and the element.**
## Compared with StyleCop
Same positions and the same fixed output as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1512/
# BRO1512: While-do footer should not be preceded by blank line
The `while` of a `do ... while` follows the block directly. Replaces StyleCop **SA1511**.
## Example
### Before
```csharp
public class Retry
{
public void Run(int times)
{
do
{
times--;
}
while (times > 0);
}
}
```
### After
```csharp
public class Retry
{
public void Run(int times)
{
do
{
times--;
}
while (times > 0);
}
}
```
## Why
Separated from its block, the `while` reads like the start of a new loop.
## How the fix works
The blank lines before `while` are removed.
## Compared with StyleCop
Same positions and the same fixed output as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1513/
# BRO1513: Element documentation header should be preceded by blank line
A documentation comment is separated from the code above it by a blank line. Replaces StyleCop **SA1514**.
## Example
### Before
```csharp
public class Order
{
private decimal total;
/// Gets the total.
public decimal Total => this.total;
}
```
### After
```csharp
public class Order
{
private decimal total;
/// Gets the total.
public decimal Total => this.total;
}
```
## Why
Without a blank line, documentation looks like it belongs to the code above it.
## How the fix works
A blank line is inserted above the documentation comment. [BRO1505](BRO1505.md) adds the same line in most of these places; the two fixes give the same result.
## Not reported
- **Right after an opening brace** or at the start of the file, like StyleCop.
- **Right below a `//` comment**: the blank line would break [BRO1506](BRO1506.md) (no blank line below a comment), and the two fixes would undo each other. StyleCop reports it.
## Compared with StyleCop
Same positions as StyleCop, except documentation right below a comment.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1514/
# BRO1514: Braces should not be omitted
The child statement of an `if`, `else`, `for`, `foreach`, `while`, `do`, `using`, `lock` or `fixed` statement is a
block. Replaces StyleCop **SA1503**.
## Example
### Before
```csharp
public class Counter
{
public int Clamp(int value)
{
if (value < 0)
return 0;
return value;
}
}
```
### After
```csharp
public class Counter
{
public int Clamp(int value)
{
if (value < 0)
{
return 0;
}
return value;
}
}
```
## Why
A statement added below an `if` without braces looks like part of it but always runs. Braces make the extent of every
condition and loop explicit.
## How the fix works
The braces go at the indentation of the statement that owns them, the child statement one level deeper (from
`indent_style`/`indent_size`). `{` starts its own line unless `csharp_new_line_before_open_brace` leaves out
`control_blocks`; an `else` follows the `}` as `csharp_new_line_before_else` says; a `do` statement's `while` keeps its
line. A trailing comment stays with the statement. Nested statements are all fixed in one pass.
This rule replaces the SDK's IDE0011, whose fix breaks multi-targeted projects under `dotnet format`
(reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000) and [dotnet/roslyn#86001](https://github.com/dotnet/roslyn/issues/86001); see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Configuration
```ini
[*.cs]
# StyleCop's allowConsecutiveUsings, also for 'fixed (a) fixed (b) { }'. false: the inner 'using' or 'fixed' gets its
# own braces.
stylebro_allow_consecutive_usings = true
# Not a StyleCop setting (StyleCop issue #2252). true: a jump statement ('return', 'throw', 'break', 'continue',
# 'goto', 'yield break') on the line of its 'if' needs no braces: 'if (x) return;'. On the next line, or after
# 'else', it still gets braces. Default false.
stylebro_allow_single_line_jump_statements = false
# Not a StyleCop setting. all (default): every statement StyleCop checks. if_and_loops: if/else, for, foreach, while
# and do only, like Sonar's S121: not 'using', 'lock', 'fixed' or a foreach that deconstructs ('foreach (var (a, b) in x)').
# Applies to BRO1515 and BRO1516 too.
stylebro_braces_statements = all
# The SDK's setting, which BRO1514-BRO1516 follow:
# true (or not set): StyleCop's rules, as described here
# when_multiline: only multi-line child statements (BRO1515) and inconsistent if/else chains (BRO1516); "multi-line"
# as the SDK counts it: the statement, its header (a 'for' split over lines) or a do statement's
# 'while' part spans lines
# false: braces aren't required (nothing is reported)
csharp_prefer_braces = true
```
`stylebro-migrate` copies `allowConsecutiveUsings` from `stylecop.json` and writes `csharp_prefer_braces` from the
StyleCop rules that are on: `true` when any of SA1503, SA1519 and SA1520 is (each of BRO1514-BRO1516 is then on where
its StyleCop rule was, so exactly StyleCop's statements are reported), `false` when none is. Not `when_multiline` for
SA1519/SA1520 alone: its "multi-line" also counts a split `if` header, which SA1519 accepts
([#1488](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1488)). A `csharp_prefer_braces` the repository sets
at severity `silent` or `none` isn't kept: nothing enforced it.
`stylebro_allow_single_line_jump_statements` narrows `csharp_prefer_braces = true`. With `when_multiline`, every
single-line child statement is already allowed, so the option changes nothing there; with `false` nothing is reported
anyway. The option only exempts statements from BRO1514: when another clause of the same `if`/`else` chain has or gets
braces, [BRO1516](BRO1516.md) still asks for them on the jump too (`if (x) return; else { ... }` becomes fully braced
in one run). `stylebro-migrate` doesn't write it: `stylecop.json` has no such setting.
`stylebro_braces_statements = if_and_loops` is what `stylebro-migrate` writes for a repository that enforces Sonar's S121
(with `csharp_prefer_braces = true`), so BRO1514 reports nothing S121 accepts. When the StyleCop setup turns BRO1514 on
too (SA1503), StyleCop's statements stay checked and the option isn't written.
## Not reported
- **A multi-line child statement** when [BRO1515](BRO1515.md) is on, and **a clause of an `if`/`else` chain where
another clause has braces** when [BRO1516](BRO1516.md) is on: those rules report them (like StyleCop).
- **`if (x) return;`** (a jump statement on the `if` line) with `stylebro_allow_single_line_jump_statements = true`.
- **`using`, `lock`, `fixed` and deconstructing `foreach` statements** with `stylebro_braces_statements = if_and_loops`
(an `if` whose child is one still gets braces around it).
- **`using (a) using (b) { }`** and **`fixed (a) fixed (b) { }`**: consecutive `using` statements, and consecutive
`fixed` statements, share one block, unless `stylebro_allow_consecutive_usings = false` (see Configuration). StyleCop
only allows `using`s; the SDK's IDE0011 allows both.
- **Comments or directives where the braces would go**, `#if` in the owning statement, a multi-line string in the
statement, or other code after it on its line: the fix can't place the braces safely.
- **A statement in a one-line block** (`void M(bool x) { if (x) return; }`) when [BRO1508](BRO1508.md)/
[BRO1509](BRO1509.md) is on: expanding the block adds the braces too.
## Compared with StyleCop
Same positions and output, apart from the cases above that are skipped.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1515/
# BRO1515: Braces should not be omitted from multi-line child statement
A child statement that spans several lines is a block. Replaces StyleCop **SA1519**.
## Example
### Before
```csharp
public class Mailer
{
public void Send(bool urgent, string to)
{
if (urgent)
Deliver(to,
priority: 1);
}
private static void Deliver(string to, int priority)
{
}
}
```
### After
```csharp
public class Mailer
{
public void Send(bool urgent, string to)
{
if (urgent)
{
Deliver(to,
priority: 1);
}
}
private static void Deliver(string to, int priority)
{
}
}
```
## Why
Without braces, a statement continued over several lines is easy to misread as several statements, only the first of
which belongs to the condition.
## How the fix works
Like [BRO1514](BRO1514.md); the statement's continuation lines move with it, so their relative indentation stays.
## Not reported
The same cases as [BRO1514](BRO1514.md).
## Compared with StyleCop
Same positions and output, apart from the skipped cases.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1516/
# BRO1516: Use braces consistently
When one clause of an `if`/`else` chain has braces, all of them do. Replaces StyleCop **SA1520**.
## Example
### Before
```csharp
public class Tariff
{
public decimal Rate(int zone)
{
if (zone == 1)
{
return 0.5m;
}
else
return 1m;
}
}
```
### After
```csharp
public class Tariff
{
public decimal Rate(int zone)
{
if (zone == 1)
{
return 0.5m;
}
else
{
return 1m;
}
}
}
```
## Why
A chain that mixes clauses with and without braces is harder to scan, and the clause without braces is the one that
gets a second statement by mistake.
## How the fix works
Like [BRO1514](BRO1514.md).
## Not reported
- **A multi-line clause** when [BRO1515](BRO1515.md) is on: that rule reports it (like StyleCop).
- The cases [BRO1514](BRO1514.md) skips.
## Compared with StyleCop
Same positions and output, apart from the skipped cases.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1517/
# BRO1517: Code should not contain multiple blank lines in a row
Two or more blank lines in a row become one. Replaces StyleCop **SA1507**.
## Example
### Before
```csharp
public class Report
{
public int Total(int a, int b)
{
var sum = a + b;
return sum;
}
}
```
### After
```csharp
public class Report
{
public int Total(int a, int b)
{
var sum = a + b;
return sum;
}
}
```
## Why
One blank line separates; more add scrolling without saying anything more.
## How the fix works
The first blank line stays, the others are removed, in every run of blank lines at once (also several runs between
directives, like empty `#region`s in a row). Blank lines inside strings and in disabled `#if` code aren't touched.
This rule replaces the SDK's IDE2000, which writes merge conflict markers in multi-targeted projects under
`dotnet format` and also counts blank lines at the start of a file and before `}` (see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Not reported
- **At the start or end of the file**: the end is [BRO1507](BRO1507.md)'s.
- **Right after `{`**, **right before `}`** and **below a `//` comment** when [BRO1503](BRO1503.md),
[BRO1518](BRO1518.md) or [BRO1506](BRO1506.md) is on: those rules remove all of them there. Blank lines between
two comments are BRO1517's again (BRO1506 allows a blank line above a comment).
## Compared with StyleCop
Same positions and output, apart from the places left to the rules above.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1518/
# BRO1518: Closing braces should not be preceded by blank line
A closing brace directly follows the code above it. Replaces StyleCop **SA1508**.
## Example
### Before
```csharp
public class Inventory
{
public int Count(int[] items)
{
return items.Length;
}
}
```
### After
```csharp
public class Inventory
{
public int Count(int[] items)
{
return items.Length;
}
}
```
## Why
A blank line before `}` separates nothing from nothing, and makes the end of the block harder to see.
## How the fix works
The blank lines between the last line of code (or comment) and the `}` are removed.
This rule replaces the SDK's IDE2002, which writes merge conflict markers in multi-targeted projects under
`dotnet format` (see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Not reported
- **Below a `//` comment on its own line** when [BRO1506](BRO1506.md) is on (below a trailing comment, `x(); // note`,
they are reported: BRO1506 skips those), and **in an empty block** (`{`, blank lines, `}`) when
[BRO1503](BRO1503.md) is on: those rules remove the same lines.
## Compared with StyleCop
Same positions and output, apart from the places left to the rules above.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1519/
# BRO1519: Closing brace should be followed by blank line
A statement after a block is separated from it by a blank line. Replaces StyleCop **SA1513**.
## Example
### Before
```csharp
public class Ledger
{
public int Sum(int[] values)
{
var total = 0;
foreach (var value in values)
{
total += value;
}
return total;
}
}
```
### After
```csharp
public class Ledger
{
public int Sum(int[] values)
{
var total = 0;
foreach (var value in values)
{
total += value;
}
return total;
}
}
```
## Why
The blank line marks where the block's work ends and the next step begins.
## How the fix works
A blank line is inserted after the `}`. Fixes that create a `}` followed by a statement ([BRO1514](BRO1514.md) adding
braces, [BRO1508](BRO1508.md)/[BRO1509](BRO1509.md) expanding a one-line block) add this blank line themselves when
this rule is on, so one `dotnet format` run is enough.
This rule replaces the SDK's IDE2003, which writes merge conflict markers in multi-targeted projects under
`dotnet format` (see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## Not reported
StyleCop's exceptions: what follows is `}`, `else`, `catch`, `finally`, a do statement's `while`, `)`, `,`, `;` ending
an initializer, `return` or `throw`, `.`, `:`, `=>`, `&&`, `||`, `]`, an accessor, a query clause, a `////` comment or a
directive, a property initializer's `=`; the braces are on one line; the `}` closes an interpolation; it is the last `}`
in the file. Also:
- **An empty statement's `;` after the `}`** (`};`) when [BRO1101](BRO1101.md) is on: it removes the `;`, and the
blank line would be left in front of what follows.
- **A `}` inside an expression or pattern followed by a token that continues it**: `?`, `??`, `?.`, a binary operator,
`switch`, `with`, `is`, `as`, `when`, a designation. Such a `}` doesn't end a block (StyleCop reports these; CSharpier
removes the blank line again).
- **A comment, documentation or a member after the `}`**: [BRO1504](BRO1504.md), [BRO1513](BRO1513.md) and
[BRO1505](BRO1505.md) ask for that blank line (when they're on), so two fixes don't add it twice.
- **Code after the `}` on its line**, or a comment after it: there's no line to put the blank line after.
- **A switch section after the `}`** when `stylebro_blank_line_between_switch_sections` ([BRO1526](BRO1526.md)) wants no
blank line there (`omit`, or `omit_after_block` after a section ending in a block).
## Compared with StyleCop
Same positions and output, apart from the places left to the rules above and a `}` followed by a token that
continues its expression or pattern (not reported).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1520/
# BRO1520: Place the operator consistently when an expression wraps
When the line breaks next to a binary operator (`&&`, `||`, `+`, `??`, `==`, ...) or the conditional operator's `?` or
`:`, the operator goes at the beginning of the continuation line (the default) or at the end of the broken line. Not a
StyleCop rule; it follows the SDK's `dotnet_style_operator_placement_when_wrapping`, which no SDK rule enforces.
## Example
### Before
```csharp
public class Order
{
public bool IsReady(bool paid, bool packed, bool cancelled) =>
paid &&
packed &&
!cancelled;
public string Label(bool express, string name) =>
express ?
name + " (express)" :
name;
}
```
### After
```csharp
public class Order
{
public bool IsReady(bool paid, bool packed, bool cancelled) =>
paid
&& packed
&& !cancelled;
public string Label(bool express, string name) =>
express
? name + " (express)"
: name;
}
```
## Why
With the operator first, the start of each continuation line says how it joins the expression; a reader scanning the
left edge sees `&&` against `||` without reading to the end of the line above. It's also the SDK's default for the
setting, and what the "wrap expression" refactorings in Visual Studio produce.
## How the fix works
Only the operator moves: it goes to the other side of the line break with one space between it and the code on its
line. The line break, blank lines and the continuation line's indentation stay as they are, and trailing whitespace on
the broken line goes.
## Configuration
```ini
[*.cs]
# The SDK's setting: beginning_of_line (default) or end_of_line.
dotnet_style_operator_placement_when_wrapping = beginning_of_line
```
The SDK documents the key for binary operators (its own wrapping refactorings use it there); StyleBro applies it to the
conditional operator's `?` and `:` as well, so one key decides where every operator of a wrapped expression goes (the
owner's [decision](https://github.com/bisforboman/stylebro/blob/main/docs/decisions/2026-10-15-bro1520-keeps-applying-the-sdk-operator-placement-key-to.md)).
## Not reported
- **A comment or a directive next to the operator** (`a && // why`, `#if` between the operands): moving the operator
would move it across the comment or out of the `#if` branch.
- **An operator alone on its line** (line breaks on both sides): neither placement applies.
- **A query right after the operator** (`... ??` + `from x in ...`): the query layout rules ([BRO1127](BRO1127.md)-[BRO1130](BRO1130.md)) indent its clauses by whether `from` starts its line, so they would re-indent the query.
- **Pattern combinators** (`and`, `or` in patterns) and the `=` of assignments ([BRO1522](BRO1522.md)).
- **Code with syntax errors** around the operator.
## Compared with StyleCop
StyleCop has no such rule. Roslynator's RCS0027 (binary operators) and RCS0028 (`?`/`:`) do the same, off by default
and with their own option (`roslynator_binary_operator_new_line`, `roslynator_conditional_operator_new_line`).
`stylebro-migrate` turns this rule off, so migrating doesn't change a StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1521/
# BRO1521: Place '=>' consistently when an expression body wraps
When the line breaks next to the `=>` of an expression-bodied member (method, property, accessor, operator, local
function, ...) or a switch expression arm, the `=>` goes at the end of the broken line (the default) or at the
beginning of the continuation line. Not a StyleCop rule.
## Example
### Before
```csharp
public class Temperature
{
private readonly double celsius = 21.5;
public double Fahrenheit
=> (celsius * 9 / 5) + 32;
public string Describe()
=> celsius switch
{
< 0 => "freezing",
_ => "fine",
};
}
```
### After
```csharp
public class Temperature
{
private readonly double celsius = 21.5;
public double Fahrenheit =>
(celsius * 9 / 5) + 32;
public string Describe() =>
celsius switch
{
< 0 => "freezing",
_ => "fine",
};
}
```
## Why
One place for the `=>` in the whole codebase. The default puts it where lambdas have it: at the end of the line, so
every `=>` follows one convention.
The reference repositories StyleBro is tested on (2026-10-04) are split for expression bodies: four put `=>` at the
beginning of the line (OpenTelemetry 676 to 335, Jellyfin 398 to 14, FluentValidation 52 to 3, Serilog 2 to 0) and
three at the end (Polly 543 to 130, FFMpegCore 3 to 0, CsvHelper 2 to 0); Newtonsoft.Json has one of each (1,259 at
the beginning and 901 at the end in all). Lambdas are not split: 5,299 of 5,308 have `=>` at the end of the line.
Switch arms broken at `=>` are rare (8 at the beginning, 14 at the end) and follow the same setting.
## How the fix works
Only the `=>` moves: it goes to the other side of the line break with one space between it and the code on its line.
The line break and the continuation line's indentation stay as they are.
## Configuration
```ini
[*.cs]
# end_of_line (default) or beginning_of_line.
stylebro_arrow_placement_when_wrapping = end_of_line
```
## Not reported
- **Lambdas** (`x =>` followed by the body on the next line): a block body's `{` goes on its own line after the `=>`,
and lambdas keep `=>` at the end of the line almost everywhere (5,299 of 5,308 in the reference repositories, block
bodies included); they're left alone.
- **An `=>` right after a `where` constraint**: [BRO1111](BRO1111.md) places it on its own line with the body.
- **A query as the body** (`=>` + `from x in ...`): the query layout rules ([BRO1127](BRO1127.md)-[BRO1130](BRO1130.md)) indent its clauses by whether `from` starts its line, so they would re-indent the query.
- **A comment or a directive next to the `=>`**, an `=>` alone on its line, and code with syntax errors.
## Compared with StyleCop
StyleCop has no such rule. Roslynator's RCS0032 does the same, off by default and with its own option
(`roslynator_arrow_token_new_line`). `stylebro-migrate` turns this rule off, so migrating doesn't change a
StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1522/
# BRO1522: Place '=' consistently when an assignment wraps
When the line breaks next to the `=` of an assignment (also compound ones like `+=` and `??=`) or an initializer
(fields, properties, locals, parameter defaults, enum members), the `=` goes at the end of the broken line (the default)
or at the beginning of the continuation line. Not a StyleCop rule.
## Example
### Before
```csharp
public class Shipping
{
private readonly decimal baseRate
= 4.95m;
public decimal Cost(decimal weight)
{
var cost
= baseRate;
cost
+= weight * 0.5m;
return cost;
}
}
```
### After
```csharp
public class Shipping
{
private readonly decimal baseRate =
4.95m;
public decimal Cost(decimal weight)
{
var cost =
baseRate;
cost +=
weight * 0.5m;
return cost;
}
}
```
## Why
One place for the `=` in the whole codebase. The end of the line is what C# code does almost everywhere: in the
reference repositories StyleBro is tested on (2026-10-04), 518 of the 524 assignments and initializers that break next
to `=` have it at the end of the line (all eight repositories agree).
## How the fix works
Only the `=` moves: it goes to the other side of the line break with one space between it and the code on its line.
The line break and the continuation line's indentation stay as they are.
## Configuration
```ini
[*.cs]
# end_of_line (default) or beginning_of_line.
stylebro_equals_placement_when_wrapping = end_of_line
```
## Not reported
- **A `{` or `[` after the `=`** (an array initializer or collection expression on its own lines):
`int[] a =` + `{ 1, 2 }` is brace layout, not wrapping.
- **A query after the `=`** (`var q =` + `from x in ...`): the query layout rules ([BRO1127](BRO1127.md)-[BRO1130](BRO1130.md)) indent its clauses by whether `from` starts its line, so they would re-indent the query.
- **Named arguments and members** (`[Attr(Name =` ..., `new { X =` ...), `using` aliases.
- **A comment or a directive next to the `=`**, an `=` alone on its line, and code with syntax errors.
## Compared with StyleCop
StyleCop has no such rule. Roslynator's RCS0052 does the same, off by default and with its own option
(`roslynator_equals_token_new_line`). `stylebro-migrate` turns this rule off, so migrating doesn't change a
StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1523/
# BRO1523: Each call of a split call chain starts its own line
When a member access chain is split over several lines (a `.` or `?.` of the chain starts a line), every call after the
chain's first line starts its own line. Not a StyleCop rule; Roslynator's RCS0054 checks the same.
## Example
### Before
```csharp
using System.Collections.Generic;
using System.Linq;
public class Directory
{
public List ActiveNames(IEnumerable people) =>
people
.Where(p => p.Length > 0).Select(p => p.Trim())
.Distinct().OrderBy(p => p).ToList();
}
```
### After
```csharp
using System.Collections.Generic;
using System.Linq;
public class Directory
{
public List ActiveNames(IEnumerable people) =>
people
.Where(p => p.Length > 0)
.Select(p => p.Trim())
.Distinct()
.OrderBy(p => p)
.ToList();
}
```
## Why
A chain someone has already split reads as a list of steps, one per line; a step tacked onto the end of another line is
easy to miss when scanning the left edge. In the eight reference repositories, 99.6% of the split chains (20,880 of
20,959) already follow the rule (see [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)).
## What counts
- **A chain is split** when one of its `.` or `?.` starts a line. A chain on one line is fine, and so is one where only
the arguments span lines (`Task.Run(() => { ... }).ConfigureAwait(false)`).
- **The chain's first line** may hold the receiver and one call: `services.AddLogging()` + newline + `.AddOptions()` is
checked from the second line on. Only calls below the first line are checked.
- **A chain whose first line holds two or more calls** wraps by filling lines and isn't checked:
`sb.Append(id).Append(name)` + newline + `.Append(text).Append(end)` stays as it is. The receiver's own call counts
(`RuleFor(x => x.Name).NotNull()` + newline + `.WithMessage(...)`).
- **A call** counts from the member right after the previous call: `.WriteTo.Sink(x)` and `.Items[0].Trim()` are one
step each and stay together, `.Result` or `.Length` after the last call stays where it is, and the first member after
the receiver (`new Config { ... }.Apply()`) isn't checked.
- `?.` counts like `.`, `x!` and `x[0]` after a call belong to that call.
## How the fix works
Only the gap before the `.` (or `?.`) changes: it becomes a line break plus the indentation of the chain's lines that
already start with `.`, the nearest one above (or else below). The rule doesn't check indentation, so a chain aligned
under its first `.` stays aligned. Arguments and lambda bodies aren't touched; a multi-line step moves only when its
other lines are already at least as deep as its new line (a lambda body under the step), otherwise it's skipped.
## Not reported
- **A comment in the gap** (`.Where(x) /* why */ .Select(y)`) or **a directive anywhere in the chain** (`#if` between
steps or inside an argument). `#region`/`#endregion` lines don't count (also not for a multi-line step's
indentation): the chain is judged the same as after BRO1112/BRO1113 remove them, so one run is enough.
- **A multi-line step whose other lines are less indented** than its new line would be: the fix would leave its lambda
body to the left of the call.
- **Chains whose first line holds two or more calls** (see What counts).
- **Chains inside an interpolated string's hole** (a line break there needs C# 11) and code with syntax errors.
## Compared with StyleCop
StyleCop has no such rule. Roslynator's RCS0054 (off by default) checks the same breaks with four differences: it
checks chains whose first line holds several calls (below that line), it also
splits a property step (`.WriteTo` + newline + `.Sink(x)`) and a trailing property, it reports chains where only the
arguments span lines, and it enforces one indentation unit deeper than the chain's first line (StyleBro keeps the
chain's own indentation). `stylebro-migrate` turns this rule off, so migrating doesn't change a StyleCop-clean
repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1524/
# BRO1524: A split conditional expression has the condition, '?' and ':' parts on their own lines
A conditional expression (`c ? a : b`) is on one line, or the condition, the `? a` part and the `: b` part each start
their own line. Like [BRO1108](BRO1108.md) for argument lists: an expression on one line is never split, but once the
line breaks next to `?` or `:`, the other one gets a line break too. Which side of the token the line break goes on
follows [BRO1520](BRO1520.md)'s setting, `dotnet_style_operator_placement_when_wrapping`. Not a StyleCop rule: proposed
in StyleCop's issue [#651](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/651), which the maintainers
accepted and nobody built.
## Example
### Before
```csharp
public class Order
{
public string Label(bool express, string name)
{
var label = express ? name + " (express)"
: name;
return label;
}
public decimal Shipping(bool express, decimal weight) =>
express
? 10m : weight * 0.5m;
}
```
### After
```csharp
public class Order
{
public string Label(bool express, string name)
{
var label = express
? name + " (express)"
: name;
return label;
}
public decimal Shipping(bool express, decimal weight) =>
express
? 10m
: weight * 0.5m;
}
```
## Why
Each part on its own line shows at a glance where the condition ends and which value is which; a `:` hidden in the
middle of a line after a long `? a` part is easy to miss. It's the layout most code already uses: in eight open-source
repositories, 672 of the 719 split conditionals had every part on its own line (631 with `?`/`:` at the beginning of the
line, 41 at the end).
## How the fix works
It inserts a line break before the `?` or `:` that shares its line with both neighbors (after it with
`dotnet_style_operator_placement_when_wrapping = end_of_line`). The new line is indented like the first part of the
expression that already starts a line. A `?` or `:` that has a line break on the wrong side is [BRO1520](BRO1520.md)'s;
with both rules on, one `dotnet format` run gives the layout above.
## Configuration
```ini
[*.cs]
# BRO1520's setting: beginning_of_line (default) or end_of_line.
dotnet_style_operator_placement_when_wrapping = beginning_of_line
```
## Not reported
- **An expression on one line**, also when a part spans lines (a lambda with a block body, a wrapped argument list):
only line breaks right next to `?` and `:` make it split.
- **Chains** (`a ? x : b ? y : z`, a conditional in another one's `: b` part). Teams lay these out as a list (each
`condition ? value :` on its own line, or each `: condition ? value` on its own line); checking each link on its own
would turn the list into a staircase. A switch expression is usually the clearer form. Conditionals nested elsewhere
(in the condition or in `? a`, usually in parentheses) are checked on their own.
- **A comment or a directive next to `?` or `:`**.
- **A `? a` or `: b` part that spans lines** and would move to a new line: its other lines would need re-indenting.
- **Code with syntax errors** in the expression.
## Compared with StyleCop
StyleCop has no such rule; it was proposed as a readability rule in
[#651](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/651) (with `?` and `:` at the beginning of the line)
and never implemented. Chains were the open question there; StyleBro leaves them alone. `stylebro-migrate` turns this
rule off, so migrating doesn't change a StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1525/
# BRO1525: Attributes should not be followed by a blank line
No blank line between an attribute list and the element it applies to, or between two attribute lists of the same
element. Not a StyleCop rule: StyleCop proposed it as SA1521 (AttributesMustNotBeFollowedByBlankLine, issue
[#738](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/738)) and never implemented it.
## Example
### Before
```csharp
using System;
public class Order
{
public void Ship()
{
}
[Obsolete("Use Ship.")]
public void Send()
{
}
public int Count
{
[Obsolete("Use Size.")]
get => 0;
}
}
```
### After
```csharp
using System;
public class Order
{
public void Ship()
{
}
[Obsolete("Use Ship.")]
public void Send()
{
}
public int Count
{
[Obsolete("Use Size.")]
get => 0;
}
}
```
## Why
An attribute is part of the declaration below it. A blank line after it makes it look like it belongs to nothing, or
to the code above; [BRO1511](BRO1511.md) asks the same of documentation comments.
## How the fix works
It removes the blank lines between the attribute list and the next line of code. [BRO1505](BRO1505.md) (elements
separated by a blank line) doesn't count those lines, since an element's code starts at its first attribute: a member
whose only blank line sits below its attribute gets BRO1505's blank line above the attribute, in the same
`dotnet format` run.
## Not reported
- **A comment, documentation comment or directive** between the attribute and the element (`[Theory]`, a blank line, a
`// ...` comment): [BRO1504](BRO1504.md) wants a blank line above a comment that follows code, and an `#if` changes
which element the attribute applies to.
- **Assembly and module attributes** (`[assembly: ...]`): they apply to no element below them.
- **Code with syntax errors** around the element.
## Compared with StyleCop
StyleCop has no such rule. Its maintainers gave it the id SA1521 in issue
[#738](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/738) ("up for grabs" since 2015), but it was never
implemented and has no documentation page. `stylebro-migrate` turns this rule off, so migrating doesn't change a
StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1526/
# BRO1526: Blank line between switch sections
The sections of a `switch` statement are separated by a blank line, or (if you configure it) not. Not a StyleCop rule:
from Roslynator's [RCS0061](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0061).
## Example
### Before
```csharp
public static class Shipping
{
public static decimal Cost(string zone, decimal weight)
{
switch (zone)
{
case "local":
return 5m;
case "national":
case "island":
return 5m + weight;
default:
return 20m + (2 * weight);
}
}
}
```
### After
```csharp
public static class Shipping
{
public static decimal Cost(string zone, decimal weight)
{
switch (zone)
{
case "local":
return 5m;
case "national":
case "island":
return 5m + weight;
default:
return 20m + (2 * weight);
}
}
}
```
## Why
A long `switch` reads like a list of small blocks. A blank line between them shows where one case ends and the next
begins, the way [BRO1505](BRO1505.md) separates members. Some teams prefer compact sections; the option is for them.
## Configuration
```ini
[*.cs]
stylebro_blank_line_between_switch_sections = include
```
- `include` (default): one blank line between sections.
- `omit`: no blank line between sections.
- `omit_after_block`: a blank line, except after a section whose last statement is a block (`case 1: { ... }`).
Labels of one section (`case 2:` and `case 3:` above) belong together and are never separated. Several blank lines
between sections are [BRO1517](BRO1517.md)'s, which leaves one.
## How the fix works
It adds a line break before the next section's first label, or removes the blank lines there.
**After a multi-line block** (a section that ends in `{ ... }`, or in an `if`/`else` whose last `}` closes the section)
the setting decides too: where it wants no blank line (`omit`, or `omit_after_block` after a `{ ... }` section),
[BRO1519](BRO1519.md) doesn't ask for one after the `}`, and [BRO1504](BRO1504.md) doesn't ask for one above a comment
that starts the next section. Where it wants one (`include`), BRO1519 wants the same and this rule leaves that gap to it.
## Not reported
- **A comment or directive between the sections**: the blank line may belong to the comment ([BRO1504](BRO1504.md)), and
an `#if` can hide sections. A trailing comment after the previous section's last statement is fine.
- **Sections on one line** (`case 1: return 1; case 2: return 2;`).
- **Switch expressions**: their arms aren't sections.
- **Code with syntax errors** in the `switch`.
## Compared with StyleCop
StyleCop has no such rule; its SA1513 (StyleBro's BRO1519) only asks for a blank line after a `}`. `stylebro-migrate`
turns this rule off, so migrating doesn't change a StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1527/
# BRO1527: Auto-accessors should be on one line
A property or indexer whose accessors are all auto-accessors (`get;`, `set;`, `init;`, also with a modifier such as
`private set;`) goes on one line: `public int Simple { get; set; }`. Not a StyleCop rule: from Roslynator's
[RCS0042](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0042).
## Example
### Before
```csharp
public class Person
{
public string Name
{
get;
set;
}
public int Age
{
get;
private set;
} = 18;
}
```
### After
```csharp
public class Person
{
public string Name { get; set; }
public int Age { get; private set; } = 18;
}
```
## Why
The one-line form is how auto-properties are written almost everywhere; spread over five lines they look like a
property with logic. The SDK's IDE0360 (`get { return field; }` -> `get;`, in `stylebro-migrate init --modernize`)
leaves the accessors on their old lines; this rule finishes the job.
## How the fix works
The whitespace from the declaration's name to the `{`, between the accessors and before the `}` becomes one space each.
An initializer (`= 18;`) stays after the `}`. While this rule is on, [BRO1505](BRO1505.md) (and
[BRO1001](BRO1001.md)'s sort, which adds the blank lines BRO1505 wants) already treat a property this fix will put on one
line as a one-line property, so two such properties may sit together whichever fix `dotnet format` runs first. When
BRO1505 is off, the fix adds the blank line [BRO1519](BRO1519.md) wants after the multi-line property's `}`, for the same
reason.
IDE0360's fix runs in `dotnet format`'s code-style pass, before the analyzer pass with this rule, so a property it
shortens is put on one line in the same run, and IDE0360 doesn't touch the result. They don't fight.
## Not reported
- An accessor with a body (`get { ... }`), an expression body (`get => x;`) or an attribute (`[Obsolete] get;`).
- A comment or directive anywhere from the declaration's name to the `}`, or a line break inside an accessor.
- An initializer that doesn't start on the `}` line.
- A declaration whose modifiers, type and name span several lines (a generic type split over lines, as CSharpier
writes it): the SDK's formatter (IDE0055) puts the `{` on a line of its own then, and the two would undo each other.
- `csharp_preserve_single_line_blocks = false` in `.editorconfig`: the SDK's formatter (IDE0055) would expand the list
again. The preset sets it to `true`.
A long line isn't split or avoided: the one-line form is as long as the declaration plus the accessors.
## Compared with StyleCop
StyleCop has no such rule (SA1502 allows `{ get; set; }` on one line but doesn't ask for it). `stylebro-migrate` turns
it off (see [Rules beyond StyleCop](../differences-from-stylecop.md#rules-beyond-stylecop)).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1601/
# BRO1601: Overrides and implementations should inherit their documentation
An override or interface implementation without documentation gets `/// `, which takes the
documentation from the member it overrides or implements. Replaces StyleCop **SA1600** for these members.
## Example
### Before
```csharp
using System;
/// Something that has a size.
public interface ISized
{
/// Gets the size in bytes.
long Size { get; }
}
/// A file on disk.
public class DiskFile : ISized, IDisposable
{
public long Size => 0;
public void Dispose()
{
}
public override string ToString() => "file";
}
```
### After
```csharp
using System;
/// Something that has a size.
public interface ISized
{
/// Gets the size in bytes.
long Size { get; }
}
/// A file on disk.
public class DiskFile : ISized, IDisposable
{
///
public long Size => 0;
///
public void Dispose()
{
}
///
public override string ToString() => "file";
}
```
## Why
The documentation of an override or implementation is usually the same as its base's. `` says so
explicitly, and IntelliSense and documentation generators show the inherited text.
**Other missing documentation isn't reported.** StyleCop's SA1600 reports every undocumented element, but the only
automatic fix for a method or class without documentation is placeholder text, which satisfies the rule without
documenting anything. StyleBro only reports what it can fix meaningfully.
## How the fix works
`/// ` is inserted on its own line right before the member, above its attributes, at the member's
indentation. A plain `//` comment above the member stays above it; blank lines between that comment and the member go too when
[BRO1506](BRO1506.md) is on (it would remove them, but can't see them once the `///` is in between). When code sits on the line above, a blank line goes
in between ([BRO1513](BRO1513.md)); when a property sits right below a documented property, a blank line goes there too
([BRO1505](BRO1505.md) lets undocumented single-line properties sit together, documented ones not).
Checked: methods, properties, indexers and events that override a member or implicitly implement an interface member.
Explicit interface implementations (`void IDisposable.Dispose()`) aren't: they're only reachable through the interface,
whose documentation tools show (like StyleCop's unreleased `master`).
## Not reported
- **Projects that don't generate documentation** (no `true`),
like StyleCop's documentation rules, which say so with SA0001 and stay quiet. The package's build targets pass the
property to the analyzer, so a build and `dotnet format` agree (`dotnet format` parses documentation comments even
where the build doesn't). Without the package's targets (the analyzer referenced directly), the compiler's
documentation mode decides.
- **A member with `#if`, `#elif`, `#else` or `#endif` above it or between its attributes.** In a multi-targeted
project the member can start with a different token in each target framework (`[Obsolete]` only under
`#if NET5_0_OR_GREATER`), each copy wanted `` in another place, and [BRO1602](BRO1602.md) turned the
other copy's into `//`: `dotnet format` never converged. Other directives (`#region`, `#pragma`) are fine.
- **Explicit interface implementations** (see How the fix works).
## Configuration
Which members need documentation, like StyleCop's `documentationRules` in `stylecop.json`. A member counts by its
effective accessibility: a public member of an internal class is internal, of a private class private; protected
members are exposed.
```ini
[*.cs]
# Visible outside the assembly (default true)
stylebro_document_exposed_elements = true
# Visible only inside the assembly (default true)
stylebro_document_internal_elements = true
# Private, or in a private type (default false)
stylebro_document_private_elements = false
```
How the inserted tag is written (existing tags are never rewritten):
```ini
[*.cs]
# compact (default): ///
# spaced: ///
stylebro_inheritdoc_style = compact
```
With `spaced`:
```csharp
///
public override string ToString() => "file";
```
## Compared with StyleCop
Same positions as StyleCop 1.2.0-beta.556 for overrides and implicit implementations. Explicit interface
implementations (`void IDisposable.Dispose()`) aren't reported, like in StyleCop's `master` (unreleased, 2959cac8; the
owner's decision, see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)); beta.556 reports them. A member with a conditional directive
above it or between its attributes isn't reported (StyleCop reports it). StyleCop's own SA1600 fix has no Fix All, so
`dotnet format` doesn't apply it.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1602/
# BRO1602: Single-line comments should not use documentation style slashes
A comment that doesn't document a type or member uses `//`, not `///`. Replaces StyleCop **SA1626**.
## Example
### Before
```csharp
public class Totals
{
public int Sum(int[] values)
{
/// Start from zero.
var sum = 0;
foreach (var value in values)
{
sum += value;
}
return sum;
}
}
```
### After
```csharp
public class Totals
{
public int Sum(int[] values)
{
// Start from zero.
var sum = 0;
foreach (var value in values)
{
sum += value;
}
return sum;
}
}
```
## Why
`///` starts XML documentation. Inside a method it documents nothing, and the compiler warns about it (CS1587) when
the project generates documentation.
## How the fix works
Each line's `///` becomes `//`. When text follows the slashes directly (`///note`), a space is added (`// note`), so
the comment also satisfies [BRO1002](BRO1002.md). `////` (commented-out code) isn't a documentation comment and
isn't reported.
## Compared with StyleCop
Same results as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1603/
# BRO1603: Documentation text should end with a period
Summary, remarks, parameter, type parameter, return value, value and exception text are sentences and end with a
period. Replaces StyleCop **SA1629**.
## Example
### Before
```csharp
public class Prices
{
/// Applies a discount
/// The full price
/// The discounted price, see
public decimal Discount(decimal price) => price * 0.9m;
}
```
### After
```csharp
public class Prices
{
/// Applies a discount.
/// The full price.
/// The discounted price, see .
public decimal Discount(decimal price) => price * 0.9m;
}
```
## Why
Consistent punctuation makes generated documentation and IntelliSense read as prose, and it's the convention in the
.NET documentation.
## How the fix works
A period is added right after the last text, or after an inline element at the end (``, ``). A
trailing space stays after the period.
This rule reads documentation as XML, which the compiler only does when the project generates documentation
(`true`), like StyleCop (its SA0001 says so).
## Not reported
- **Text that ends with `?`, `!` or `:`.** A question, an exclamation or an introduction is a finished sentence.
- **Text that ends with `,` or `;`** (`1033 => "en",`, `value;`): a line of code or a list that goes on.
- **A period followed only by closing punctuation**: `(see above.)`, `"done."`, `'done.'`, `[note.]`.
- **Text that ends with a block element**, like a list or a code sample.
- **Empty elements.**
- **Text that is only a number** (`0` on an enum member, `0x1F`): a value, not a sentence.
- **Text whose last word ends in a period and digits** (`Supports HTML 5.0`): a period after it reads as part of the
number.
- **Text that ends with an inline element whose own text ends a sentence**:
`The name of the request.`, `is it done?`. In ``, which holds
code, only a quoted sentence counts (`"User logged in."`); `Calls Done.` gets its period.
- **Events and finalizers**: StyleCop's SA1629 doesn't check their documentation, and neither does this rule.
- **Text that ends in code**: its last word contains a bracket, brace, `=`, `;`, `:`, a slash, `|`, `$`, `#`, `_` or a
backtick (`digraph {`, `Example: A1:B10`, `Run(x)`, a URL). A period would read as part of the code. A closing `)`
without its `(` in the word ends a parenthetical and still gets the period (`(in ms).`). A quoted literal at the
end that reads as code is code too: followed by `;`, with an escape, or a punctuation char
(`Default is ',';`, `Usually "\r\n"`, `The separator ','`); a quoted word (`the word "done"`) still gets its period.
- **Elements StyleCop doesn't know are passed over, like StyleCop:** the text before them counts. Text that ends with
`...` (vs-threading) is checked where the sentence before it ends, and gets no period after
the element. The period goes after an inline element: ``, ``, ``, ``, and HTML's
``, ``, ``, ``, ``, ``, ``, ``, ``, `` (StyleCop passes over those too and
puts the period before them).
- **Tags named in `stylebro_exclude_from_punctuation_check`** (see Configuration).
- **Documentation whose XML is malformed**: `IComparable` in prose opens an element `T` that takes the summary's end
tag, and a bare `<` (`when a < b`) opens an element that never closes, so where the text ends is a guess. A bare `&`
that starts no entity (`this & that`) is malformed too (the compiler reports CS1570). Write `a < b`, `&`,
`IComparable<T>` or ``, then the
rule checks it again. The other rules that edit documentation (BRO1604-BRO1614, BRO1616-BRO1619) skip it too.
Text that ends in an XML entity (`List<T>`) gets the period after the entity. Spaces between the text and a
closing tag on the same line make way for the period: `evaluates to true ` becomes
`evaluates to true.`, not `true. `.
## Configuration
```ini
[*.cs]
# Tags whose text isn't checked, comma-separated (default: seealso)
stylebro_exclude_from_punctuation_check = seealso, example
```
StyleCop's `documentationRules.excludeFromPunctuationCheck`, with the same default; `stylebro-migrate` writes it from
`stylecop.json` when a team changed it. An excluded tag isn't checked at the top level or nested (an excluded `para`
inside a `summary`). The rule checks `summary`, `remarks`, `param`, `typeparam`, `returns`, `value` and `exception`
anyway, so `seealso` is never checked, set or not.
## Compared with StyleCop
Same results as StyleCop, except:
- text ending with `?`, `!` or `:`, which StyleCop reports and turns into `question?.`;
- a period followed by a closing quote or bracket (`"done."`), which StyleCop reports and turns into `"done.".`
([#2860](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2860)); StyleCop accepts only `.)`;
- text ending in an entity: StyleCop 1.2.0-beta.556 reports the entity's `;` and its fix replaces it (`>.`,
[#3802](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3802), fixed after beta.556);
- text ending in code (`digraph {`, `A1:B10`), which StyleCop reports and turns into `digraph {.`;
- text ending in a `` element that quotes a sentence (`"User {User} logged in."`), which StyleCop reports
and turns into `.` ([#2784](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2784), open);
- text ending in an inline element with text of its own (`a sentence.`): StyleCop passes over it
and checks the text before it, so it reports `Ends with ` and writes `Ends with. `; StyleBro checks
the element's own text and puts a missing period after the element;
- text whose last word ends in a period and digits (`HTML 5.0`), which StyleCop turns into `5.0.`.
StyleCop checks every top-level tag not in `excludeFromPunctuationCheck`; StyleBro only the tags listed above.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1604/
# BRO1604: Property summary documentation should match accessors
A property's summary begins with the words its accessors call for: `Gets or sets`, `Gets` or `Sets` (with an
`init` accessor: `Gets or initializes`, `Gets or sets` or just `Gets`, and `Initializes`), followed by `a value indicating whether`
for a `bool`. Replaces StyleCop **SA1623**.
## Example
### Before
```csharp
public class Order
{
/// The order number.
public string Number { get; set; }
/// Gets or sets the total.
public decimal Total { get; }
/// Whether the order is paid.
public bool IsPaid { get; set; }
}
```
### After
```csharp
public class Order
{
/// Gets or sets the order number.
public string Number { get; set; }
/// Gets the total.
public decimal Total { get; }
/// Gets or sets a value indicating whether the order is paid.
public bool IsPaid { get; set; }
}
```
## Why
The summary then says at a glance what callers can do with the property, and it matches the .NET documentation.
## How the fix works
A wrong prefix is replaced, or the right one is added in front, with the next word's first letter lowered
(`The order number` becomes `Gets or sets the order number`; an acronym like `URL` keeps its capitals). For a
`bool`, a summary that starts with `Whether`, `Indicates whether` or `Gets whether` has that replaced too (only `bool`,
like StyleCop: a `bool?` property's `Gets whether` stays). The words
are only added in front of a noun phrase: a summary that starts with an article (`The`, `A`, `An`) or a lower-case
word. The fix doesn't rephrase anything else.
Which words: `Gets or sets` for a getter and a setter other code can use, `Gets` for a getter only (a private or
internal setter doesn't count, see [BRO1605](BRO1605.md); a protected one does), `Sets` for a setter only (a private
or internal getter doesn't count either: `{ private get; set; }` says `Sets`, like StyleCop). An `init`
accessor counts like a setter: `Gets or initializes` (or `Gets or sets`, or just `Gets`) for `{ get; init; }`, `Initializes` when it's
the only accessor other code can use. `Gets or initializes` and `Initializes` are wrong prefixes elsewhere and get
replaced (`Gets or initializes the value.` on `{ get; set; }` becomes `Gets or sets the value.`).
This rule reads documentation as XML, which needs `true`.
## Not reported
- **Indexers.**
- **Summaries that start with an element** (``) or whose first words wrap to the next line.
- **Summaries without one of the prefixes that don't start like a noun phrase**, i.e. with anything but an article
(`The`, `A`, `An`) or a lower-case word: `This is the hash`, `Not used`, `Device id of the user`,
`If true, the command text is inspected`, `Returns the name`, `Is it closed`. Words in front of these wouldn't
make a sentence (`Gets or sets this is the hash`), and there's no fix that surely does, so there's no diagnostic
either; rephrase them by hand. Also not reported: a lower-case condition or verb (`if`, `when`, `whenever`,
`unless`, `while`, `whether` (not on a `bool`, where it's replaced), `true`, `false`, `returns`, `specifies`,
`indicates`, `determines`, `contains`, `controls`, `defines`, `represents`, `provides`, `allows`, `enables`,
`describes`, `stores`, `holds`, `is`, `has`, `can`).
- **A `bool` summary that starts with an article** (`The tokenizer predicted a multi-word token here.`): it's often a
clause, and neither `Gets or sets the tokenizer predicted ...` nor `Gets or sets a value indicating whether the open
state` makes a sentence of every one (owner's decision 2026-10-12, issue #132). Summaries that start with `Whether`
are still fixed.
## Compared with StyleCop
Same results as StyleCop's `master` (unreleased), which added `init` accessors, except that `Gets or sets` on
`{ get; init; }` is fine (master wants `Gets or initializes`; rewriting a common wording, often on attribute properties
that attribute syntax sets, is churn); StyleCop 1.2.0-beta.556 treats
`init` as if it weren't there (it wants `Gets` for `{ get; init; }`, and its fix turns `Gets or initializes` on
`{ get; set; }` into `Gets or sets or initializes`). The fixed output differs only for `bool` summaries that start
with `Whether` or `Indicates whether`, where StyleCop keeps both (`a value indicating whether whether ...`). StyleCop
also reports a `bool` summary that starts with an article, which StyleBro leaves alone (see Not reported).
StyleCop also reports the summaries that don't start like a noun phrase (see Not reported).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1605/
# BRO1605: Property summary documentation should omit accessor with restricted access
A property whose setter is private or internal is documented as `Gets`, not `Gets or sets`: other code can't set it.
Replaces StyleCop **SA1624**.
## Example
### Before
```csharp
public class Account
{
/// Gets or sets the balance.
public decimal Balance { get; private set; }
public void Deposit(decimal amount) => Balance += amount;
}
```
### After
```csharp
public class Account
{
/// Gets the balance.
public decimal Balance { get; private set; }
public void Deposit(decimal amount) => Balance += amount;
}
```
## Why
Documentation describes what callers can do. For them, a property with a private setter is read-only.
## How the fix works
`Gets or sets` becomes `Gets` (and `Gets or sets a value indicating whether` becomes `Gets a value indicating
whether`). The same goes for `Gets or initializes` on a property with a restricted `init` accessor. A `private`, `internal` or `private protected` setter counts as restricted; a `protected` or
`protected internal` one doesn't, since derived types can use it (like StyleCop). The same goes for a restricted
getter: `{ private get; set; }` is documented `Sets`, not `Gets or sets`.
This rule reads documentation as XML, which needs `true`.
## Compared with StyleCop
Same results and fixed output as StyleCop's `master` (unreleased), which added `init` accessors (StyleCop
1.2.0-beta.556 ignores them).
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1606/
# BRO1606: Constructor summary documentation should begin with standard text
A constructor's summary begins with `Initializes a new instance of the class.` (or `struct`), and a
static constructor's with `Initializes static members of the class.`. Replaces StyleCop **SA1642**.
Only summaries that say nothing else are reported: an empty one, filler (`Default constructor.`) and the standard
sentence written differently. A summary with text of its own (`Opens a connection to the server.`) is left alone.
## Example
### Before
```csharp
public class Connection
{
/// Default constructor.
/// The server's host name.
public Connection(string host)
{
Host = host;
}
/// Gets the host name.
public string Host { get; }
}
```
### After
```csharp
public class Connection
{
/// Initializes a new instance of the class.
/// The server's host name.
public Connection(string host)
{
Host = host;
}
/// Gets the host name.
public string Host { get; }
}
```
## Why
The standard sentence is what the .NET documentation uses, and it links to the type, so every constructor's
documentation starts the same recognizable way.
## How the fix works
Only the beginning counts, like in StyleCop: `Initializes a new instance of the class representing ...`
is fine. A sentence that is the standard one with the type named differently (`Initializes a new instance of the
Connection class.`, without the link) is replaced; a sentence that says more (`Initializes a new instance of Connection
with a timeout.`) isn't reported (see Not reported).
**Filler is replaced too**: a first sentence that only says it is a constructor (`Default constructor.`,
`Constructor.`, `Ctor.`, `Constructor for X.`, `Creates a new instance.`, `Creates a new instance of X.`,
`Creates an instance of the X class.`, also with `Constructs` or `Instantiates`). Any text after it stays, after the
standard sentence: `Default constructor. Starts the timer.` becomes `Initializes a new instance of the class. Starts the timer.` (found in Stateless: `... class. Default constructor.`). The list is short on
purpose; a sentence that says anything more is kept.
For a generic type the link is ``. A private constructor may also start with
`Prevents a default instance of the class from being created.`
A summary that starts with `` is judged by that paragraph's text, and the fix puts the standard sentence inside
it. An empty or whitespace-only summary gets the standard sentence; when it spans lines, on a line of its own.
This rule reads documentation as XML, which needs `true`.
## Not reported
- **Constructors without a summary** or with ``: missing documentation isn't reported.
- **Records.**
- **A whitespace-only summary whose `` line holds more than `///`**: the fix couldn't write the new line.
- **A constructor summary with text of its own** (`Opens a connection to the server.`, `Creates a words object.`): it
already says what the constructor does, and the standard sentence in front of it made two sentences doing one job
(owner's decision 2026-10-12, issue #132: about 100 public summaries in a trial). A finalizer's summary still gets
the standard sentence in front ([BRO1607](BRO1607.md)).
- **Text left after replaced filler that can't follow the standard sentence**: text that starts in lower case
(`construct a dynamic parameter bag`) or with a condition (`If`, `When`, `Whenever`, `Unless`, `While`, `Whether`,
`True`, `False`). `Initializes a new instance of the class. construct a dynamic parameter bag` isn't
English; rephrase these by hand.
- **A finalizer summary that is a sentence of its own** (it starts with `Finalizes` and says more than the standard
sentence): replacing it would lose what it says.
## Compared with StyleCop
Same results as StyleCop, except a summary that starts with ``: StyleCop 1.2.0-beta.556 reports it even when the
paragraph starts with the standard sentence, and its fix puts a second one in front of ``. StyleCop's `master`
(unreleased, 29514d23) accepts any summary that starts with `` without checking its text; StyleBro still checks
the paragraph's text (the owner's decision, see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)). A whitespace-only summary is reported
like in StyleCop (parity set `documentation-tags`). StyleCop's fix puts the standard sentence directly before the
old text, without a space (`class.Opens a connection`), and also reports the summaries StyleBro leaves alone: a
constructor summary with text of its own (StyleBro reports only empty, filler and near-miss summaries), and text after
filler that starts in lower case or with a condition.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1607/
# BRO1607: Destructor summary documentation should begin with standard text
A finalizer's summary begins with `Finalizes an instance of the class.`. Replaces StyleCop
**SA1643**.
## Example
### Before
```csharp
public class NativeBuffer
{
/// Releases the native memory.
~NativeBuffer()
{
}
}
```
### After
```csharp
public class NativeBuffer
{
/// Finalizes an instance of the class. Releases the native memory.
~NativeBuffer()
{
}
}
```
## Why
The standard sentence is what the .NET documentation uses for finalizers, and it links to the type.
## How the fix works
Like [BRO1606](BRO1606.md): the standard sentence goes first, a near miss of it is replaced, and an empty or
whitespace-only summary gets it. This rule reads documentation as XML, which needs
`true`.
## Compared with StyleCop
Same results as StyleCop, including whitespace-only summaries. StyleCop's fix leaves out the space after the standard
sentence.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1608/
# BRO1608: Void return value should not be documented
A method or delegate that returns `void` has no `` documentation. Replaces StyleCop **SA1617**.
## Example
### Before
```csharp
public class Logger
{
/// Writes a message.
/// The message.
/// Nothing.
public void Write(string message)
{
}
}
```
### After
```csharp
public class Logger
{
/// Writes a message.
/// The message.
public void Write(string message)
{
}
}
```
## Why
There's no return value to document; the element is usually left over from a method that used to return something.
## How the fix works
The `` element is removed: its whole `///` lines when it has them to itself, otherwise just the element and
the spaces before it. This rule reads documentation as XML, which needs
`true`.
## Compared with StyleCop
Same results and fixed output as StyleCop.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1609/
# BRO1609: Do not use placeholder elements
Documentation doesn't contain `` elements. Replaces StyleCop **SA1651**.
## Example
### Before
```csharp
public class Cache
{
/// Clears the cache.
public void Clear()
{
}
}
```
### After
```csharp
public class Cache
{
/// Clears the cache.
public void Clear()
{
}
}
```
## Why
Some tools generate documentation wrapped in `` to mark it as not reviewed yet. Once it's checked in,
the marker is noise.
## How the fix works
The `` tags are removed and their text stays, like StyleCop's fix. If the text isn't right, the fix
can't know that: read it when you review the change.
This rule reads documentation as XML, which needs `true`.
## Not reported
A `` at the top level of a comment, outside any element such as ``: unwrapping it would leave
its text outside every element.
## Compared with StyleCop
Same results and fixed output as StyleCop, except the top-level `` above, which StyleCop reports and
unwraps.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1610/
# BRO1610: Documentation text should not be empty
An empty `` element is removed. Replaces StyleCop **SA1627**.
## Example
### Before
```csharp
public class Parser
{
/// Parses the input.
///
public void Parse()
{
}
}
```
### After
```csharp
public class Parser
{
/// Parses the input.
public void Parse()
{
}
}
```
## Why
An empty element documents nothing; it's usually left over from a template or a generated stub.
## How the fix works
The element is removed: its whole `///` lines when it has them to itself, otherwise just the element. ``
and remarks with only whitespace count as empty.
Empty ``, `` and `` elements are StyleCop's SA1606, SA1614 and SA1616. They aren't reported:
the only fix would be placeholder text (see [BRO1601](BRO1601.md)).
This rule reads documentation as XML, which needs `true`.
## Compared with StyleCop
Same results as StyleCop. StyleCop has no fix for SA1627.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1611/
# BRO1611: Element parameter documentation should match element parameters
`` tags document the parameters that exist, in their order. Replaces StyleCop **SA1612**.
## Example
### Before
```csharp
public class Mailer
{
/// Sends a message.
/// The subject.
/// Who receives it.
public void Send(string to, string subject)
{
}
/// Queues a message.
/// Who receives it.
/// How often to retry.
public void Queue(string to)
{
}
}
```
### After
```csharp
public class Mailer
{
/// Sends a message.
/// Who receives it.
/// The subject.
public void Send(string to, string subject)
{
}
/// Queues a message.
/// Who receives it.
public void Queue(string to)
{
}
}
```
## Why
Parameters get renamed, removed and reordered, and their documentation is easily forgotten. Stale tags describe
parameters that don't exist, and IntelliSense shows the documentation in the wrong place.
## How the fix works
- **A tag for a parameter that doesn't exist** is renamed when it's the only such tag and exactly one parameter has
no tag: the parameter was most likely renamed (`recipient` becomes `to` above). Otherwise the tag is removed, since
its parameter is gone (`retries` above).
- **Tags out of order** are put in parameter order, when each tag has its own `///` lines.
A renamed tag's text isn't changed; read it when you review the fix.
This rule reads documentation as XML, which needs `true`.
## Not reported
- **Tags without a name, or two tags for the same parameter.**
- **Out-of-order tags that share a line with other documentation.**
- **Members whose documentation has a top-level ``** (`/// ` next to ``
tags), like StyleCop's SA1612. An `` inside a tag (``) doesn't count.
- **Constructors and operators**, like StyleCop, whose SA1612 doesn't check them. Primary constructors are checked:
the `` tags of a class, struct or record with a parameter list.
## Compared with StyleCop
Same results as StyleCop's `master` (unreleased): methods, indexers, delegates, and the primary constructor parameters
of classes, structs and records; StyleCop leaves constructors and operators out, and so does StyleBro, so switching
doesn't flag code StyleCop accepted. StyleCop 1.2.0-beta.556 doesn't check primary constructors. StyleCop has no fix for SA1612.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1612/
# BRO1612: Element parameter documentation should declare parameter name
Every `` tag says which parameter it documents. Replaces StyleCop **SA1613**.
## Example
### Before
```csharp
public class Ruler
{
/// Measures a distance.
/// The start.
/// The end.
/// The distance.
public double Measure(double from, double to) => to - from;
}
```
### After
```csharp
public class Ruler
{
/// Measures a distance.
/// The start.
/// The end.
/// The distance.
public double Measure(double from, double to) => to - from;
}
```
## Why
A `` without a name doesn't show up in IntelliSense or the generated documentation; the text is lost.
## How the fix works
The fix adds `name="..."` (or fills in an empty one) when the parameter is certain: the tag is the only unnamed one
and exactly one parameter has no tag, or every tag is unnamed and there is one per parameter, which are then named in
order. The tag's problems are fixed together with [BRO1611](BRO1611.md)'s, so both fixes give the same result.
This rule reads documentation as XML, which needs `true`.
## Not reported
- **When the parameter isn't certain**, for example two unnamed tags for three parameters, or an unnamed tag next to a
tag naming a parameter that doesn't exist: the fix would have to guess.
- Constructors and operators, like [BRO1611](BRO1611.md).
## Compared with StyleCop
StyleCop reports every unnamed tag and has no fix; StyleBro reports the ones it can name.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1613/
# BRO1613: Generic type parameter documentation should match type parameters
`` tags document the type parameters that exist, in their order. Replaces StyleCop **SA1620**.
## Example
### Before
```csharp
/// A cache.
/// The value type.
/// The key type.
public class Cache
{
/// Gets an item.
/// The result type.
/// The item.
public TItem Get() => default(TItem);
}
```
### After
```csharp
/// A cache.
/// The key type.
/// The value type.
public class Cache
{
/// Gets an item.
/// The result type.
/// The item.
public TItem Get() => default(TItem);
}
```
## Why
Type parameters get renamed and reordered like parameters, and stale tags describe something that no longer exists.
## How the fix works
Like [BRO1611](BRO1611.md) for ``: a tag for a type parameter that doesn't exist is renamed when it's the only
such tag and exactly one type parameter has no tag (it was most likely renamed), and removed otherwise. Tags out of
order are put in type parameter order when each has its own `///` lines. Checked on methods, delegates, classes,
structs, interfaces and records.
This rule reads documentation as XML, which needs `true`.
## Not reported
- **Duplicate tags**, and **tags out of order that share a line** with other documentation.
- **Members whose documentation has a top-level ``**, like StyleCop's SA1620.
## Compared with StyleCop
Same positions as StyleCop. StyleCop has no fix for SA1620.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1614/
# BRO1614: Generic type parameter documentation should declare parameter name
Every `` tag says which type parameter it documents. Replaces StyleCop **SA1621**.
## Example
### Before
```csharp
/// A box.
/// The content type.
public class Box
{
}
```
### After
```csharp
/// A box.
/// The content type.
public class Box
{
}
```
## Why
A `` without a name doesn't show up in IntelliSense or the generated documentation; the text is lost.
## How the fix works
Like [BRO1612](BRO1612.md) for ``: the fix adds the name when the type parameter is certain (the only unnamed
tag and exactly one undocumented type parameter, or every tag unnamed with one per type parameter, named in order).
It's fixed together with [BRO1613](BRO1613.md)'s problems.
This rule reads documentation as XML, which needs `true`.
## Not reported
- **When the type parameter isn't certain**: the fix would have to guess.
- **Members whose documentation has a top-level ``**, like StyleCop's SA1621. (Unnamed `` tags are
still [BRO1612](BRO1612.md)'s there, like SA1613.)
## Compared with StyleCop
StyleCop reports every unnamed tag and has no fix; StyleBro reports the ones it can name.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1615/
# BRO1615: File should have the XML copyright header
Every file starts with StyleCop's XML header: a `` tag naming the file and the company, around the copyright
text. Replaces StyleCop **SA1633** (with StyleCop's default `xmlHeader: true`) and **SA1634**, **SA1635**,
**SA1636**, **SA1637**, **SA1638**, **SA1640** and **SA1641**. The rule does nothing until
`stylebro_file_header_company` is set.
## Example
```ini
root = true
[*.cs]
stylebro_file_header_company = Contoso
```
### Before
```csharp
// Order handling.
namespace Shop;
public class Order
{
}
```
### After
```csharp
//
// Copyright (c) Contoso. All rights reserved.
//
// Order handling.
namespace Shop;
public class Order
{
}
```
### Before
```csharp
//
// Copyright (c) Contoso Ltd. All rights reserved.
//
// Order handling.
namespace Shop;
public class Order
{
}
```
### After
```csharp
//
// Copyright (c) Contoso. All rights reserved.
//
// Order handling.
namespace Shop;
public class Order
{
}
```
## Why
A team that wants a copyright notice in every file wants it to be the same notice, with the right file name after a
file is renamed or copied. The SDK's IDE0073 writes a plain header (`file_header_template`) but not this XML form,
which is StyleCop's default.
Teams with a plain header (StyleCop's `xmlHeader: false`) don't need this rule: use IDE0073 with
`file_header_template` instead. **Never turn on both.** Each sees the other's header as a plain comment and writes its
own above it, so every `dotnet format` run adds a header. `stylebro-migrate` turns on one of them (IDE0073 off with the
XML header, BRO1615 off with a plain one or without a `companyName`), and `stylebro-migrate init` turns BRO1615 off when
the repository has a `file_header_template`.
## Configuration
```ini
[*.cs]
# Required: the rule is off until this is set. Written into company="...".
stylebro_file_header_company = Contoso
# The copyright text (default: StyleCop's). '\n' starts a new line; {companyName} and {fileName} are filled in.
stylebro_file_header_copyright = Copyright (c) {companyName}. All rights reserved.
# Optional: a line written above and below a new header (StyleCop's headerDecoration).
stylebro_file_header_decoration = -----------------------------------------------------------------------
```
StyleCop's custom `variables` from stylecop.json aren't supported as such; [stylebro-migrate](../migrating.md) writes
their values into the copyright text.
An `.editorconfig` value ends at a `;` or `#` (also mid-line, in the .NET 10 SDK too) and loses the spaces at its ends.
All three settings read escapes for those: `\u003B` for `;`, `\u0023` for `#`, `\u0020` for a space (at either end),
`\u005C` for a backslash, any other `\uXXXX` too. stylebro-migrate writes them:
```ini
stylebro_file_header_copyright = \u0020\u0020\u0020\u0020Copyright (c) {companyName}.\n\n but WITHOUT ANY WARRANTY\u003B without even the implied warranty of
```
## How the fix works
- **No header** (code, a directive or a `///` comment first): the header is added at the top, followed by a blank line.
- **A header that isn't XML** (plain `//` comments): if its text is the configured copyright text, it becomes the XML
header. Any other comment is kept: the new header goes above it, separated by a blank line. (StyleCop's fix deletes
it.)
- **An XML header without a `` tag**: the tag is added at the top of the header, so a `` and any
`//-----` border stay.
- **A wrong `` tag** (file name, company or text): only the tag's lines are rewritten; everything else in
the header stays.
The text is compared like StyleCop does: line by line, ignoring whitespace at the start and end of each line, so
`// Copyright ...` and a tag on one line are fine. The file name is compared case-sensitively.
## Not reported
- Files without code (empty or whitespace only), and generated files.
- A header in a `/* */` comment (rare; StyleCop checks it).
- A broken XML header that already has a ``): a person has to
repair it, and adding a second header would only make it worse.
- A `` tag that shares its first or last line with other text, since only whole lines are rewritten.
## Compared with StyleCop
- The fix gives the same headers as StyleCop's, except that it never deletes a comment: StyleCop's fix replaces a plain
comment header (license text, notes) with the XML header.
- One rule instead of eight: StyleCop's SA1634-SA1641 each report one problem (sometimes three at the same place); the
message here lists all of them.
- A missing header is reported on the first line of code instead of at the start of the file, so a
`#pragma warning disable BRO1615` at the top of the file can suppress it.
- StyleCop has the header rules on by default with the company `PlaceholderCompany`; this rule is off until you name
the company, so nobody ends up with placeholder headers.
- SA1639 (a `` in the header, off by default in StyleCop) isn't covered: the summary has to be written by a
person.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1616/
# BRO1616: Write the summary's tags consistently on their own lines or on the text's line
A `` has its tags on lines of their own (the default), or, with `stylebro_summary_layout =
single_line_when_fits`, is written on one line when its text is one line and the result fits within `max_line_length`.
Not a StyleCop rule; Roslynator's RCS1253 and Meziantou's MA0177/MA0211 check the same.
## Example
### Before
```csharp
/// A shape on the screen.
public class Shape
{
/// Draws the shape
/// at its position.
public void Draw()
{
}
}
```
### After
```csharp
///
/// A shape on the screen.
///
public class Shape
{
///
/// Draws the shape
/// at its position.
///
public void Draw()
{
}
}
```
## Why
A codebase that mixes `/// Gets the name.` with the three-line form reads as two styles. The
three-line form is what Visual Studio inserts when you type `///`, and the eight repositories StyleBro is tested on
use it for 99% of their one-sentence summaries (14,329 to 148, see [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)), so it's the
default. A team that prefers the short form picks `single_line_when_fits`.
## How the fix works
Only the gap between a tag and the text next to it changes: it becomes a line break and `/// ` (indented like the
`` line), or, when joining, nothing. The text itself, any lines in between, inline elements (``,
``) and the tags stay as they are, so a missing period is still [BRO1603](BRO1603.md)'s and the `///` spacing
[BRO1005](BRO1005.md)'s.
This rule reads documentation as XML, which the compiler only does when the project generates documentation
(`true`), like the other documentation rules.
## Configuration
```ini
[*.cs]
# multi_line (default): the tags on lines of their own.
# single_line_when_fits: one line when the text is one line, has no , or , and the joined line
# fits within max_line_length (no limit when it isn't set); any other summary has its tags on lines of their own.
stylebro_summary_layout = multi_line
max_line_length = 120
```
With `single_line_when_fits`:
```csharp
/// A shape on the screen.
public class Shape
{
///
/// Draws the shape
/// at its position.
///
public void Draw()
{
}
}
```
## Not reported
- **Empty summaries** (``, ``, only blank `///` lines).
- **A tag that shares its line with something else** (`/// Draws. ...`): the layout of
the other element isn't this rule's.
- **`/** ... */` documentation comments.**
- **Summaries with XML errors** (a missing or misspelled end tag).
- With `single_line_when_fits`: **a summary already on one line**, even when it's longer than `max_line_length` (this
isn't a line-length rule), and **text on several lines** or with ``, `` or ``, which keeps (or gets)
its tags on their own lines.
- Only ``: ``, ``, `` and the other elements are left as they are.
## Compared with StyleCop
StyleCop has no such rule. Roslynator's RCS1253 (off by default, `roslynator_doc_comment_summary_style = single_line |
multi_line`) and Meziantou's MA0211 (multi-line) and MA0177 (single line when the content is one line and fits within
`max_line_length`; for every top-level element, not only the summary) do the same, off by default. Unlike them, the
multi-line layout here also moves the tags of a summary whose text starts on the `` line or ends on the
`` line. `stylebro-migrate` turns this rule off, so migrating doesn't change a StyleCop-clean repository.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1617/
# BRO1617: Write a cref's type arguments in braces
A `cref` names a generic type or member with braces, `cref="List{T}"`, not with escaped angle brackets,
`cref="List<T>"`. Checked in every `cref` attribute: ``, ``, ``, `` and
any other element. Not a StyleCop rule: it's StyleCop's proposed SA1653
([#758](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/758)), which the maintainers liked and nobody built.
## Example
### Before
```csharp
using System.Collections.Generic;
/// Keeps a of names.
///
public class Names
{
}
```
### After
```csharp
using System.Collections.Generic;
/// Keeps a of names.
///
public class Names
{
}
```
## Why
Both forms point at the same type, but `List<T>` is hard to read and to type, and a codebase that mixes the two
can't be searched for one of them. Microsoft's documentation writes crefs with braces
(`cref="IDictionary{T, U}"`).
## How the fix works
Each `<` and `>` that opens or closes a type argument (or type parameter) list becomes `{` or `}`; nothing else in
the cref changes, so `M<T>(List<List<T>>)` becomes `M{T}(List{List{T}})`.
This rule reads documentation as XML, which the compiler only does when the project generates documentation
(`true`), like the other documentation rules.
## Not reported
- **Operators.** `cref="operator <(C, C)"`: the `<` is the operator, not a type argument list.
- **Crefs the compiler can't read** (for example `List<int>`, which names a type argument instead of a type
parameter): a person has to decide what they meant.
- **Escaped angle brackets in text** (`List<T>` outside a cref) and anything inside ``.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1618/
# BRO1618: Write a C# keyword in documentation as ``
A C# keyword alone in ``, such as `null`, `true` or `static`, is written
``. Not a StyleCop rule; Meziantou's MA0154 checks the same.
## Example
### Before
```csharp
public class Cache
{
/// Finds an item.
/// The key.
/// The item, or null when it isn't cached.
public object? Find(string key) => null;
}
```
### After
```csharp
public class Cache
{
/// Finds an item.
/// The key.
/// The item, or when it isn't cached.
public object? Find(string key) => null;
}
```
## Why
`` is what Microsoft's documentation uses for keywords: documentation tools link it to the
keyword's page, and Visual Studio shows it like a keyword. `null` is only code-formatted text. One form for
keywords also keeps the documentation consistent.
## How the fix works
The fix replaces the whole `` element with ``.
This rule reads documentation as XML, which the compiler only does when the project generates documentation
(`true`), like the other documentation rules.
## Which keywords
Every reserved C# keyword (`null`, `true`, `false`, `void`, `static`, `abstract`, `sealed`, `int`, `string`, ...), plus
the contextual keywords Meziantou's MA0154 takes because they're unlikely to be an identifier: `async`, `await`,
`dynamic`, `init`, `nameof`, `nint`, `nuint`, `partial`, `record`, `required`, `scoped` and `var`. Other contextual
keywords (`value`, `get`, `set`, `add`, `from`, `select`, ...) are common words and names, so they're left alone.
## Not reported
- **Anything more than one keyword** in the ``: `null!`, `x is null`, ` null ` (spaces),
or other elements inside it.
- **A `` with attributes** (`null`).
- **Inside ``**: a code sample stays as written.
- **Different case** (`Null`): C# keywords are lower case.
---
Source: https://bisforboman.github.io/stylebro/rules/BRO1619/
# BRO1619: Put the documentation elements in the standard order
The top-level elements of a documentation comment come in this order: ``, ``, ``,
``, ``, ``, ``, ``, ``. Elements of one kind keep their order
among themselves. Not a StyleCop rule.
## Example
### Before
```csharp
public class Calculator
{
/// The sum.
/// The first number.
/// The second number.
/// Adds two numbers.
/// Overflows silently.
/// Never, in practice.
public int Add(int a, int b) => a + b;
}
```
### After
```csharp
public class Calculator
{
/// Adds two numbers.
/// The first number.
/// The second number.
/// The sum.
/// Never, in practice.
/// Overflows silently.
public int Add(int a, int b) => a + b;
}
```
## Why
Readers look for the summary first and the parameters next; a fixed order puts every member's documentation in the
same place. C# itself prescribes no order, so the order is the one most code already follows:
- ``, ``, ``, ``: the stub Visual Studio inserts when you type `///`.
- ``, then ``: the order of the sections on Microsoft Learn's API pages (parameters, returns or
property value, exceptions).
- ``, ``, `` last: the supplementary material, with the "see also" links at the end, where
documentation pages show them.
Survey #15 in [beyond-stylecop.md](https://github.com/bisforboman/stylebro/blob/main/docs/beyond-stylecop.md) listed this as Roslynator's RCS1232, but RCS1232 only orders
`` tags, which [BRO1611](BRO1611.md) already does.
## How the fix works
Like [BRO1611](BRO1611.md)'s reorder: each element's lines are a slot, and every slot gets the lines of the element
that belongs there. Lines between the elements (an empty `///`) stay where they are. The order within one kind is
kept, so `` tags in the wrong order are [BRO1611](BRO1611.md)'s and `` tags [BRO1613](BRO1613.md)'s;
`dotnet format` fixes both in one run, in either order.
This rule reads documentation as XML, which the compiler only does when the project generates documentation
(`true`), like the other documentation rules.
## Not reported
- **An element that shares a line** with another element or text: only whole lines are moved.
- **Any other top-level element** (``, ``, ``, a custom tag): where it belongs
isn't clear, so the comment is left as it is.
- **Text outside the elements** (a loose sentence between two tags).
- **`/** */` comments.**
---
Source: https://bisforboman.github.io/stylebro/configuration/
# Configuration
Every `stylebro_*` setting, in `.editorconfig` under `[*.cs]`. Each rule's page explains its settings with examples;
this page lists them all in one place. Severities work like any analyzer's
(`dotnet_diagnostic.BRO1001.severity = none`), and the package's preset sets the defaults listed here unless your
`.editorconfig` says otherwise. Some rules also follow the SDK's own settings (`csharp_prefer_braces`, `csharp_using_directive_placement`,
`dotnet_style_qualification_for_*`, `dotnet_naming_rule.*`, `max_line_length`, ...): see their rule pages.
Which rules the preset turns on depends on its version, an MSBuild property (in `Directory.Build.props` or the project
file), not an `.editorconfig` key:
| `` | Rules |
|---|---|
| unset | the package's major version: `1.0` in all of 1.x |
| `1.0`, `1.1`, ... | the rules up to that release; newer releases' rules stay off |
| `latest` | every rule in the package |
| `none` | no preset: rules at their own defaults (rules added after 1.0 off), no preset settings |
A version newer than the package warns (STYLEBRO002) and uses the package's newest; any other value fails the build
(STYLEBRO001). See [versioning](versioning.md#new-rules).
```ini
[*.cs]
stylebro_null_check_style = equality_operator
```
| Key | Values | Default | Rules |
|-----|--------|---------|-------|
| `stylebro_member_order` | kinds, comma-separated: `field`, `constructor`, `finalizer`, `delegate`, `event`, `enum`, `interface`, `property`, `indexer`, `conversion`, `operator`, `extension`, `method`, `struct`, `class` | that list (StyleCop's order) | [BRO1001](rules/BRO1001.md) |
| `stylebro_member_access_order` | accessibilities, comma-separated: `public`, `internal`, `protected_internal`, `protected`, `private_protected`, `private` | that list | [BRO1001](rules/BRO1001.md) |
| `stylebro_member_constants_first` | `true`, `false` | `true` | [BRO1001](rules/BRO1001.md) |
| `stylebro_member_static_first` | `true`, `false` | `true` | [BRO1001](rules/BRO1001.md) |
| `stylebro_member_readonly_first` | `true`, `false` | `true` | [BRO1001](rules/BRO1001.md) |
| `stylebro_keep_overloads_together` | `true`, `false`, `same_access` | `false` | [BRO1001](rules/BRO1001.md) |
| `stylebro_allow_semicolon_after_closing_brace` | `true`, `false` (`true`: Sonar's S1116, the `;` after a type's or namespace's `}` stays) | `false` | [BRO1101](rules/BRO1101.md) |
| `stylebro_constructor_initializer_placement` | `own_line`, `same_line` | `own_line` | [BRO1105](rules/BRO1105.md) |
| `stylebro_empty_string_style` | `string_empty`, `literal` | `string_empty` | [BRO1106](rules/BRO1106.md) |
| `stylebro_split_list_first_item` | `next_line`, `same_line` | `next_line` | [BRO1107](rules/BRO1107.md), [BRO1108](rules/BRO1108.md) |
| `stylebro_allow_leading_multiline_lambda` | `true`, `false` (`true`: a first argument that is a multi-line lambda, anonymous method or anonymous object may keep the other arguments on its closing line) | `false` | [BRO1107](rules/BRO1107.md), [BRO1108](rules/BRO1108.md) |
| `stylebro_closing_parenthesis_placement` | `last_item`, `own_line` | `last_item` | [BRO1110](rules/BRO1110.md) |
| `stylebro_constraint_placement` | `own_line`, `same_line` | `own_line` | [BRO1111](rules/BRO1111.md) |
| `stylebro_null_check_style` | `pattern_matching`, `equality_operator` | `pattern_matching` | [BRO1133](rules/BRO1133.md), [BRO1148](rules/BRO1148.md) |
| `stylebro_upper_case_literal_suffixes` | `all`, `l_only`, `lone_l` (Sonar's S818: only a suffix that is just `l`) | `all` | [BRO1135](rules/BRO1135.md) |
| `stylebro_allow_null_forgiving_on_unannotated` | `true`, `false` (`true`: Sonar's S8969, a `!` on a value declared without nullable annotations stays) | `false` | [BRO1147](rules/BRO1147.md) |
| `stylebro_allow_nested_dynamic_ifs` | `true`, `false` (`true`: Sonar's S1066, nested `if`s with a `dynamic` condition aren't merged) | `false` | [BRO1149](rules/BRO1149.md) |
| `stylebro_params_arrays` | `all`, `except_object_and_jagged` (Sonar's S3878: not for `params object[]`, `params Array[]` or jagged arrays) | `all` | [BRO1151](rules/BRO1151.md) |
| `stylebro_object_creation_parentheses` | `omit`, `include` | `omit` | [BRO1141](rules/BRO1141.md) |
| `stylebro_private_field_naming` | `camelCase`, `_camelCase` | not set: the SDK's naming rules decide, else `camelCase` | [BRO1303](rules/BRO1303.md), [BRO1307](rules/BRO1307.md), [BRO1310](rules/BRO1310.md) |
| `stylebro_private_mutable_static_field_naming` | `camelCase`, `_camelCase` | not set: a naming rule requiring just `static` decides, else `stylebro_private_field_naming`'s style | [BRO1303](rules/BRO1303.md) |
| `stylebro_allow_leading_underscore_in_pascal_fields` | `true`, `false` | `false` | [BRO1306](rules/BRO1306.md) |
| `stylebro_private_static_field_naming` | `PascalCase`, `camelCase`, `_camelCase` | not set: the SDK's naming rules decide, else `PascalCase` | [BRO1306](rules/BRO1306.md) |
| `stylebro_allowed_hungarian_prefixes` | prefixes, comma-separated | none | [BRO1310](rules/BRO1310.md) |
| `stylebro_allow_common_hungarian_prefixes` | `true`, `false` | `true` | [BRO1310](rules/BRO1310.md) |
| `stylebro_rename_public_api` | `true`, `false` | `false` | [BRO1302](rules/BRO1302.md)-[BRO1314](rules/BRO1314.md): names other assemblies see |
| `stylebro_tuple_element_name_casing` | `PascalCase`, `camelCase` | `PascalCase` | [BRO1311](rules/BRO1311.md) |
| `stylebro_match_base_parameter_names` | `all`, `non_generic_methods` (Sonar's S927: methods only, not where the base's parameter type is a type parameter the member fills in) | `all` | [BRO1313](rules/BRO1313.md) |
| `stylebro_allowed_namespace_components` | names, comma-separated | none | [BRO1312](rules/BRO1312.md) |
| `stylebro_trailing_comma` | `include`, `omit` | `include` | [BRO1401](rules/BRO1401.md), [BRO1509](rules/BRO1509.md) |
| `stylebro_comment_blank_line_exempt_prefixes` | prefixes, comma-separated, case-sensitive | none | [BRO1504](rules/BRO1504.md) |
| `stylebro_allow_adjacent_single_line_members` | `true`, `false` | `false` | [BRO1505](rules/BRO1505.md) |
| `stylebro_blank_line_below_standalone_comments` | `allow`, `remove` (like StyleCop's SA1512) | `allow` | [BRO1506](rules/BRO1506.md) |
| `stylebro_allow_empty_single_line_blocks` | `true`, `false` | `false` | [BRO1508](rules/BRO1508.md), [BRO1509](rules/BRO1509.md) |
| `stylebro_allow_consecutive_usings` | `true`, `false` | `true` | [BRO1514](rules/BRO1514.md) |
| `stylebro_allow_single_line_jump_statements` | `true`, `false` | `false` | [BRO1514](rules/BRO1514.md) |
| `stylebro_braces_statements` | `all`, `if_and_loops` (Sonar's S121: not `using`, `lock`, `fixed` or a deconstructing `foreach`) | `all` | [BRO1514](rules/BRO1514.md)-[BRO1516](rules/BRO1516.md) |
| `stylebro_arrow_placement_when_wrapping` | `end_of_line`, `beginning_of_line` | `end_of_line` | [BRO1521](rules/BRO1521.md) |
| `stylebro_equals_placement_when_wrapping` | `end_of_line`, `beginning_of_line` | `end_of_line` | [BRO1522](rules/BRO1522.md) |
| `stylebro_blank_line_between_switch_sections` | `include`, `omit`, `omit_after_block` | `include` | [BRO1526](rules/BRO1526.md) |
| `stylebro_document_exposed_elements` | `true`, `false` | `true` | [BRO1601](rules/BRO1601.md) |
| `stylebro_document_internal_elements` | `true`, `false` | `true` | [BRO1601](rules/BRO1601.md) |
| `stylebro_document_private_elements` | `true`, `false` | `false` | [BRO1601](rules/BRO1601.md) |
| `stylebro_inheritdoc_style` | `compact`, `spaced` | `compact` | [BRO1601](rules/BRO1601.md) |
| `stylebro_exclude_from_punctuation_check` | tag names, comma-separated | `seealso` | [BRO1603](rules/BRO1603.md) |
| `stylebro_file_header_company` | text | not set: the rule does nothing | [BRO1615](rules/BRO1615.md) |
| `stylebro_file_header_copyright` | text (`\n`, `{companyName}`, `{fileName}`) | StyleCop's copyright text | [BRO1615](rules/BRO1615.md) |
| `stylebro_file_header_decoration` | a line of text | none | [BRO1615](rules/BRO1615.md) |
| `stylebro_summary_layout` | `multi_line`, `single_line_when_fits` | `multi_line` | [BRO1616](rules/BRO1616.md) |
| `stylebro_layout_by` | `csharpier`, or not set | not set | [BRO1149](rules/BRO1149.md) lays a merged condition out as CSharpier would; [BRO1514](rules/BRO1514.md)-[BRO1516](rules/BRO1516.md) count a statement CSharpier will split as multi-line. `stylebro-migrate` writes it when the repository uses CSharpier ([getting started](getting-started.md)) |
| `stylebro_layout_print_width` | a number | `100` (CSharpier's) | with `stylebro_layout_by = csharpier`: CSharpier's `printWidth` |
Coming from StyleCop, [`stylebro-migrate`](migrating.md) writes these from your `stylecop.json` and rule settings.
---
Source: https://bisforboman.github.io/stylebro/baseline/
# Baseline: fail only on new violations
Turning StyleBro on in a large codebase usually reports hundreds of existing violations. `dotnet format` can fix them
all at once, but a team may not want that one big change (blame history, open pull requests, review time). A baseline
records today's violations so the build, the IDE and `dotnet format` ignore them, and only new code has to follow the
rules. Existing code gets fixed when someone touches it, or whenever the team chooses to regenerate the baseline.
## Creating one
```bash
dotnet tool install --global StyleBro.Migrate --prerelease
stylebro-migrate baseline
```
Run it in the folder with your solution (or name it, like `format`: `stylebro-migrate baseline src/MySolution.sln`, or
`--project`). It runs `dotnet format whitespace`, `style` and `analyzers --verify-no-changes`, each on its own (in one
run, the analyzers' lines would be counted after the whitespace pass split a line, and the baseline would point at the
wrong lines), to find every violation `dotnet format` would fix today and
writes them to `stylebro.baseline` in that folder (the current folder when you name a solution or project below it).
Commit the file.
```text
Wrote stylebro.baseline: 820 violations on 759 lines.
BRO1601 154
BRO1603 139
...
```
The StyleBro.Analyzers package picks up the nearest `stylebro.baseline` above each project, like `.editorconfig`.
Another location: `path/to/stylebro.baseline` in a `Directory.Build.props` (the
file must be called `stylebro.baseline`); `none` turns it off.
## What it covers
Every StyleBro rule, and the SDK rules `stylebro-migrate init` or `stylebro-migrate --write` turns on (IDE0003, IDE0009, IDE0011, IDE0036,
IDE0040, IDE0047, IDE0048, IDE0049, IDE0055, IDE0065, IDE0073, IDE2000, IDE2002, IDE2003). A baselined violation isn't
reported by the build or the IDE, and `dotnet format` leaves it alone.
**Not covered: whitespace formatting.** `dotnet format` (and `dotnet format whitespace`) formats whitespace directly,
without diagnostics, so nothing can hide it. Either run `dotnet format whitespace` once (whitespace-only changes are
easy to review), or check only `dotnet format style` and `dotnet format analyzers` in CI. `stylebro-migrate baseline`
says how many such formatting changes it found (whitespace, line endings, charset, final newline, using order:
`dotnet format` reports them as WHITESPACE, ENDOFLINE, CHARSET, FINALNEWLINE and IMPORTS). Files under `obj/` and `bin/`
(generated AssemblyInfo and global usings) don't count.
**Findings kept on purpose are covered too.** A rename StyleBro's fix leaves because it would break code only the fix
can see (the name read by reflection, used in generated code: see
[Findings kept on purpose](getting-started.md#findings-kept-on-purpose)) stays a warning after every `dotnet format`
run. The baseline records it like any other violation, so warnings as errors don't fail the build over it.
`stylebro-migrate format` lists these with their reasons; check them before you rename one by hand.
## How entries match
Each line of the file is a rule, a file (relative to the baseline), a fingerprint of the reported line's text, and how
many violations of that rule the line has:
```text
BRO1106 src/Orders/Invoice.cs 5d1e0c6b7f2a9e31 1
```
The fingerprint ignores where the line is and how it's indented, so edits elsewhere in the file and moved code keep
the entry. **Changing the line itself makes its violations new**: whoever edits a line fixes it (`dotnet format` does
that). Renaming or moving a file makes all of its entries stale, so its violations are reported again.
## Keeping it small
Regenerate the baseline whenever you like; it only ever lists what's still there. Entries for fixed code are harmless:
they only match a line with exactly that text, up to their count, so a stale baseline is just longer than it needs to be.
To pay off part of the debt, fix a folder (`dotnet format --include src/Orders`) and regenerate.
---
Source: https://bisforboman.github.io/stylebro/ci/
# StyleBro in CI
Two checks keep a repository clean. They overlap on purpose: the build catches what someone forgot to format, and
`dotnet format` proves that the code is exactly what the fixes produce.
1. **The build** reports StyleBro's rules as warnings (and the built-in .NET rules too, with
`EnforceCodeStyleInBuild`). With warnings as errors, a violation fails the build.
2. **`stylebro-migrate format --verify-no-changes`** fails (exit code 2) when formatting would change any file. It
also covers whitespace formatting, which isn't a build warning. Unlike plain `dotnet format --verify-no-changes`, it
doesn't fail on [findings kept on purpose](getting-started.md#findings-kept-on-purpose): renames StyleBro's fixes
leave because they'd break code only the fix can see (a name read by reflection, say). Those stay as build warnings,
so suppress or [baseline](baseline.md) them where warnings are errors.
Exit codes of `stylebro-migrate format --verify-no-changes`: **0** clean (nothing to change; kept findings are listed
with their reasons), **2** formatting would change a file (the output says which findings), anything else: `dotnet
format` itself failed. A script or an AI agent can loop "run `stylebro-migrate format`, then check" and stop on 0; with
plain `dotnet format --verify-no-changes` such a loop never ends while kept findings exist.
To catch problems before they're committed: [Pre-commit hook](pre-commit.md).
Fixing a failure is the same everywhere: run `stylebro-migrate format` (or `dotnet format`, see
[the notes](#notes) on other analyzers) locally and commit the result.
## Setup
```xml
true
true
```
Then, once, `stylebro-migrate init --write` (or `stylebro-migrate --write` when coming from StyleCop) to put the
built-in rules' severities into `.editorconfig`. They can't come from the package: `dotnet format` ignores rule
severities in a package's global config, so without them in `.editorconfig` the build would report rules
`dotnet format` doesn't fix.
Prefer `TreatWarningsAsErrors` alone. Don't list StyleBro rules in ``: `dotnet format` then skips
those rules entirely (see [BRO1001](rules/BRO1001.md)).
## GitHub Actions
```yaml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
- run: dotnet build MySolution.sln
- run: dotnet tool install --global StyleBro.Migrate --prerelease
- name: Code style
run: stylebro-migrate format MySolution.sln --verify-no-changes --severity warn --no-restore --report format-report
- name: Upload what dotnet format would change
if: failure()
uses: actions/upload-artifact@v7
with:
name: format-report
path: format-report
```
## Azure Pipelines
```yaml
steps:
- task: UseDotNet@2
inputs:
version: 10.0.x
- script: dotnet build MySolution.sln
displayName: Build
- script: dotnet tool install --global StyleBro.Migrate --prerelease
displayName: Install stylebro-migrate
- script: stylebro-migrate format MySolution.sln --verify-no-changes --severity warn --no-restore --report $(Build.ArtifactStagingDirectory)/format-report
displayName: Code style
- publish: $(Build.ArtifactStagingDirectory)/format-report
artifact: format-report
condition: failed()
```
## Notes
- **`--severity warn`** makes `dotnet format` apply and check everything at warning level, StyleBro's default.
- **`--no-restore`** after a build saves a second restore; `dotnet format` needs a restored solution either way.
- **`--report`** writes `format-report.json`: every file and line `dotnet format` would change and why (rule id or
`WHITESPACE`), kept findings included. Uploading it on failure shows what to fix without rerunning anything.
- **How `--verify-no-changes` tells kept findings apart:** when `dotnet format --verify-no-changes` reports findings,
`stylebro-migrate format` formats a temporary copy of the repository (the fixes only run when they may write) and
checks whether any file changed; the fixes write down which findings they kept and why. The copy is deleted, the
repository isn't touched. That costs a second format run, only when there are findings.
- **The same result on every OS:** without `end_of_line` in `.editorconfig`, `dotnet format`'s whitespace pass writes
the OS's line ending into the line breaks it rewrites (after `=>` followed by a comment, for example), so a CRLF file
checked on Linux, or an LF file on Windows, looked unformatted. `stylebro-migrate format` keeps each file's own
ending when the file used one throughout, and `--verify-no-changes` ignores findings that only swap it. Setting
`end_of_line` (`stylebro-migrate init` does when your files agree) picks one ending for every file instead.
- **Without the tool:** plain `dotnet format --verify-no-changes --severity warn` works too, but it fails on kept
findings. Suppress or baseline them first.
- **Other analyzers and compiler fixes.** Plain `dotnet format --verify-no-changes` also fails on every other analyzer
package's fixable warnings and on compiler fixes (CS8618's `required`), and locally plain `dotnet format` applies
them, which can change behavior or break the build. To check and fix only StyleBro's rules and the built-in rules
`stylebro-migrate init` turns on, name them: `dotnet format --diagnostics BRO1001 BRO1505 ... IDE0055 IDE0036
--verify-no-changes`. `stylebro-migrate format` (the .NET tool StyleBro.Migrate, installed in the examples above)
builds that list for you, locally and in CI.
- **Only some checks?** `dotnet format whitespace`, `dotnet format style` (built-in .NET rules) and
`dotnet format analyzers` (StyleBro and other analyzer packages) check one part each, with the same options.
- **How long it takes:** a check loads the whole solution, once per target framework in multi-targeted repositories.
Orleans (about 200 projects) took 11.5 minutes for `stylebro-migrate format --verify-no-changes` and 57 minutes
(about 9 GB of memory) for its first full `format`. Budget the job's timeout accordingly, or check only the projects
a change touches.
- **Kept findings and `TreatWarningsAsErrors`:** the findings StyleBro's fixes keep on purpose don't fail
`stylebro-migrate format --verify-no-changes`, but they are warnings, so they fail a build that treats warnings as
errors. Suppress or baseline them in the first run ([getting-started.md](getting-started.md#findings-kept-on-purpose)).
- **Old-style (non-SDK) projects:** restore with Visual Studio's MSBuild before the check
(`msbuild -t:restore -p:RestorePackagesConfig=true`) and keep the .NET Framework reference assemblies available:
[getting-started.md](getting-started.md#old-style-non-sdk-projects).
- **A large existing codebase:** commit a [baseline](baseline.md) so CI fails only on new violations. A baseline can't
hide whitespace formatting; either run `dotnet format whitespace` once, or check only `dotnet format style` and
`dotnet format analyzers` in CI until you do.
- **Several target frameworks:** use `stylebro-migrate format` instead of `dotnet format`, with the same options:
```
stylebro-migrate format MySolution.sln --verify-no-changes --severity warn
```
`dotnet format` loads one copy of every file per target framework, and the SDK's formatting fix (IDE0055) edits
each copy on its own: each sees other `#if` code, Roslyn can't merge the results, and `dotnet format` crashes
without writing anything (Serilog, Newtonsoft.Json; reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000) and [dotnet/roslyn#86001](https://github.com/dotnet/roslyn/issues/86001)). Its whitespace pass only formats the first framework's code ([dotnet/sdk#56585](https://github.com/dotnet/sdk/issues/56585)),
so code behind `#if` stays unformatted while the other frameworks' builds report it. `stylebro-migrate format` runs
`dotnet format` once per target framework, each time on the projects that target it, loaded for that framework
alone: nothing to merge, and every framework's `#if` code gets fixed (Serilog: 505 IDE0055 warnings to 0; with all
StyleBro rules, the second run changes nothing). A repository without multi-targeted projects gets one plain
`dotnet format` run. The SDK fixes that wrote conflict markers there (IDE0011, IDE0040, IDE0047/IDE0048,
IDE2000/IDE2002/IDE2003; reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000)) are replaced by StyleBro rules and off.
One limit: code that has to be indented differently for different frameworks, like an `else` inside `#if` followed
by a block after `#endif`, can't satisfy IDE0055 for all of them; the last framework's run wins and the build
reports it for the others (Newtonsoft.Json: 32 lines in 4 files). Restructure such code, or suppress IDE0055 there.
---
Source: https://bisforboman.github.io/stylebro/pre-commit/
# Pre-commit hook
A pre-commit hook runs StyleBro on the C# files you're about to commit, so a style problem never reaches CI. It uses
`stylebro-migrate format --files` (see [Scripts and AI agents](agents.md#format-files)), which loads only the projects
those files belong to.
There are two kinds of hook:
- **Check only** (recommended for a hand-written hook): `--verify-no-changes` changes nothing. When formatting would
change a staged file, the commit stops and says to run `stylebro-migrate format`.
- **Fix and stage**: format the staged files and `git add` them again. Husky.Net and lefthook do this safely, also for
files you staged only partly. A hand-written hook can't do that easily (see
[Partly staged files](#partly-staged-files)).
The examples call `stylebro-migrate` as a global tool (`dotnet tool install -g StyleBro.Migrate --prerelease`). With
the tool in the repository's tool manifest (`dotnet tool install StyleBro.Migrate --prerelease`), write
`dotnet stylebro-migrate` instead.
Every recipe here was tested on a repository with a multi-targeted library (stylebro-migrate 0.7.0-alpha.1,
Husky.Net 0.9.1, lefthook 2.2.2, Git Bash on Windows).
## A plain git hook
Save it as `.git/hooks/pre-commit`. It's POSIX sh, so it also runs in Git Bash on Windows (git runs hooks with it). On
Linux and macOS, make it executable too: `chmod +x .git/hooks/pre-commit`.
### Check only (recommended)
```sh
#!/bin/sh
# StyleBro: fail the commit when formatting would change a staged C# file.
git diff --cached --name-only --diff-filter=ACMR -z -- '*.cs' |
xargs -0 -r stylebro-migrate format --verify-no-changes --files || {
echo "StyleBro: formatting would change the files above." >&2
echo "Run 'stylebro-migrate format --files ...' (or 'stylebro-migrate format'), review, git add, commit again." >&2
exit 1
}
```
A commit stopped by it looks like this:
```
C:\src\shop\src\Orders\My Order.cs(5,36): warning BRO1509: Put 'Empty' on separate lines
Not clean: formatting would change 1 file. Run 'stylebro-migrate format'.
StyleBro: formatting would change the files above.
Run 'stylebro-migrate format --files ...' (or 'stylebro-migrate format'), review, git add, commit again.
```
It never writes a file, so edits you didn't stage stay unstaged. It checks the files as they are in your working
folder, though, not the staged version: an unstaged edit that needs formatting stops the commit too, and a staged
problem that an unstaged edit already fixed slips through (CI still catches that).
### Fix and stage
```sh
#!/bin/sh
# StyleBro: format the staged C# files, then stage the result.
staged() { git diff --cached --name-only --diff-filter=ACMR -z -- '*.cs'; }
# 'git add' would also stage edits you left unstaged: stop instead.
partial=$(staged | xargs -0 -r git --literal-pathspecs diff --name-only --)
if [ -n "$partial" ]; then
echo "StyleBro: these files also have unstaged changes:" >&2
echo "$partial" >&2
echo "Stage the whole files (git add), or set the other edits aside, then commit again." >&2
exit 1
fi
staged | xargs -0 -r stylebro-migrate format --files || exit 1
staged | xargs -0 -r git --literal-pathspecs add --
```
The commit then contains the formatted files. A file you staged only partly stops the commit instead: re-adding it
would stage your other edits too.
## Husky.Net
[Husky.Net](https://alirezanet.github.io/Husky.Net/) is a .NET tool. Set it up once:
```
dotnet new tool-manifest
dotnet tool install Husky
dotnet tool install StyleBro.Migrate --prerelease
dotnet husky install
dotnet husky add pre-commit -c "dotnet husky run --group pre-commit"
```
Then add the task to `.husky/task-runner.json`:
```json
{
"$schema": "https://alirezanet.github.io/Husky.Net/schema.json",
"tasks": [
{
"name": "stylebro",
"group": "pre-commit",
"command": "dotnet",
"args": ["stylebro-migrate", "format", "--files", "${staged}"],
"include": ["**/*.cs"]
}
]
}
```
`${staged}` stages the files again after the task. For a partly staged file, Husky.Net writes the staged version to a
temporary copy next to it (`1a2b3_Order.cs`), formats both, and stages the formatted copy's content: the commit gets
the formatted staged version, and your other edits stay unstaged (the working file is formatted as well). While the
task runs, the project compiles both copies of that file (the same types twice), so a fix that needs the semantic
model, like a rename, may not apply to it; the check in CI shows anything left.
For a check only, add `"--verify-no-changes"` to `args`.
## lefthook
[lefthook](https://lefthook.dev/) is a single binary (from its GitHub releases, npm, Homebrew, ...). `lefthook.yml`:
```yaml
pre-commit:
commands:
stylebro:
glob: "*.cs"
run: stylebro-migrate format --files {staged_files}
stage_fixed: true
```
Then `lefthook install`. `stage_fixed: true` runs `git add` on the files after the command. For partly staged files,
lefthook sets the unstaged edits aside before the command and puts them back afterwards, so the commit gets the
formatted staged version and your other edits stay unstaged. For a check only, add `--verify-no-changes` and drop
`stage_fixed`.
## Partly staged files
`git add -p` stages part of a file. Re-adding the file after formatting would stage the rest too, and the commit would
contain edits you meant to leave out.
| Hook | Partly staged file |
|---|---|
| Plain hook, check only | Nothing is written; the commit stops if the working file needs formatting. |
| Plain hook, fix and stage | Stops and names the file. |
| Husky.Net (`${staged}`) | Formats the staged version in a temporary copy; other edits stay unstaged. |
| lefthook (`stage_fixed`) | Sets other edits aside while formatting; they stay unstaged. |
Auto-fixing is fine with Husky.Net and lefthook, and with the plain hook when you always stage whole files.
## Speed
Every commit with C# files loads the projects they belong to with MSBuild. Measured on a small library (three target
frameworks) and its test project:
| Commit | Time |
|---|---|
| No C# files staged | instant (nothing runs) |
| Check only, clean | 7 to 12 s |
| Check only, a file needs formatting | about 25 s (a second run on a copy tells [kept findings](getting-started.md#findings-kept-on-purpose) apart) |
| Fix and stage | 20 to 35 s (formats until a run changes nothing) |
A project with several target frameworks is formatted once per framework, so each framework adds a load. Files of
several projects cost one run per project. Large solutions take longer: see the measurements in
[Scripts and AI agents](agents.md#format-files). If that's too slow for every commit, use the check-only hook and
leave the rest to [CI](ci.md).
Findings StyleBro keeps on purpose (a rename that would break reflection, ...) don't fail `--verify-no-changes` and
aren't changed by a fix run, so they never stop a commit. They stay build warnings.
## File names with spaces
The plain hooks pass file names with `git diff -z` and `xargs -0`, so spaces and other unusual characters survive
(`git --literal-pathspecs` keeps `[` and `*` in a name from being read as patterns). Don't loop over
`$(git diff --name-only)`: the shell splits names at spaces. Husky.Net passes each file as its own argument, and lefthook
quotes the names it puts into `{staged_files}`. All four were tested with `src/My Helper.cs`.
---
Source: https://bisforboman.github.io/stylebro/agents/
# Using StyleBro from scripts and AI agents
`stylebro-migrate` has three things for scripts and AI coding agents: a JSON result for every command (`--json`), a
fast format of just the files an edit touched (`format --files`), and, on request (`--agents-md`), a section in the
repository's `AGENTS.md` that tells an agent how to work with StyleBro. The site also has an index for agents:
[llms.txt](https://bisforboman.github.io/stylebro/llms.txt) (every page and rule with one line each) and
[llms-full.txt](https://bisforboman.github.io/stylebro/llms-full.txt) (all pages in one file).
## The loop for an agent
```
stylebro-migrate format --files src/Orders/OrderService.cs src/Orders/Order.cs # after an edit: seconds
stylebro-migrate format --verify-no-changes # before finishing: exit 0 = clean
```
- `format --files` fixes StyleBro's findings (and the built-in rules `init` turns on, plus whitespace) in the named
files only, loading just the project each file belongs to. It repeats until a run changes nothing, like `format`.
- `--verify-no-changes` changes nothing and exits 0 when formatting would change no file. Findings StyleBro's fixes
keep on purpose don't fail it ([Findings kept on purpose](getting-started.md#findings-kept-on-purpose)); plain
`dotnet format --verify-no-changes` does fail on them. It takes `--files` too.
- Don't hand-fix the BRO warnings the format leaves: each is kept for a reason (a rename would break reflection, a
`nameof`, generated code, ...). `--json` lists them with the reason.
Exit codes: 0 clean (only kept findings left, if any); 2 a run still changed files after 3 runs, or with
`--verify-no-changes`, formatting would change a file; 1 a usage error (a file not found, no project above a file);
anything else is `dotnet format`'s.
## `format --files`
```
stylebro-migrate format --files [ ...] [--verify-no-changes] [dotnet format options]
stylebro-migrate format --files [ ...]
```
Paths are relative to the current folder, or else to the git repository's root. Without a solution or project named,
each file is formatted in the nearest project file above it (files of several projects: one run per project); with one
named, every file must be under its folder. A file a project includes from somewhere else (``) is looked up by folder only: name the project.
In a git pre-commit hook (plain, Husky.Net or lefthook), `--files` checks or fixes just the staged files:
[Pre-commit hook](pre-commit.md).
Measured (2026-10-10) right after setting StyleBro up, each run until clean (two `dotnet format` runs):
| Repository | Whole solution | `--files`, 2 files | `--files --verify-no-changes` |
|---|---|---|---|
| Jellyfin (41 projects, one target framework) | 169 s | 20 s | 10 s |
| Serilog (the library targets 7 frameworks: one `dotnet format` per framework) | 153 s | 83 s | |
Most of the time is loading projects: a file of a project with many target frameworks costs one load per framework.
## `--json`
Every command takes `--json`: `init`, the StyleCop migration (`stylebro-migrate [path]`, dry run and `--write`), the
`--diff` previews and `format` (with `--verify-no-changes` and `--files` too). The result is one JSON object on stdout;
everything the command would print for people goes to stderr instead, so `stylebro-migrate format --json > result.json`
keeps the text on the screen and the object in the file.
```json
{
"schemaVersion": 1,
"command": "format",
"previewOf": null,
"path": "C:\\src\\shop",
"write": false,
"settings": [],
"conventions": [],
"sonar": null,
"agentsMd": null,
"runs": [
{ "run": 1, "filesChanged": [ "src/Orders/Order.cs" ], "changesPerRule": { "BRO1303": 2, "IDE0055": 1 } },
{ "run": 2, "filesChanged": [], "changesPerRule": {} }
],
"changesPerRule": { "BRO1303": 2, "IDE0055": 1 },
"filesPerRule": null,
"keptFindings": [
{
"path": "src/Orders/Order.cs",
"line": 12,
"column": 17,
"rule": "BRO1303",
"reason": "NameInString",
"reasonText": "the name is in a string in the solution (reflection, serialization): renaming would compile but break at run time",
"message": "Rename '_total' to 'total'"
}
],
"patch": null,
"clean": true,
"exitCode": 0
}
```
Every field is always there (empty or `null` where it doesn't apply). Paths in the object are relative to `path`, with
`/`.
| Field | Type | Meaning |
|---|---|---|
| `schemaVersion` | number | `1`. Raised when a field changes meaning or goes away; new fields don't raise it. |
| `command` | string | `init`, `migrate`, `format`, `preview` (`--diff`) or `baseline`. |
| `previewOf` | string or null | For `preview`: `init`, `migrate` or `format`. |
| `path` | string | The folder the command ran on (full path); with `format --files`, the git repository's root. |
| `write` | bool | `init`/`migrate`: whether files were written (`--write`). Always `false` for a preview (it writes to a copy). |
| `settings` | array | `init`/`migrate`/`preview`: every setting of the blocks written (or that `--write` would write): `file` (relative), `section` (`*.cs`, a folder's glob, ...), `key`, `value`, `why` (the comment above it, or `null`). |
| `conventions` | array | `init`: each convention found in the code: `key`, `what`, `counts` (value -> places), `verdict` (`kept`, `default`, `mixed`, `off`, `set`, `tooFew`), `result` (the text after the verdict in the report: the value, the rule turned off, or why the default stays). |
| `sonar` | object or null | With a SonarQube setup: `sources`, `rulesOn`, `fixed` (`sonar`, `by`), `notApplied` (`sonar`, `reason`), `notes`. |
| `agentsMd` | object or null | `init`/`migrate`: `file`, `created`, `changed`, `written` (null without `--agents-md`). |
| `runs` | array | `format`/`preview`: each `dotnet format` run: `run` (1-based), `filesChanged`, `changesPerRule` (`format` only; `{}` in a preview). With `--verify-no-changes`: at most one entry, the files formatting would change. |
| `changesPerRule` | object | Rule id -> changes, all runs together (`format`: from `dotnet format`'s report; `preview`: what the check before the fixes found). Whitespace counts as `IDE0055`. |
| `filesPerRule` | object or null | `preview`: rule id -> the files it changed (`IDE0055` for whitespace-only files, `other` when nothing was reported). |
| `keptFindings` | array | `format`: findings StyleBro's fixes keep on purpose: `path`, `line`, `column` (1-based), `rule`, `reason` (one of the names below), `reasonText`, `message`. |
| `patch` | string or null | `preview`: the patch file (full path). |
| `clean` | bool or null | `format`: no file is left to change (exit code 0). `preview`: the last run changed nothing. `null` for `init`/`migrate`. |
| `exitCode` | number | The process's exit code. |
`reason` is one of: `NameInString`, `NameInNameof`, `OtherPropertyKept`, `PublicApi`, `ImplementsAnotherMember`,
`DerivedMemberHasNewName`, `NewNameTaken`, `NewNameMeansSomethingElse`, `GeneratedReference`, `ReferenceNotByName`,
`AddsErrors`, `DisabledCode`, `NamespaceFromOutside`, `DerivedTypeInAnotherProject`,
`UsedInProjectNotLoaded`, `NothingToRename`, `MembersReadByPosition`, `AddsDeconstruction`, `KeptWithOtherElement`
(what each means: `reasonText`).
## AGENTS.md
It's opt-in: with `--agents-md`, `stylebro-migrate init --write` and `stylebro-migrate --write` put a short StyleBro
section into the repository's `AGENTS.md` (created when there's none), between ``; running the command again with `--agents-md` replaces it, and leaves the rest of the file
alone. Without `--write` the commands print the section, and `--diff --agents-md` includes it in the patch. Without
`--agents-md` nothing is written; the commands print one tip line:
```
Tip: --agents-md writes a StyleBro section into AGENTS.md for AI coding agents.
```
`AGENTS.md` is the file most coding agents read. With `--agents-md`, a repository with only a `CLAUDE.md` gets an
`AGENTS.md` too, and the command says to add a line `@AGENTS.md` to `CLAUDE.md` (Claude Code reads `CLAUDE.md` and imports what it names).
The section:
```markdown
## StyleBro (C# style)
This repository uses [StyleBro](https://bisforboman.github.io/stylebro/): analyzers whose fixes `dotnet format` applies.
- After editing C# files, run `stylebro-migrate format --files ` (seconds; the tool is the
StyleBro.Migrate package: `dotnet tool install -g StyleBro.Migrate`).
- Before finishing, `stylebro-migrate format --verify-no-changes` must exit 0. It does when only findings kept on
purpose are left.
- Don't hand-fix BRO warnings the format leaves: they are kept on purpose (a rename would break reflection, `nameof`,
generated code, ...); the format output gives each one's reason. Leave them unless asked.
- Settings live in `.editorconfig` (the `stylebro-migrate` block); don't add `#pragma` or `[SuppressMessage]` for
StyleBro rules unless asked.
- `--json` gives machine-readable output. Docs for agents: https://bisforboman.github.io/stylebro/agents/ (index:
https://bisforboman.github.io/stylebro/llms.txt).
```
---
Source: https://bisforboman.github.io/stylebro/modernizing/
# Modernizing code for newer runtimes
`stylebro-migrate init --modernize` turns on the .NET SDK's own rules that rewrite code into newer C# and newer APIs:
target-typed `new()`, collection expressions, `is not null`, file-scoped namespaces, `ArgumentNullException.ThrowIfNull`,
ranges, and more. StyleBro adds no rules of its own here: the SDK has a fixer for each that `dotnet format` applies.
What `init` adds is the choice of which rules are safe in your repository, and the package adds the
[multi-target guard](#the-multi-target-guard), which keeps the newer-API rules out of projects where a target framework
lacks the API.
```
stylebro-migrate init --modernize # shows the blocks and notes
stylebro-migrate init --modernize --write # adds them to .editorconfig
dotnet format
```
It's opt-in: plain `init` doesn't write these rules, and StyleBro's preset doesn't turn them on. The rules go in their own
block in the root `.editorconfig`, between `# BEGIN stylebro-modernize` and `# END stylebro-modernize`. Running the
command again replaces it, and it stays when you run `init` without `--modernize` or `stylebro-migrate --write`. Keys your
root `.editorconfig` already sets for C# (a rule's severity, `csharp_style_namespace_declarations`) are left out, so
your settings stay. The CA rules need the .NET analyzers, which are on by default from .NET 5; set
`true` for projects that target only .NET Framework or .NET Standard.
## The tiers
| Tier | Needs | Rules | Severity |
|---|---|---|---|
| A | C# 7.3 or older, no new API | IDE0016-IDE0020, IDE0029, IDE0030, IDE0041, IDE0054, IDE0180, IDE0240, IDE0241, IDE0250, IDE0270, CA1825, CA1829, CA1834 | warning |
| B | a newer C# version | IDE0028, IDE0031, IDE0062, IDE0074, IDE0078, IDE0083, IDE0090, IDE0150, IDE0161 (with `csharp_style_namespace_declarations = file_scoped`), IDE0170, IDE0300-IDE0304, IDE0306, IDE0340, IDE0350, IDE0360 | warning, or suggestion when a multi-targeted project doesn't set `LangVersion` |
| C | a newer API | CA1510-CA1513, CA1847, CA1850, CA1864, CA1865, CA1872, CA2249, CA2263, IDE0056, IDE0057, IDE0330 | warning; the [multi-target guard](#the-multi-target-guard) hides them where a framework lacks the API |
`dotnet format` fixes warnings, not suggestions (its default `--severity` is `warn`), so a suggestion only shows up in
the IDE. The command prints a note for every tier it turned down, with the reason and how to turn it on.
IDE0031 (`?.`) was tier A until SDK 10: its fix now also writes C# 14's null-conditional assignment
(`if (node != null) { node.Count = 2; }` -> `node?.Count = 2;`), and in a `net48;net10.0` project without `LangVersion`
the net48 build failed with CS8370 (probed). IDE0350 (SDK 10, `(int x) => x + 1` -> `x => x + 1`) is tier B because
lambdas with modifiers (`(out int r)` -> `(out r)`) need C# 14. It leaves the parentheses out where it can, so it ends
where StyleBro's BRO1136 (`(x) => x` -> `x => x`) would, and one `dotnet format` run is enough (probed). When the SDK
is updated, check tier A the same way.
## Examples
Tier A:
```csharp
// Before
if (ReferenceEquals(value, null)) { return; } // IDE0041
int[] empty = new int[0]; // CA1825
builder.Append("a"); // CA1834
count = count + 1; // IDE0054
// After
if (value is null) { return; }
int[] empty = Array.Empty();
builder.Append('a');
count++;
```
Tier B:
```csharp
// Before
namespace App
{
class Cache
{
private readonly object gate = new object(); // IDE0090
private int[] sizes = new int[0]; // IDE0300
bool Check(object o) => !(o is string); // IDE0083
}
}
// After
namespace App; // IDE0161
class Cache
{
private readonly object gate = new();
private int[] sizes = [];
bool Check(object o) => o is not string;
}
```
Tier C:
```csharp
// Before
if (name == null)
{
throw new ArgumentNullException(nameof(name)); // CA1510
}
var inner = text.Substring(1, text.Length - 2); // IDE0057
var kind = (Kind)Enum.Parse(typeof(Kind), value); // CA2263
// After
ArgumentNullException.ThrowIfNull(name);
var inner = text[1..^1];
var kind = Enum.Parse(value);
```
## Multi-targeted projects
In a project with `net48;net8.0` every file is compiled once per framework, and
the rules run in each. They check what that compilation has: `ArgumentNullException.ThrowIfNull` exists for net8.0
and not for net48, so CA1510 reports only in the net8.0 copy, `dotnet format` applies the fix to the shared file, and
the net48 build breaks. CA1847 is worse: `Contains('a')` still compiles on net48, but binds to LINQ's
`Enumerable.Contains` there. Probed on the SDK 10 analyzers: 14 of 15 API rules broke the net48 build.
The language rules have the same problem only when `LangVersion` isn't set: the SDK then picks a C# version per
framework (7.3 for .NET Framework and netstandard2.0), the rules fire only for the newer one, and the older one gets
CS8370. With `latest` (or any explicit value) in the project or a `Directory.Build.props`
it imports, every framework uses the same version and the rules are safe. `init` checks the project file and the
`Directory.Build.props` files it imports. Single-target projects are always safe: the rules only fire when the
project's framework and C# version have the feature.
For the API rules StyleBro has the multi-target guard, so tier C is at warning in every repository.
## The multi-target guard
`MultiTargetSuppressor` (in the StyleBro.Analyzers package) hides a tier C diagnostic when the project targets several
frameworks and one of them lacks the API the rule's fix writes. A hidden diagnostic is neither reported by the build nor
fixed by `dotnet format`: in a `net48;net8.0` project `throw new ArgumentNullException(...)` stays, while a
single-target `net8.0` project in the same repository gets `ArgumentNullException.ThrowIfNull`. Single-target projects
are left to the rule itself, which only reports when the project's framework has the API.
The package's build targets pass the project's framework list to the compiler as `StyleBroTargetFrameworks` (the
evaluated `TargetFrameworks` with `,` for `;`, so a list set in an imported `.props` counts too; every per-framework
build sees the whole list). Each rule has a minimum: the first .NET (Core) and .NET Standard version with every API its
fix can write.
| Rule | Fix writes | .NET (Core) | .NET Standard |
|---|---|---|---|
| CA1510 | `ArgumentNullException.ThrowIfNull` | 6.0 | none |
| CA1511 | `ArgumentException.ThrowIfNullOrEmpty` | 7.0 | none |
| CA1512 | `ArgumentOutOfRangeException.ThrowIfNegative`, ... | 8.0 | none |
| CA1513 | `ObjectDisposedException.ThrowIf` | 7.0 | none |
| CA1847 | `string.Contains(char)` | 2.1 | 2.1 |
| CA1850 | static `HashData` (SHA/MD5 5.0, HMAC 6.0) | 6.0 | none |
| CA1864 | `Dictionary.TryAdd` | 2.0 | 2.1 |
| CA1865 | `StartsWith(char)`, `EndsWith(char)`, `IndexOf(char, StringComparison)` | 2.1 | 2.1 |
| CA1872 | `Convert.ToHexString` (5.0), `ToHexStringLower` (9.0) | 9.0 | none |
| CA2249 | `string.Contains(string, StringComparison)` | 2.1 | 2.1 |
| CA2263 | generic overloads (`Enum.Parse` 2.0, `Enum.GetValues` 5.0) | 5.0 | none |
| IDE0056 | `a[^1]` (`System.Index`) | 3.0 | 2.1 |
| IDE0057 | `s[1..^1]` (`System.Range`) | 3.0 | 2.1 |
| IDE0330 | `System.Threading.Lock` | 9.0 | none |
.NET Framework has none of them, and a framework the guard doesn't recognize (`monoandroid`, `uap`, ...) counts as
lacking everything. A minimum is the newest API the fix can write, so some safe fixes are hidden too (CA1872 in a
`net8.0;net9.0` project, where only the lowercase variant needs .NET 9).
What it doesn't see:
- CA2263 covers any method with a generic overload; the table knows the base library's. A package whose generic
overload exists for only some of the project's frameworks isn't known.
- Projects without the StyleBro.Analyzers package get no guard.
- It works per build: a framework added later hides the rules again, one removed lets them fix.
- To turn it off for a project, set `StyleBroTargetFrameworks` to a single framework (that is how
`scripts/verify-package.ps1` checks that the rules do fire there without it).
## A second run
Some fixes produce code another rule then rewrites, and `dotnet format` applies them in one pass in no fixed order:
CA2249 writes `s.Contains("x", StringComparison.CurrentCulture)`, which CA1847 turns into `Contains('x', ...)` on the
next run; with every rule on, IDE0041 was once left for the next run; fixers write new blocks on one line, which the
formatter wraps on the next run. Run `dotnet format` until a run changes nothing (usually two).
## Left out
Opt-in by hand (add `dotnet_diagnostic..severity = warning` outside the block), debated or with side effects:
| Rule | Why not by default |
|---|---|
| IDE0290 | Primary constructors: debated style; fields become captured parameters. |
| IDE0066 | Switch statement to switch expression: debated readability. |
| IDE0063 | Simple `using` declaration: changes when the object is disposed (end of the scope). |
| IDE0305 | `x.ToList()` to `[.. x]`: debated readability. |
| IDE0251 | `readonly` struct members: together with IDE0250 (readonly struct) in one run it adds a redundant `readonly` to members of the now-readonly struct. Turn it on after a run with IDE0250 if you want it. |
Skipped:
| Rule | Why |
|---|---|
| CA1866, CA1867 | No fixer (the SDK's fixer handles CA1865 only), so `dotnet format` can't apply them. |
| IDE0038 | No Fix All, so `dotnet format` can't apply it. |
| CA1845, CA1846 | Span-based performance rewrites, not modernization; they overlap IDE0057. |
| IDE0230 | UTF-8 string literals: niche, and the rewrite (`"ab"u8.ToArray()`) isn't nicer. |
| IDE0320 | `static` lambdas: noise. |
| CA1860 | `Any()` to `Count != 0`: many find `Any()` clearer. |
---
Source: https://bisforboman.github.io/stylebro/versioning/
# Versioning
StyleBro.Analyzers and StyleBro.Migrate share one version number. What changed in each release:
[CHANGELOG.md](https://github.com/bisforboman/stylebro/blob/main/CHANGELOG.md) (the same text as the
[GitHub Releases](https://github.com/bisforboman/stylebro/releases)).
## Before 1.0
The current releases are alphas (`0.x.y-alpha.n`). Anything may change between them: rules, settings, defaults and
what a fix writes. Read the release notes before upgrading.
## From 1.0
| Release | May | Never |
|---|---|---|
| Patch (`1.0.x`) | fix bugs, including false positives and wrong fixes; make a rule report less | make a rule report more |
| Minor (`1.x`) | add rules and settings | turn a new rule on by itself (see [New rules](#new-rules)) |
| Major (`x.0`) | change defaults, remove settings, change what existing rules fix | |
### Settings and rule ids
- `stylebro_*` keys are kept. A renamed key keeps working under its old name, with a deprecation note, for at least one
minor release.
- Rule ids (`BROxxxx`) are never reused, also when a rule is removed.
### New rules
Rules added after 1.0 stay off until you opt in through a preset version, like .NET's `AnalysisLevel`:
```xml
1.1
latest
```
So upgrading the package never breaks a build by itself: you raise the preset version when you're ready for the new
rules (each release's notes list them).
- Unset means the package's major version: `1.0` in all of 1.x.
- `stylebro-migrate init --write` pins the newest version it knows in `Directory.Build.props`, so a new setup gets
every rule at once.
- With `none` (also after `stylebro-migrate --write`), rules added after 1.0 are off
too: they are off by default, and only the preset turns them on. To turn on a single one,
`dotnet_diagnostic.BROxxxx.severity = warning` in `.editorconfig`.
- A version newer than the package warns (STYLEBRO002) and uses the package's newest; anything else fails the build
(STYLEBRO001).
Before 1.0 every rule counts as a 1.0 rule: `1.0`, `latest` and unset are the same.
## When 1.0 ships
After two clean trials in a row: first runs on new kinds of repositories with no high-severity bugs.
---
Source: https://bisforboman.github.io/stylebro/stylebro-vs-stylecop/
# StyleBro vs StyleCop.Analyzers
For a team deciding whether to switch. Measured numbers only; each one says where it comes from. As of
**2026-10-05** (StyleBro's `main`; StyleCop.Analyzers 1.2.0-beta.556, its latest release).
## Summary
| | StyleCop.Analyzers 1.2.0-beta.556 | StyleBro (`main`, 2026-10-05) |
|---|---|---|
| Fixes under `dotnet format` | Many rules have no fix, no Fix All, or a fix that misbehaves there ([below](#what-it-is)) | Every rule has a fix with Fix All; one run converges |
| StyleCop's 197 diagnostics | All | 106 by StyleBro rules, 45 by the .NET SDK's rules, 42 dropped by design, 4 variants or not applicable |
| Rules beyond StyleCop | None | 26 (most from Roslynator, Meziantou, Sonar or StyleCop's own tracker) |
| Analyzer time, Newtonsoft.Json (242 files) | 1,291-2,171 ms (182 analyzers) | 411-687 ms (66 analyzers); 3.1-3.4x faster in each of 3 runs ([Speed](#speed)) |
| StyleCop's open bugs on the replaced rules | 54 checked | 34 not shared, 8 shared (7 fixed, 1 kept on purpose), 12 not applicable or the same by design |
| Moving a StyleCop-clean repo | (the starting point) | Files changed after `stylebro-migrate --write` + `dotnet format`: Polly 1, OpenTelemetry 1, a private app 7; only 1 of those 9 is a StyleBro fix ([Migration](#migration)) |
| Baseline (fail only on new violations) | No | Yes (`stylebro-migrate baseline`) |
| Multi-targeted projects | Not measured | Fix All merges each target framework's copy itself (no conflict markers in Newtonsoft.Json, 8 frameworks); `stylebro-migrate format` works around the SDK's IDE0055 crash |
| Release status | 1.2.0-beta.556 (beta for years; last stable 1.1.118) | 0.7.0-alpha.1 on nuget.org (129 rules), a prerelease |
## What it is
StyleBro is a set of Roslyn analyzers whose every rule has a code fix that `dotnet format` applies, with Fix All, so
`dotnet format` fixes everything StyleBro reports. Fixes are deterministic and idempotent: after one run, a second run
changes nothing (the CI check for this repository and the [real-world check](#correctness) both test that). Where a fix
could be unsafe (it would change behavior, lose a comment, or depend on `#if` code), StyleBro skips the case instead of
reporting something it can't fix.
Formatting the SDK already handles (spacing, indentation, using order, modifier order, ...) isn't duplicated: StyleBro's
preset sets the SDK's options and `stylebro-migrate init` turns the SDK rules on.
StyleCop's fixes, run under `dotnet format`, from [differences-from-stylecop.md](differences-from-stylecop.md#same-reports-different-fix):
- **No fix, or none that works there:** SA1108, SA1114, SA1115, SA1117, SA1125, SA1612/SA1613/SA1620/SA1621/SA1627;
SA1141's fix has no Fix All and SA1142's throws.
- **Renames:** the naming rules (SA1300-SA1314) lower or remove only the first letter or prefix (`MAX_VALUE` -> `MAXVALUE`), and under
`dotnet format` apply part of the renames per run or nothing; SA1316 renames the declaration only, so every use
breaks the build.
- **Fixes that lose or mangle code:** SA1132 keeps attributes on the first field only (`[Obsolete]` disappears from the others);
SA1501/SA1502 write `{` at column 1 in nested blocks and CRLF into LF files; SA1504 collapses or expands the accessors
of every property in the project the same way; SA1633 deletes a plain comment header (license
text) when it writes the XML header.
- **More runs:** nested initializers need several runs for SA1413 (StyleCop issue
[#3953](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3953): three); SA1120 and SA1103 leave work for
the next run.
## Coverage
StyleCop 1.2 has 197 diagnostics ([stylecop-mapping.md](stylecop-mapping.md), [backlog.md](https://github.com/bisforboman/stylebro/blob/generated/docs/backlog.md#summary)):
| | Diagnostics | |
|---|---:|---|
| StyleBro rule | 106 | 92 BRO rules replace them; differences listed in [differences-from-stylecop.md](differences-from-stylecop.md) |
| .NET SDK rule | 45 | Spacing, indentation, using order, modifier order and more: the preset sets the options, `stylebro-migrate init` the severities |
| Dropped: no safe automatic fix | 32 | [skipped-rules.md](skipped-rules.md#dropped-no-safe-automatic-fix) |
| Dropped: StyleCop never reports it | 10 | SA1109, SA1126, SA1301, SA1409, SA1628, SA1630-SA1632, SA1644, SA1650 |
| Variants, not applicable | 4 | SA1119_p, SX1309S, SA0001, SA0002 |
Why the 32 are dropped ([skipped-rules.md](skipped-rules.md) has every one, with what revisiting would take):
- **They need text only a person can write.** Missing documentation (SA1601, SA1602, SA1604-SA1611, SA1614-SA1616,
SA1618, SA1619, SA1622): the only automatic fix is placeholder text, which satisfies the rule and documents nothing
(SA1600 alone: ~28,000 findings in the three surveyed StyleCop repos). StyleBro adds `/// ` to
overrides and interface implementations, where that's a real fix (BRO1601).
- **They need code moved between files or files renamed** (SA1402, SA1403, SA1649), which `dotnet format` can't do.
- **They have no deterministic fix:** SA1118 (where to break a multi-line argument), SA1401 (fields should be private),
SA1404-SA1406 (justification and message text), SA1414 (tuple element names), SA1625, SA1645-SA1648 and others.
## Speed
Measured 2026-10-05 with [scripts/benchmark](https://github.com/bisforboman/stylebro/blob/main/scripts/benchmark/README.md) in `compare` mode on Newtonsoft.Json's main
project (`Src/Newtonsoft.Json` at `52fa3ae`, 242 files): both analyzer DLLs load into one process and take turns on
freshly parsed sources (which goes first alternates, garbage collection before each), single-threaded, compiler
telemetry per analyzer, each analyzer's fastest of 20-30 runs, summed. StyleCop: `StyleCop.Analyzers.dll` from the
StyleCop.Analyzers.Unstable 1.2.0.556 package (what StyleCop.Analyzers 1.2.0-beta.556 installs). StyleBro: a Release
build of `src/StyleBro.Analyzers` from `main` (`fb430c4`). Each tool runs with its default rule set and no
configuration.
| Run | StyleCop.Analyzers (182 analyzers, 197 diagnostics) | StyleBro (66 analyzers, 118 rules) | Ratio |
|---|---:|---:|---:|
| 1 | 2,171 ms | 687 ms | 3.2x |
| 2 | 1,467 ms | 435 ms | 3.4x |
| 3 | 1,291 ms | 411 ms | 3.1x |
Other builds were running on the machine at the same time, so the totals swing by up to 70 % between runs; the
alternation exposes both tools to the same noise, and the ratio held. StyleCop's slowest: SA1121 (159 ms), SA1101
(143 ms), SA1027 (109 ms). StyleBro's: FieldNamingAnalyzer (72 ms), DocumentationAnalyzer (66 ms),
CommentSpacingAnalyzer (59 ms) (run 1).
Not the same work: StyleCop's number includes spacing and indentation rules that StyleBro leaves to the SDK (IDE0055 and
the other rules `init` turns on), whose cost isn't in StyleBro's number. In a real build analyzers run concurrently with
the compiler, so the wall-clock difference is smaller: a private app built in the same time with and without StyleBro
(9-12 s either way), Newtonsoft.Json 8.4 -> ~11.8 s with every StyleBro rule (CLAUDE.md, 2026-10-02, alpha.8). An
earlier figure of 1,671 ms for StyleCop came from the benchmark before it parsed fresh sources per run and isn't
comparable.
## Correctness
- **StyleCop's open bugs** ([differences-from-stylecop.md](differences-from-stylecop.md#stylecops-open-bugs),
2026-10-04): every open issue on a rule StyleBro replaces was read; the 54 that describe behavior StyleBro could share
were run against StyleBro. 34 aren't shared, for example: SA1201's fix moves a method into `#if DEBUG`
([#1588](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1588)), SA1309's rename doesn't compile
([#2939](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2939)), SA1135 crashes on `using X = string;`
([#3882](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3882)), fixes write CRLF into LF files
([#3656](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3656)). 8 were shared: 7 fixed, 1 kept like
StyleCop by the owner's decision.
- **Parity with StyleCop:** 42 parity sets run both tools on edge-case files and compare positions and fixed output;
every difference must be a documented deviation ([scripts/stylecop-survey](https://github.com/bisforboman/stylebro/tree/main/scripts/stylecop-survey)). StyleCop's
unreleased `master` was compared too: where it fixed a gap, StyleBro follows it.
- **Real-world check on every pull request:** every rule on 8 public repositories (FFMpegCore, Polly, OpenTelemetry,
Newtonsoft.Json, Serilog, Jellyfin, FluentValidation, CsvHelper; `scripts/realworld/repos.psd1`). A pull request fails
on an analyzer crash, a new compile error, a merge conflict marker, more than 2 `dotnet format` runs to converge, or a
test that passed on the untouched code and fails after the fixes. Running the tests found what compiling didn't:
renames that broke reflection and serialization, member sorting that changed serialized JSON order. Those cases are
now skipped; one known failure is left (Newtonsoft.Json's `MemberSearchFlags` serializes an attribute-less class's
private fields by reflection, in declaration order).
- **Mutation testing:** 320 guards (`scripts/mutation/entries/`) are each broken on purpose; a test must fail for
every one.
- **Unit tests:** 842, including every example on every rule page (before compiles and is reported, one Fix All pass
gives exactly the after, the after is clean).
## Migration
`stylebro-migrate` ([migrating.md](migrating.md)) reads a repository's StyleCop setup (rulesets, global configs,
`.editorconfig` files, `stylecop.json`, the StyleCop version) and writes StyleBro and SDK settings that match it, carries
`#pragma warning disable SA...`, `[SuppressMessage]` and `` suppressions over to the replacing rules, and
reports the StyleCop rules nothing enforces any more.
Files `dotnet format` changes after `stylebro-migrate --write` on repositories that were StyleCop-clean (CLAUDE.md,
"Migration re-measured with the SDK replacements", 2026-10-03; `scripts/stylecop-survey/Measure-MigrationDelta.ps1`):
| Repository | Files changed | What |
|---|---:|---|
| Polly | 1 | A file plain `dotnet format` already breaks (conflict markers in a multi-targeted file), not StyleBro |
| OpenTelemetry | 1 | A line ending in a file with mixed line endings |
| A private app | 7 | 6 line-ending-only, 1 BRO1104 on a target-typed `new()`, which the older StyleCop version used there misses (1.2 reports it) |
The rules StyleCop doesn't have are turned off by the migration, so they change nothing there.
- **Baseline** ([baseline.md](baseline.md)): `stylebro-migrate baseline` records today's violations; the build, the IDE
and `dotnet format` ignore them and only new or edited lines count. On FFMpegCore with every rule: 820 violations on
759 lines baselined, then 0 StyleBro errors in the build and `dotnet format --verify-no-changes` clean (2026-10-02).
Whitespace formatting can't be baselined.
- **Multi-targeted projects:** StyleBro's Fix All merges each target framework's copy of a file itself, so its fixes
write no conflict markers (Newtonsoft.Json, 8 frameworks). The SDK's IDE0055 fix crashes plain `dotnet format` there (reported upstream as [dotnet/roslyn#86000](https://github.com/dotnet/roslyn/issues/86000) and [dotnet/roslyn#86001](https://github.com/dotnet/roslyn/issues/86001));
`stylebro-migrate format` runs `dotnet format` once per target framework (Serilog: 505 IDE0055 findings -> 0 in one
run, 2026-10-03; [ci.md](ci.md#notes)).
## Beyond StyleCop
26 rules StyleCop doesn't have ([differences-from-stylecop.md](differences-from-stylecop.md#rules-beyond-stylecop),
chosen in [beyond-stylecop.md](https://github.com/bisforboman/stylebro/blob/main/docs/beyond-stylecop.md) from Roslynator, Meziantou, Sonar and StyleCop's issue tracker),
each with a fix like every other rule. For example: null checks in one form (BRO1133), operator placement when an
expression wraps (BRO1520), split call chains one call per line (BRO1523), no `$`/`@` a plain string doesn't need
(BRO1138), generic crefs in braces (BRO1617), no `else` after a jump (BRO1143, off by default). The preset turns 22
of them on; `stylebro-migrate` turns them off, so migrating changes nothing until you opt in.
`stylebro-migrate init --modernize` ([modernizing.md](modernizing.md)) turns on the SDK's own rules for newer C# and
APIs in tiers, and StyleBro's multi-target guard hides a newer-API rule in a project where one target framework lacks
the API.
## Limits
What StyleBro doesn't do:
- **Report missing documentation** (SA1600 and friends), except overrides and implementations. If you rely on SA1600 to
make people write docs, keep StyleCop's SA1600 for reporting or use the compiler's CS1591.
- **Move types into their own files or rename files** (SA1402, SA1403, SA1649).
- **Line length.** Neither does StyleCop; there's no deterministic fix (where to break).
- **Baseline whitespace formatting.**
- **Rename everything it reports:** a field whose name appears in a string elsewhere (reflection, serialization) keeps
its name and its warning (Newtonsoft.Json: 49 such names, 2026-10-01).
Where it deliberately differs from StyleCop's defaults ([differences-from-stylecop.md](differences-from-stylecop.md#preset-choices)):
no `this.` prefix, usings outside the namespace, region removal (BRO1112) and namespace-name casing (BRO1312) off, and the
XML file header only once a company is configured. `stylebro-migrate` keeps a team's own settings instead.
Checked and not checked:
- Visual Studio 2022 (17.13): build, light bulb fixes, Fix All and live squiggles checked by the owner (2026-10-05,
[backlog.md](https://github.com/bisforboman/stylebro/blob/generated/docs/backlog.md)). **Rider: not checked.**
- Needs Roslyn 4.8 (.NET 8 SDK, Visual Studio 17.8) or newer.
- **Prerelease:** 0.7.0-alpha.1 on nuget.org; versions and behavior can still change between prereleases.
---
Source: https://bisforboman.github.io/stylebro/differences-from-stylecop/
# Differences from StyleCop
What changes when a project moves from StyleCop.Analyzers with its default settings to StyleBro with its preset. Each
difference is deliberate; this page says why, and how to get StyleCop's behavior back where that's possible.
Teams that changed StyleCop's settings don't need the preset: `stylebro-migrate` writes settings that match theirs
([migrating.md](migrating.md)). Rules StyleBro doesn't cover at all are in [skipped-rules.md](skipped-rules.md).
Kept up to date by tests: every preset setting whose value differs from StyleCop's defaults must be named on this page
(`MigrationTests.EveryPresetDifferenceFromStyleCop_IsDocumented`), and every BRO rule must appear in the rule list
below (`MigrationTests.EveryRule_IsInTheDifferencesPage`).
## Preset choices
The preset follows StyleCop's defaults except here.
| Topic | StyleCop default | StyleBro preset | Why | To get StyleCop's behavior |
|---|---|---|---|---|
| `this.` prefix (SA1101) | Required | Not required (`dotnet_style_qualification_for_field`, `_property`, `_method`, `_event` = `false`; `stylebro-migrate init` writes `dotnet_diagnostic.IDE0009.severity = none`, unless your `.editorconfig` requires `this.`) | Most teams turn SA1101 off (2 of the 3 surveyed); it's also the SDK's default. User decision. With these keys `false`, [BRO1131](rules/BRO1131.md) (SA1100) removes `base.` instead of writing `this.`. | Set the four `dotnet_style_qualification_for_*` keys to `true` and IDE0009 to `warning` in `.editorconfig`. |
| Using placement (SA1200, [BRO1008](rules/BRO1008.md)) | Inside the namespace | Outside (`csharp_using_directive_placement = outside_namespace`) | Works with file-scoped namespaces, matches the .NET templates; all three surveyed teams turn SA1200 off. User decision. Without the preset BRO1008 needs the setting (unset: nothing reported, owner's decision); `stylebro-migrate` writes StyleCop's choice, `inside_namespace` by default. | `csharp_using_directive_placement = inside_namespace`. |
| Regions between members (SA1124, [BRO1112](rules/BRO1112.md)) | Reported | Off (`dotnet_diagnostic.BRO1112.severity = none`) | Removing every region in an existing codebase is a large one-time change, so it's opt-in. (Regions inside code, SA1123/[BRO1113](rules/BRO1113.md), stay on.) | `dotnet_diagnostic.BRO1112.severity = warning`. |
| Namespace names (SA1300, [BRO1312](rules/BRO1312.md)) | Reported | Off (`dotnet_diagnostic.BRO1312.severity = none`) | Renaming a namespace renames every type in it: a breaking change for other code and for stored type names (`$type`, `Type.GetType`). In the surveyed repositories every lower-case namespace part was deliberate (brand names like `iText`, `iOS`; culture codes). User decision; findings in [proposals/namespace-names.md](https://github.com/bisforboman/stylebro/blob/main/docs/proposals/namespace-names.md). | `dotnet_diagnostic.BRO1312.severity = warning`. |
| Missing documentation (SA1600, SA1601, SA1602, SA1611, SA1615, ...) | Reported | Only overrides and interface implementations, fixed with `` ([BRO1601](rules/BRO1601.md)) | The only automatic fix for other members is placeholder text, which satisfies the rule without documenting anything. User decision. | Not available (keep StyleCop's SA1600 for reporting only, or use the compiler's CS1591). |
| Several statements on one line (SA1107) | Reported | Split by the SDK formatter (`csharp_preserve_single_line_statements = false`, IDE0055) | A new project gets one statement per line. `stylebro-migrate` writes `true` instead: `false` also splits one-line anonymous methods and blocks StyleCop accepts (`delegate { /* comment */ }` in StyleCop-clean vs-threading), so SA1107 is reported as not expressible there. | `csharp_preserve_single_line_statements = true` (nothing splits `a(); b();` then). |
| File header (SA1633-SA1641) | Required, XML format (`// `) | XML format with [BRO1615](rules/BRO1615.md) (`dotnet_diagnostic.BRO1615.severity = warning`), but it does nothing until `stylebro_file_header_company` is set; `stylebro-migrate` turns it off when `stylecop.json` names no company | A header needs the team's company name and copyright text; a placeholder header everywhere is worse than none. | `stylebro_file_header_company = YourCompany` (and `stylebro_file_header_copyright` if the text differs). For a plain header (`xmlHeader: false`): `file_header_template = ...` and `dotnet_diagnostic.IDE0073.severity = warning`. |
Settings that look different but match StyleCop: `stylebro_private_field_naming = camelCase` (SA1306/SA1309: no
leading underscore; `_camelCase` is StyleCop's opt-in SX1309 and SX1309S together;
`stylebro_private_mutable_static_field_naming = _camelCase` gives static fields alone the `_`), `stylebro_document_*` (stylecop.json's documentationRules defaults), `stylebro_member_*`
(SA1201-SA1204/SA1214 order, checked pairwise), and the SDK settings for spacing, braces, modifiers, type aliases,
parentheses, using order and blank lines. `dotnet_style_require_accessibility_modifiers = for_non_interface_members`
matches SA1400, which doesn't ask for modifiers on interface members (`always` would).
StyleCop rules that are on by default but that nothing enforces under the preset: the **candidate**, **drop** and
**SDK not on yet** entries in [skipped-rules.md](skipped-rules.md) whose facts say "on by default" (for example SA1118,
SA1401, SA1402, SA1649).
## SDK rules that replace StyleCop rules
Where StyleBro relies on an SDK rule instead of shipping its own (its options come from the preset, its severity from
`stylebro-migrate init`), `dotnet format` fixes with the SDK's logic, which isn't
always exactly StyleCop's. Details in [skipped-rules.md](skipped-rules.md#covered-with-known-differences).
- **IDE0036** (for SA1206, SA1207) enforces the SDK's whole modifier order, where StyleCop only checks that the access
modifier comes first and `static` next. On six real repos it changed nothing StyleCop accepts.
- **IDE0073** (for SA1633 with `xmlHeader: false`) writes a plain header; the XML header is [BRO1615](rules/BRO1615.md).
- **Using sorting** in `dotnet format` happens whenever `dotnet_sort_system_directives_first` is set, even to `false`.
The preset doesn't set it: `stylebro-migrate --write` writes it when SA1208 or SA1210 is on, `init` when the code
sorts its usings.
## Rule by rule
Every StyleBro rule reports what its StyleCop rule reports, with the exceptions below; each rule page has the details
under "Compared with StyleCop". Two things differ for every rule:
- **Fixes work under `dotnet format`.** Every StyleBro diagnostic has a fix with Fix All. Several StyleCop fixes don't
run under `dotnet format` (no Fix All, or they change nothing), apply only part of a rename per run, or produce code
that breaks another rule or doesn't compile. Those are listed per rule below.
- **Unsafe cases are skipped instead of fixed badly.** Where a fix could change behavior or lose code, StyleBro doesn't
report the case at all (or, for renames that compile but might break reflection or serialization, reports it and
leaves the name). StyleCop reports these.
### Reports less than StyleCop
| Rule | Not reported | Why |
|---|---|---|
| [BRO1001](rules/BRO1001.md) (SA1201-SA1204, SA1214) | Members out of order across regions (each region is sorted on its own); types with `#if`/`#else`/`#endif` between members; types where sorting would change what initializers compute, a struct's (or `[StructLayout]` type's) field layout, or the order a serializer writes attributed members in; containers where a member led by a group comment would move (the comment would end up above part of the group). One diagnostic per type, not per member. | Moving members across directives or reordering dependent initializers changes behavior; a comment can't be split between the members it describes. |
| [BRO1008](rules/BRO1008.md) (SA1200) | Files where a name would bind to something else (or to nothing) with the usings moved; files with more than one namespace, types or attributes outside the namespace, usings on both levels, global usings or extern aliases, a directive before the namespace body (except `#region` lines above the usings), a using sharing its line with other code. With `inside_namespace`, a file without a namespace. Anything when `csharp_using_directive_placement` isn't set (StyleCop defaults to inside). | Moving usings changes how names are looked up: the SDK's IDE0065 fix broke builds (CS0104, CS0118). The other cases have no single place to move to, or need a merge `dotnet format` would sort again. |
| [BRO1001](rules/BRO1001.md) (SA1201-SA1204, SA1214) | Members out of order across regions (each region is sorted on its own); types with `#if`/`#else`/`#endif` between members; types where sorting would change what initializers compute, a struct's (or `[StructLayout]` type's) field layout, or the order a serializer writes attributed members in; containers where a member led by a group comment would move (the comment would end up above part of the group), or a member led by a comment that sits right below the previous member (followed by a blank line, see BRO1504 below). One diagnostic per type, not per member. | Moving members across directives or reordering dependent initializers changes behavior; a comment can't be split between the members it describes. |
| [BRO1514](rules/BRO1514.md), [BRO1515](rules/BRO1515.md), [BRO1516](rules/BRO1516.md) (SA1503, SA1519, SA1520) | A comment or directive where the braces would go, `#if` in the owning statement, a multi-line string in the statement, other code after it on its line | The fix can't place the braces without moving the comment or reindenting string contents; StyleCop's formatter-based fix does. |
| [BRO1514](rules/BRO1514.md), [BRO1515](rules/BRO1515.md), [BRO1516](rules/BRO1516.md) (SA1503, SA1519, SA1520) | `fixed (a) fixed (b) { }` (consecutive `fixed` statements sharing one block), unless `stylebro_allow_consecutive_usings = false` | The idiom for pinning several arrays, like consecutive `using`s (StyleCop's allowConsecutiveUsings); the SDK's IDE0011 allows it too. |
| [BRO1302](rules/BRO1302.md)-[BRO1314](rules/BRO1314.md) (SA1300, SA1302-SA1316) | Names other assemblies see: public, protected or protected internal types, members, parameters, type parameters, tuple elements in signatures, and namespaces of such types, unless `stylebro_rename_public_api = true` | Renaming them breaks code outside the solution, which the fix can't reach (named arguments too); owner's decision 2026-10-09 in [decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md). Set the key to report them like StyleCop. |
| [BRO1301](rules/BRO1301.md)-[BRO1310](rules/BRO1310.md), [BRO1314](rules/BRO1314.md) (SA1300, SA1302-SA1313) | A name a `nameof(...)` produces (in the member for variables and parameters, in the type for fields; elsewhere in the solution the fix keeps the warning) | The rename would change the string: Ocelot built an HTTP header name with `nameof`, and a test read a private field with `GetField(nameof(_components))`. |
| [BRO1504](rules/BRO1504.md), [BRO1506](rules/BRO1506.md) (SA1515, SA1512) | A run of `//` lines right below a line ending in `;`, with a blank line after it | It describes the code above (eShop: a note below a field); StyleCop's fixes move the blank line above it, attaching it to the next member. |
| [BRO1506](rules/BRO1506.md) (SA1512) | A standalone `//` comment: a blank line right above and right below it, not before a `}`; unless `stylebro_blank_line_below_standalone_comments = remove`, which `stylebro-migrate` writes when SA1512 is on | Owner's choice (2026-10-14): a note on the code above or a section header; SA1512's fix glued them onto unrelated code below (AzureEventGridSimulator, 10 files). |
| [BRO1120](rules/BRO1120.md) (SA1120) | Empty comments that are all a pair of braces holds (`catch` / `{` / `//` / `}`) | They mark the block as empty on purpose; StyleCop's fix leaves a bare `{ }`. |
| [BRO1311](rules/BRO1311.md) (SA1316) | Names in tuple literals and inferred names (StyleCop's `includeInferredTupleElementNames`, off by default) | Not tuple types; renaming an inferred name means renaming the variable it comes from. |
| [BRO1132](rules/BRO1132.md) (SA1108) | A statement header spanning several lines (`if (a` + `&& b) // c`); a comment before a single-line block (`if (x) // c` + `{ y(); }`), a comment spanning lines, a directive between the header and `{` | After a split condition the comment usually explains its last line, not the block; moved into the block it would mislead (user decision). The fix needs a line of its own inside the block for the comment, and moves only one-line comments. |
| [BRO1310](rules/BRO1310.md) (SA1305) | Constant, `static readonly`, public and internal fields; names where another name in the member would get the same new name (`xItem`, `yItem`) | Pascal fields are [BRO1306](rules/BRO1306.md)'s, which removes the prefix; the rename must not collide. |
| [BRO1405](rules/BRO1405.md) (SA1119) | Parentheses whose removal changes how the code parses (`F(a < b, (c > (d + 1)))` would become a generic call) | StyleCop's fix doesn't compile there. Its fix also leaves the spaces of `( b )` behind; StyleBro's removes them. StyleCop's hidden SA1119_p (the IDE greying of the same `(` and `)`) isn't ported. |
| [BRO1517](rules/BRO1517.md), [BRO1518](rules/BRO1518.md), [BRO1519](rules/BRO1519.md) (SA1507, SA1508, SA1513) | Places another StyleBro blank-line rule fixes when it's on: after `{` (BRO1503), below a comment (BRO1506), a comment, documentation or member after `}` (BRO1504, BRO1513, BRO1505); also a `}` with code or a comment after it on its line; a `}` inside an expression or pattern followed by a token that continues it (`?`, `??`, `?.`, a binary operator, `with`, `as`, `when`, ...; StyleCop exempts only `.`, `:`, `&&`, `\|\|`) | Two fixes adding or removing the same blank line would conflict under `dotnet format`; a continuation token doesn't follow the end of a block (CSharpier removes that blank line again). |
| [BRO1101](rules/BRO1101.md) (SA1106) | `while (x) ;` and labeled `end: ;` | StyleCop's fix hides a likely bug in the first and breaks the build in the second. |
| [BRO1102](rules/BRO1102.md) (SA1133) | Attribute lists with a comment between the attributes | StyleCop's fix drops the comment. |
| [BRO1103](rules/BRO1103.md) (SA1131) | Comparisons using a type's own `==`/`<` | A user-defined operator may not be symmetric. |
| [BRO1131](rules/BRO1131.md) (SA1100) | `base.M()` for a virtual M in a type that isn't sealed | `this.M()` would call a derived type's override instead: a behavior change. |
| [BRO1104](rules/BRO1104.md) (SA1129) | `new T()` for type parameters; `new S();` statements | `default(T)` differs for reference types; StyleCop's fix for the statement doesn't compile. |
| [BRO1110](rules/BRO1110.md) (SA1111) | A comment at the end of the last item's line when code follows the `)` (`2 // two` then `); var x = 1;`) | The code would end up behind the comment. |
| [BRO1301](rules/BRO1301.md), [BRO1302](rules/BRO1302.md) (SA1312, SA1313) | `_`/`__` parameters; names whose rename could clash or is inferred elsewhere; constructor parameters named exactly like a property or field of their type | Discards; renames must compile; serializers bind constructor parameters to members by name (CsvHelper case-sensitively). |
| [BRO1301](rules/BRO1301.md), [BRO1302](rules/BRO1302.md) (SA1312, SA1313) | Range variables and lambda parameters that end up in an expression tree (a query over `IQueryable`, an `Expression<...>` lambda, or a lambda nested in one) | The names are data at run time (the expression's `ToString()`, query providers): Mapster's tests compare a query's text. |
| [BRO1120](rules/BRO1120.md) (SA1120) | Empty lines of the file header when `file_header_template` is set | IDE0073 writes that header from the template, empty `//` lines included; removing them makes the two rules fight. |
| [BRO1303](rules/BRO1303.md) (SA1306, SA1309) | Non-private, non-protected fields (those are BRO1306's); fields of serialized types, attributed fields, names used in strings | Renames must not break serialization or reflection. |
| [BRO1303](rules/BRO1303.md), [BRO1306](rules/BRO1306.md), [BRO1307](rules/BRO1307.md), [BRO1308](rules/BRO1308.md), [BRO1310](rules/BRO1310.md) (SA1303-SA1311) | Interop names: SCREAMING_CASE constants and `static readonly` fields (`S_OK`, `MAX_PATH`), COM GUID fields (`IID_IContextMenu`), and the fields of types with `[StructLayout]`, `[ComImport]`, `[UnmanagedFunctionPointer]` or `[DllImport]`/`[LibraryImport]` methods (and types nested in them). StyleCop skips only `*NativeMethods` classes | Owner's decision (docs/decisions.md): these names come from native headers and are searched for by them; Flow.Launcher got `S_OK` -> `SOk` and `cbSize` -> `CbSize`. |
| [BRO1306](rules/BRO1306.md), [BRO1307](rules/BRO1307.md), [BRO1308](rules/BRO1308.md), [BRO1309](rules/BRO1309.md) (SA1303, SA1304, SA1307, SA1311; SA1308; SA1310; SA1300) | Namespaces (SA1300's, they are [BRO1312](rules/BRO1312.md)'s); BRO1309: snake_case names (`device_id`, usually wire names); names that would become keywords or merge digits (`Int32_0`); the same rename guards | Renames must compile and keep working. |
| [BRO1312](rules/BRO1312.md) (SA1300, namespaces) | Off by default. Not reported: the project's root namespace or a part of it, a namespace another assembly also declares, one declared in generated code, a new name that already exists (`taken` next to `Taken`) | Resource names follow the root namespace, not the declaration; the other assembly and the generator keep the old name; merging two namespaces can make type names collide. |
| [BRO1401](rules/BRO1401.md) (SA1413) | Lists with `#if` between the braces | Each target framework's copy needs a different edit. |
| [BRO1508](rules/BRO1508.md), [BRO1509](rules/BRO1509.md) (SA1501, SA1502) | One-line blocks with a comment inside the braces or after the `}`; a `case` block on the line of `switch (x) {`. A local function's body is reported once (BRO1509), not as both SA1501 and SA1502. | The fix would have to move the comment (one after the `}` would leave the line a line-based suppression marks); the `case` block has no line to indent from. |
| [BRO1118](rules/BRO1118.md) (SA1114) | A comment between `(` and the first item | The fix would have to move the comment (StyleCop has no fix). |
| [BRO1513](rules/BRO1513.md) (SA1514) | Documentation right below a `//` comment | The blank line would break BRO1506 (SA1512), and the two fixes would undo each other. |
| [BRO1504](rules/BRO1504.md) (SA1515) | A comment that continues the trailing comment above it, starting in the same column (`x(); // because ...` + `// ... the rest` aligned below) | A blank line would split the sentence, and the next `dotnet format` run would move the separated half to the code's indentation (akka.net, trial 22). |
| [BRO1504](rules/BRO1504.md) (SA1515) | A comment right after a collection expression's `[` | It starts the list like a comment after `{`; StyleCop fixed this after 1.2.0-beta.556 ([#3766](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3766)). |
| [BRO1501](rules/BRO1501.md) (SA1509) | The brace of an entry in an initializer (`{ "ssh", 22 }` after a blank line in a dictionary initializer) | The blank line groups the entries; StyleCop's fix removes it (StyleCop bug [#2832](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2832), open; user decision 2026-10-04). |
| [BRO1504](rules/BRO1504.md) (SA1515) | A comment right after `=>` (switch expression arm, lambda, expression body) | It starts what comes after the arrow; StyleCop's fix puts a blank line between them (StyleCop bug [#3392](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3392), open; user decision 2026-10-04). |
| [BRO1504](rules/BRO1504.md) (SA1515) | A comment whose text starts with a prefix in `stylebro_comment_blank_line_exempt_prefixes` (default none; e.g. `ReSharper, @formatter`) | Tool markers belong to the line below them (StyleCop issues [#3546](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3546), [#1490](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1490)); with the default, the same as StyleCop. |
| [BRO1504](rules/BRO1504.md) (SA1515) | A comment between the links of a call chain (the next token is the `.` or `?.` of a member access: `AddHealthChecks()`, `// liveness`, `.AddCheck(...)`) | The comment describes the next link; a blank line would split the chain (found in davidfowl/TodoApi; owner's decision 2026-10-11). |
| [BRO1504](rules/BRO1504.md) (SA1515) | A comment between the items of a list (right after `(`, `[`, `{` or `,`, or right before `,` or the closing token of an argument, attribute argument, parameter, initializer, collection expression or tuple list) | The comment describes the next item; a blank line would split the list (found in Chickensoft's GameDemo; owner's decision 2026-10-12). |
| [BRO1505](rules/BRO1505.md) (SA1516) | With `stylebro_allow_adjacent_single_line_members = true` (default `false`): two neighbouring single-line members without a block body (`void A();` above `int B() => 1;`) | Some teams keep short members together, like fields (StyleCop issue [#2441](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2441)). |
| [BRO1105](rules/BRO1105.md), [BRO1111](rules/BRO1111.md) (SA1128, SA1127) | With `stylebro_constructor_initializer_placement` / `stylebro_constraint_placement = same_line` (default `own_line`): an initializer or `where` clause on the declaration's line; instead, one on a line of its own is joined when the line fits `max_line_length` | Teams that prefer that layout turn SA1128/SA1127 off in StyleCop; StyleBro can enforce it. |
| [BRO1310](rules/BRO1310.md) (SA1305) | Parameters of `extern`, `[DllImport]` and `[LibraryImport]` methods | They keep the native API's names, like BRO1309 leaves the methods alone (StyleCop bug [#2859](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2859), open). |
| [BRO1002](rules/BRO1002.md) (SA1005) | `dotnet new` template markers `//-:` and `//+:` | A space breaks them (StyleCop bug [#2689](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2689), open). |
| [BRO1510](rules/BRO1510.md) (SA1504) | One-line accessors where one has an attribute on its own line above it | The attribute isn't part of the accessor's layout; StyleCop counts its line ([#3434](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3434)). |
| [BRO1601](rules/BRO1601.md) (SA1600) | Everything except overrides and implicit interface implementations | See "Missing documentation" above; explicit implementations like StyleCop's `master`. |
| [BRO1603](rules/BRO1603.md) (SA1629) | Text ending with `?`, `!` or `:`; a period followed by a closing quote or bracket (`"done."`) | Already ends a sentence; StyleCop's fix writes `question?.` and `"done.".`. |
| [BRO1603](rules/BRO1603.md) (SA1629) | Text whose last word is code: a bracket, brace, `=`, `;`, `:`, slash, `|`, `$`, `#`, `_` or backtick in it (`digraph {`, `Example: A1:B10`, a URL) | A period reads as part of the code; StyleCop's fix writes `digraph {.` (found in Stateless and spreadcheetah). |
| [BRO1603](rules/BRO1603.md) (SA1629) | Text ending with `,` or `;` (`1033 => "en",`, `value;`) | Code or a list that goes on; a period there gives `"en",.`. |
| [BRO1603](rules/BRO1603.md) (SA1629) | Text ending with an inline element whose own text ends a sentence (`is it done?`); text whose last word ends in a period and digits (`HTML 5.0`) | Already ends a sentence, or reads as a number (AngleSharp's `it.0` became `it.0.`). StyleCop passes over an inline element with text and checks the text before it: it reports `Ends with ` and writes `Ends with. `; when that text ends with a period, like SSH.NET's `class. ... request.`, both stay quiet. |
| [BRO1603](rules/BRO1603.md) (SA1629) and the other rules that edit documentation | A comment with a bare `&` that starts no entity (`this & that`) | Malformed XML (the compiler reports CS1570), like a bare `<`: where the text ends is a guess, so a person fixes it first. |
| [BRO1609](rules/BRO1609.md) (SA1651) | A `` at the top level of a comment, outside `` and the other elements | Unwrapping it leaves its text outside every element. |
| [BRO1603](rules/BRO1603.md) (SA1629) | Text that is only a number (`0` on an enum member) | A value, not a sentence. StyleCop 1.1.118 doesn't check enum members at all, so StyleCop-clean repositories have them (Soulseek.NET's message codes); 1.2 reports them. |
| [BRO1603](rules/BRO1603.md) (SA1629) | Text ending with `?`, `!` or `:`; a period followed by a closing quote or bracket (`"done."`); a trailing `"... ."` | Already ends a sentence; StyleCop's fix writes `question?.`, `"done.".` and `.` (StyleCop bug [#2784](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2784), open). |
| [BRO1606](rules/BRO1606.md) (SA1642) | A summary whose first `` starts with the standard sentence | StyleCop fixed this after 1.2.0-beta.556; beta.556's fix writes the sentence twice. |
| [BRO1601](rules/BRO1601.md) (SA1600) | A member with `#if`/`#elif`/`#else`/`#endif` above it or between its attributes | In a multi-targeted project each framework's copy wanted `` in another place and BRO1602 undid the other's: `dotnet format` never converged. |
| [BRO1604](rules/BRO1604.md) (SA1623) | A summary without a known prefix that doesn't start with an article or a lower-case word (`This is the hash`, `If true, ...`, `Returns ...`), or starts with a lower-case condition or verb; a `bool` summary that starts with an article (`The tokenizer predicted ...`) | `Gets or sets this is the hash` isn't a sentence; a person rephrases it. A `bool` summary starting with an article is often a clause (owner's decision 2026-10-12, issue #132). |
| [BRO1606](rules/BRO1606.md) (SA1642) | A constructor summary with text of its own (`Opens a connection to the server.`, `Creates a words object.`); text left after replaced filler that starts in lower case or with a condition (`construct a bag`, `If set, ...`) | The summary already says what the constructor does; the standard sentence in front made two sentences doing one job (owner's decision 2026-10-12, issue #132). Only empty summaries, filler (`Default constructor.`, `Creates a new instance of X.`) and the standard sentence written differently are reported; filler is replaced by the standard sentence, which StyleCop's fix keeps after it. |
| [BRO1611](rules/BRO1611.md) (SA1612) | Constructors and operators | Matches StyleCop 1.2, which doesn't check them either. |
| [BRO1612](rules/BRO1612.md), [BRO1614](rules/BRO1614.md) (SA1613, SA1621) | Unnamed ``/`` tags whose (type) parameter isn't certain (two unnamed tags for three parameters, an unnamed tag next to a stale one) | The fix would have to guess the name. |
| [BRO1615](rules/BRO1615.md) (SA1633-SA1638, SA1640, SA1641) | Anything until `stylebro_file_header_company` is set; headers in a `/* */` comment; a broken XML header that already has a `(x, y)` with an argument that would name the element; `new ValueTuple()`; creations in expression trees | The names would make BRO1124 change code on a second run; there's no literal for the empty creation (StyleCop's fix writes `()`); expression trees can't contain tuple literals. |
| [BRO1124](rules/BRO1124.md) (SA1142) | `nameof(t.Item1)`; `x.Item1` whose element is named `x` | The fix would change the string; `x.x` says nothing (the name is usually inferred from another lambda's parameter). |
| [BRO1125](rules/BRO1125.md) (SA1130) | Anonymous methods whose lambda wouldn't bind the same (another overload, no target type for `var`), `ref`/`out` parameters | StyleCop's fix for these changes behavior or doesn't compile. |
| [BRO1126](rules/BRO1126.md) (SA1135) | A qualified name an enclosing namespace would hide; a type argument that starts with an alias | StyleCop's fix would refer to the wrong namespace; an alias at the start of the name is fine for StyleCop too (StyleCop bug [#3884](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3884), open). |
| [BRO1127](rules/BRO1127.md), [BRO1128](rules/BRO1128.md) (SA1102, SA1103) | Blank lines with a comment line between clauses; a query whose clauses share a line across a comment | The fix would have to move the comment, or couldn't satisfy the rule. |
| [BRO1403](rules/BRO1403.md) (SA1410) | `delegate() { }` that BRO1125 turns into a lambda | One fix per anonymous method; StyleCop reports both rules there. |
### Reports more than StyleCop
| Rule | Also reported | Why |
|---|---|---|
| [BRO1310](rules/BRO1310.md) (SA1305) | A prefix after a leading underscore (`_iCount`) | StyleCop reports it once SA1309 removed the underscore; StyleBro does both in one rename. |
| [BRO1407](rules/BRO1407.md) (SA1408) | `and`/`or` mixed in a pattern (`v is > 1 and < 5 or 10`) | StyleCop's current source checks patterns too; the released 1.2.0-beta.556 predates it. |
| [BRO1104](rules/BRO1104.md) (SA1129) | Target-typed `new()` for value types | StyleCop 1.1.118 predates it; 1.2 reports it too. |
| [BRO1401](rules/BRO1401.md) (SA1413) | Multi-line switch expressions without a trailing comma | StyleCop 1.1.118 doesn't check them; 1.2 does. `stylebro-migrate` turns BRO1401 off for a 1.1.x repository whose code has such a switch. |
| [BRO1107](rules/BRO1107.md), [BRO1108](rules/BRO1108.md) (SA1116, SA1117) | Record parameters, primary-constructor parameters and base arguments | StyleCop 1.2.0-beta.556 doesn't check them; they're lists like any other. |
| [BRO1110](rules/BRO1110.md) (SA1111) | A `)` on its own line after target-typed `new(...)` and `: this(...)`/`: base(...)` arguments | StyleCop (1.1.118 and 1.2) doesn't check these lists; they're argument lists like any other. |
| [BRO1306](rules/BRO1306.md) (SA1303, SA1311) | Constants and `static readonly` fields starting with `_` | StyleCop leaves them to SA1309; StyleBro renames to the full correct name in one pass (`stylebro_allow_leading_underscore_in_pascal_fields = true` keeps them, set by `stylebro-migrate` when SA1309 is off). |
| [BRO1123](rules/BRO1123.md) (SA1141) | Tuple types in locals, arrays, nullables, `typeof` and type arguments of calls | StyleCop only checks declarations and creations. |
| [BRO1125](rules/BRO1125.md) (SA1130) | Calls like `list.ForEach(delegate (int item) { ... })` | StyleCop's overload check misses them; StyleBro binds the lambda and finds the same method. |
| [BRO1128](rules/BRO1128.md) (SA1103) | A mixed query that also has SA1104/SA1105 findings | StyleCop leaves the rest of the query to a second run. |
| [BRO1510](rules/BRO1510.md) (SA1504) | A one-line accessor with an attribute above it next to a multi-line accessor | StyleCop counts the attribute line, so it sees two multi-line accessors ([#3434](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3434)). |
| [BRO1505](rules/BRO1505.md) (SA1516) | A member whose `///` directly follows the previous member, and file-scoped namespace declarations | StyleCop misses the first when it parses docs, and 1.1.118 misses the second (1.2 reports it). |
| [BRO1002](rules/BRO1002.md) (SA1005) | Nothing extra; StyleCop 1.1.118 reported `// two spaces`, 1.2 and StyleBro don't | |
### Same reports, different fix
| Rule | StyleCop's fix | StyleBro's fix |
|---|---|---|
| [BRO1311](rules/BRO1311.md) (SA1316) | Renames the declaration only: every use (`t.count`) and override then breaks the build | Renames every tuple type element with the name, the literals converted to them and every use, in the whole solution; a rename that wouldn't compile is skipped and the warning stays |
| [BRO1603](rules/BRO1603.md) (SA1629) | Text ending with an HTML inline element (``, ``, ``, ``, ...): StyleCop passes over it and puts the period after the text before it | The period after the element, like after `` or ``. Other unknown elements (a tool's ``) are passed over like StyleCop. |
| [BRO1002](rules/BRO1002.md) (SA1005) | `// ` with a trailing space for an empty comment | `//` |
| [BRO1104](rules/BRO1104.md) (SA1129) | `default(T)` | `default(T)` too, unless the SDK's `csharp_prefer_simple_default_expression = true` is set: then the `default` literal where it binds the same (IDE0034 then has nothing left to simplify) |
| [BRO1108](rules/BRO1108.md) (SA1117) | None that works | Each item on its own line |
| [BRO1132](rules/BRO1132.md) (SA1108) | None | Moves the comment into the block, on its own line right after `{` |
| [BRO1301](rules/BRO1301.md)-[BRO1309](rules/BRO1309.md) (naming) | Lowers/removes only the first letter or prefix, can't fix `_name`, under `dotnet format` applies part of the renames per run or nothing; `MAX_VALUE` -> `MAXVALUE`, `m_Upper` -> `Upper` | The complete correct name in one pass (`MaxValue`), with overrides, implementations, named arguments and docs |
| [BRO1401](rules/BRO1401.md) (SA1413) | `"beta",}` (no space), nested lists need a second run | `"beta", }`, nested lists in one pass |
| [BRO1505](rules/BRO1505.md) (SA1516) | Misses the blank line above a `///` comment; splits two members on a line without indentation | Inserts above the doc comment; indents |
| [BRO1508](rules/BRO1508.md), [BRO1509](rules/BRO1509.md) (SA1501, SA1502) | Writes `{` at column 1 in nested blocks, leaves accessors and nested declarations side by side, moves `while` of `do ... while` to its own line, writes CRLF into `\n` files | Indents from `.editorconfig`, one item per line, adds the blank lines BRO1505 wants, keeps `} while (x);` and the file's line endings, respects `csharp_new_line_before_open_brace`/`_else`/`_catch`/`_finally` |
| [BRO1510](rules/BRO1510.md) (SA1504) | Under `dotnet format`, collapses or expands the accessors of every property in the project the same way, whichever the first diagnostic offers; deletes comments in a body it collapses | Collapses one-statement bodies or expands, per property; never collapses a body with a comment; adds the blank line BRO1505 wants |
| [BRO1604](rules/BRO1604.md) (SA1623) | `a value indicating whether whether ...` | Keeps the existing "whether" |
| [BRO1606](rules/BRO1606.md), [BRO1607](rules/BRO1607.md) (SA1642, SA1643) | `class.Opens a connection` (no space) | `class. Opens a connection` |
| [BRO1114](rules/BRO1114.md) (SA1132) | Keeps the attributes on the first field only, so the others lose them (`[Obsolete]` disappears from the second field) | Copies the attributes and the documentation comment to every field; blank line between event fields |
| [BRO1115](rules/BRO1115.md) (SA1125) | None | `int?` for `Nullable`, nested ones in one pass |
| [BRO1402](rules/BRO1402.md) (SA1411) | `[Obsolete ]` for `[Obsolete( )]` | `[Obsolete]` |
| [BRO1118](rules/BRO1118.md), [BRO1119](rules/BRO1119.md) (SA1114, SA1115) | None | Removes the blank lines |
| [BRO1120](rules/BRO1120.md) (SA1120) | Removes only the reported empty comment; the next one at the same end is reported on the next run | Removes every empty comment at that end of the group |
| [BRO1121](rules/BRO1121.md) (SA1136) | For an enum on one line, leaves `{ A,` and `C }` on the brace lines | Expands the enum like BRO1509 |
| [BRO1610](rules/BRO1610.md), [BRO1611](rules/BRO1611.md) (SA1627, SA1612) | None | Removes the empty ``; renames, removes and reorders `` tags |
| [BRO1612](rules/BRO1612.md), [BRO1613](rules/BRO1613.md), [BRO1614](rules/BRO1614.md) (SA1613, SA1620, SA1621) | None | Names unnamed tags when certain; renames, removes and reorders `` tags |
| [BRO1112](rules/BRO1112.md) (SA1124) | Removes a region between switch-expression arms with the blank lines around it | Keeps one blank line where it was |
| [BRO1615](rules/BRO1615.md) (SA1633-SA1641) | Deletes a plain comment header (license text, notes) when it writes the XML header | Keeps any other comment below the new header; otherwise the same headers |
| [BRO1123](rules/BRO1123.md), [BRO1124](rules/BRO1124.md) (SA1141, SA1142) | None under `dotnet format` (SA1141's has no Fix All, SA1142's throws) | Tuple syntax and element names |
| [BRO1125](rules/BRO1125.md) (SA1130) | Leaves two spaces after `=`, pulls a body on its own line up behind `=>` | Keeps the layout |
| [BRO1128](rules/BRO1128.md) (SA1103) | Joins the query on one line or splits it, whichever action comes first | Each clause on its own line |
| [BRO1107](rules/BRO1107.md), [BRO1108](rules/BRO1108.md) (SA1116, SA1117) | Leaves a moved lambda's block body at its old indentation (StyleCop bugs [#1620](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1620), [#3183](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3183), open) | Moves the body with the lambda |
### Same as StyleCop
Reports and fixes match StyleCop (checked with `scripts/stylecop-survey/Compare-WithStyleCop.ps1`):
[BRO1003](rules/BRO1003.md), [BRO1004](rules/BRO1004.md), [BRO1005](rules/BRO1005.md), [BRO1006](rules/BRO1006.md), [BRO1007](rules/BRO1007.md), [BRO1404](rules/BRO1404.md), [BRO1406](rules/BRO1406.md), [BRO1116](rules/BRO1116.md), [BRO1117](rules/BRO1117.md),
[BRO1511](rules/BRO1511.md), [BRO1512](rules/BRO1512.md), [BRO1105](rules/BRO1105.md), [BRO1106](rules/BRO1106.md), [BRO1109](rules/BRO1109.md), [BRO1111](rules/BRO1111.md),
[BRO1113](rules/BRO1113.md), [BRO1304](rules/BRO1304.md), [BRO1305](rules/BRO1305.md), [BRO1501](rules/BRO1501.md),
[BRO1502](rules/BRO1502.md), [BRO1503](rules/BRO1503.md), [BRO1506](rules/BRO1506.md),
[BRO1507](rules/BRO1507.md), [BRO1602](rules/BRO1602.md), [BRO1605](rules/BRO1605.md), [BRO1608](rules/BRO1608.md),
[BRO1129](rules/BRO1129.md), [BRO1130](rules/BRO1130.md). (BRO1304, BRO1305 and BRO1309 have working fixes under `dotnet format`; StyleCop's
change nothing there.)
### Compared with StyleCop's unreleased `master`
The tables above compare with the last release, 1.2.0-beta.556. StyleCop's `master` has changed some of these rules
since (not released; see [decisions](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md), 2026-10-04). Where `master` fixed a gap, StyleBro follows it:
| Rule | Follows `master` | beta.556 |
|---|---|---|
| [BRO1104](rules/BRO1104.md) (SA1129) | `new nint()` -> `default(nint)` (also on .NET 7+: the fix must agree across a multi-targeted project's frameworks) | Always `nint.Zero` (CS0117 on older runtimes); `master`: `nint.Zero` on C# 11 + .NET 7+ |
| [BRO1604](rules/BRO1604.md), [BRO1605](rules/BRO1605.md) (SA1623, SA1624) | `init` accessors: `Gets or initializes`, `Gets or sets` or `Gets`, `Initializes`; `Gets or initializes` is a wrong prefix elsewhere (StyleCop master: `Gets or sets` on `{ get; init; }` is wrong too) | Ignores `init`; its fix writes `Gets or sets or initializes` |
| [BRO1611](rules/BRO1611.md) (SA1612) | `` tags of primary constructors (classes, structs, records) | Not checked |
| [BRO1505](rules/BRO1505.md) (SA1516) | No blank line needed between two single-line properties, unless one is documented (`///`) | Wants none even next to a documented one |
| [BRO1601](rules/BRO1601.md) (SA1600) | Explicit interface implementations not reported | Reported |
Where `master` changed behavior StyleBro keeps (the owner's decision):
| Rule | `master` | StyleBro (like beta.556) |
|---|---|---|
| [BRO1505](rules/BRO1505.md) (SA1516) | Two fields need a blank line when the second spans several lines | Only when the first does |
| [BRO1401](rules/BRO1401.md) (SA1413) | Multi-line property patterns get a trailing comma, nested ones too (94671b70) | Not checked (owner's decision 2026-10-11: follow the release) |
| [BRO1606](rules/BRO1606.md) (SA1642) | Any summary that starts with `` is accepted, text not checked | Checks the paragraph's text |
| [BRO1311](rules/BRO1311.md) (SA1316) | Names in an override's or implementation's signature not reported | Reported, renamed together with the base |
## Rules beyond StyleCop
Rules StyleCop doesn't have. The preset turns them on at `warning`:
- `dotnet_diagnostic.BRO1133.severity`
- `dotnet_diagnostic.BRO1134.severity`
- `dotnet_diagnostic.BRO1135.severity`
- `dotnet_diagnostic.BRO1136.severity`
- `dotnet_diagnostic.BRO1137.severity`
- `dotnet_diagnostic.BRO1138.severity`
- `dotnet_diagnostic.BRO1139.severity`
- `dotnet_diagnostic.BRO1140.severity`
- `dotnet_diagnostic.BRO1141.severity`
- `dotnet_diagnostic.BRO1142.severity`
- `dotnet_diagnostic.BRO1144.severity`
- `dotnet_diagnostic.BRO1146.severity`
- `dotnet_diagnostic.BRO1149.severity`
- `dotnet_diagnostic.BRO1150.severity`
- `dotnet_diagnostic.BRO1151.severity`
- `dotnet_diagnostic.BRO1408.severity`
- `dotnet_diagnostic.BRO1410.severity`
- `dotnet_diagnostic.BRO1520.severity`
- `dotnet_diagnostic.BRO1521.severity`
- `dotnet_diagnostic.BRO1522.severity`
- `dotnet_diagnostic.BRO1523.severity`
- `dotnet_diagnostic.BRO1524.severity`
- `dotnet_diagnostic.BRO1525.severity`
- `dotnet_diagnostic.BRO1526.severity`
- `dotnet_diagnostic.BRO1527.severity`
- `dotnet_diagnostic.BRO1616.severity`
- `dotnet_diagnostic.BRO1617.severity`
- `dotnet_diagnostic.BRO1618.severity`
- `dotnet_diagnostic.BRO1619.severity`
`stylebro-migrate` writes them as `none`, so a StyleCop-clean repository doesn't change when it migrates. Turn them on
in `.editorconfig` to use them.
| Rule | What it checks | From |
|---|---|---|
| [BRO1133](rules/BRO1133.md) | Null checks as `is null`/`is not null` (`stylebro_null_check_style = pattern_matching`, the default) or `== null`/`!= null` (`equality_operator`) | Roslynator RCS1248 |
| [BRO1520](rules/BRO1520.md) | Binary operators and `?`/`:` at the beginning of the line when an expression wraps (`dotnet_style_operator_placement_when_wrapping`) | Roslynator RCS0027/RCS0028 |
| [BRO1521](rules/BRO1521.md) | `=>` of expression bodies and switch arms at the end of the line (`stylebro_arrow_placement_when_wrapping`) | Roslynator RCS0032 |
| [BRO1522](rules/BRO1522.md) | `=` of assignments and initializers at the end of the line (`stylebro_equals_placement_when_wrapping`) | Roslynator RCS0052 |
| [BRO1134](rules/BRO1134.md) | A comment between a type's, namespace's, member's, accessor's or local function's header and its `{` moves into the body, like BRO1132 (SA1108) for statements | StyleCop issue [#605](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/605) (proposed, never implemented) |
| [BRO1523](rules/BRO1523.md) | In a call chain split over several lines, every call after the first line starts its own line | Roslynator RCS0054 |
| [BRO1135](rules/BRO1135.md) | Integer literal suffixes in upper case (`1L`, `2U`, `3UL`; only `l` with `stylebro_upper_case_literal_suffixes = l_only`, only a suffix that is just `l` with `lone_l`); real suffixes (`f`, `d`, `m`) aren't checked | Sonar S818, StyleCop issue #1563 |
| [BRO1525](rules/BRO1525.md) | No blank line between an attribute list and its element or the element's next attribute list | StyleCop's proposed SA1521 ([#738](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/738), never implemented) |
| [BRO1524](rules/BRO1524.md) | A split conditional expression has the condition, `? a` and `: b` on their own lines (`?`/`:` side from `dotnet_style_operator_placement_when_wrapping`); chains aren't checked | StyleCop issue [#651](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/651) (proposed, never implemented) |
| [BRO1526](rules/BRO1526.md) | A blank line between switch sections (`stylebro_blank_line_between_switch_sections = include`, the default), none (`omit`), or none after a section that ends in a block (`omit_after_block`); after a multi-line block BRO1519 (SA1513) decides. The preset turns it on (`dotnet_diagnostic.BRO1526.severity = warning`) | Roslynator RCS0061 |
| [BRO1143](rules/BRO1143.md) | No `else` after an `if` branch that ends in `return`, `throw`, `break`, `continue`, `goto` or `yield break`. Off by default, also in the preset (a matter of taste) | Meziantou MA0071, Roslynator RCS1211 |
| [BRO1147](rules/BRO1147.md) | No null-forgiving `!` on a value whose flow state is already not-null. Off by default, also in the preset; skips projects with several target frameworks | Sonar S8969, the SDK's IDE0370 |
| [BRO1148](rules/BRO1148.md) | `x is not null` / `x is null` (or `!= null` / `== null`, BRO1133's option) instead of `x.HasValue` / `!x.HasValue` on a nullable value type. Off by default, also in the preset | Meziantou MA0171 |
| [BRO1616](rules/BRO1616.md) | `` tags on lines of their own (`stylebro_summary_layout = multi_line`), or the summary on one line when its text is one line and fits (`single_line_when_fits`) | Roslynator RCS1253, Meziantou MA0177/MA0211 |
| [BRO1136](rules/BRO1136.md) | `x => x` instead of `(x) => x` for one parameter without a type, modifier or attribute | StyleCop issue [#762](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/762) (proposed, never implemented) |
| [BRO1137](rules/BRO1137.md) | No `return;` as the last statement of a body, no `yield break;` as the last statement of an iterator | StyleCop issue [#760](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/760), Roslynator RCS1134 |
| [BRO1138](rules/BRO1138.md) | No `$` without interpolations, no `@` without a backslash, quote or line break, no single-line raw string a plain string can write | Roslynator RCS1214, RCS1192, RCS1262 |
| [BRO1139](rules/BRO1139.md) | `else if` on one line: an `if` on the line after `else`, or alone in the else's block | Roslynator RCS0041, RCS1006 |
| [BRO1140](rules/BRO1140.md) | `record R(int X);` instead of an empty body | Roslynator RCS1251, Meziantou MA0206 |
| [BRO1141](rules/BRO1141.md) | `new T { ... }` without empty parentheses (`stylebro_object_creation_parentheses = omit`, the default) or with them (`include`) | Roslynator RCS1050 |
| [BRO1142](rules/BRO1142.md) | One local per declaration, like BRO1114 (SA1132) for fields | Roslynator RCS1081, Sonar S1659 |
| [BRO1144](rules/BRO1144.md) | `@field`, `@extension`, `@partial` where C# 14 reads the identifier as a keyword (a member or local named `field` used in a property accessor, a type named `extension` and members of that type, a method returning a type named `partial`) | Sonar S8367, S8368, S8380 (no fix); compiler CS9258, CS9272, CS9273, CS9306 |
| [BRO1145](rules/BRO1145.md) | Off by default. `class Marker;` instead of `class Marker { }` for classes, structs and interfaces (C# 12); not reported below C# 12, or in a multi-targeted project unless every framework defaults to C# 12 or the project sets LangVersion | Meziantou MA0206 |
| [BRO1146](rules/BRO1146.md) | `record R` instead of `record class R` (`record struct` stays) | Meziantou MA0174 |
| [BRO1149](rules/BRO1149.md) | An `if` that is the only statement of an enclosing `if`, neither with an `else`, joins it: `if (a && b)` | Sonar S1066 (no fix) |
| [BRO1150](rules/BRO1150.md) | `source.Count(p)` instead of `source.Where(p).Count()`, also for `Any`, `LongCount`, `First`, `FirstOrDefault`, `Last`, `LastOrDefault`, `Single`, `SingleOrDefault` (System.Linq's Enumerable and Queryable; not in expression trees) | Sonar S2971 (no fix), Roslynator RCS1077 |
| [BRO1151](rules/BRO1151.md) | The elements, not an array, for a `params` parameter: `Sum(1, 2, 3)` instead of `Sum(1, new[] { 2, 3 })`, where the call still binds to the same method | Sonar S3878 (no fix) |
| [BRO1527](rules/BRO1527.md) | Auto-accessors only (`get;`, `private set;`, `init;`) spread over several lines go on the declaration's line: `public int Simple { get; set; }` | Roslynator RCS0042 |
| [BRO1408](rules/BRO1408.md) | No `enum E : int` or `class C : object` | Roslynator RCS1042, Sonar S1939 |
| [BRO1409](rules/BRO1409.md) | Off by default. `internal`, not `public`, on an ordinary method of a type that can't be seen outside the assembly; properties, constructors, interface implementations, overrides, attributed methods and types, types with a base from another assembly and convention names (`Dispose`, `GetEnumerator`, ...) are left out, and a name found in a string or `nameof` isn't fixed | StyleCop issue [#2981](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2981) (proposed, never implemented) |
| [BRO1410](rules/BRO1410.md) | Parentheses around a pattern that parses the same without them (`x is (> 0)`, `o is (string s)`); ones BRO1407 (SA1408) wants between `and` and `or` stay | StyleCop's SA1119 checks parenthesized expressions only (its SA1119_p fades the parentheses; StyleBro fades both rules' with BRO1405_p/BRO1410_p) |
| [BRO1313](rules/BRO1313.md) | Off by default. A parameter of an override or interface implementation has the base member's parameter name (named arguments updated) | StyleCop's proposed SA1315 ([#1949](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1949), never implemented); SDK CA1725 (no fix) |
| [BRO1314](rules/BRO1314.md) | Off by default. Methods returning `Task`, `Task`, `ValueTask`, `ValueTask` or `IAsyncEnumerable` end in `Async`; tests, controllers, hubs, event handlers and attributed methods skipped | Roslynator RCS1046, Meziantou MA0137 |
| [BRO1617](rules/BRO1617.md) | A cref's type arguments in braces: `cref="List{T}"`, not `cref="List<T>"` | StyleCop's proposed SA1653 ([#758](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/758), never implemented) |
| [BRO1618](rules/BRO1618.md) | A C# keyword alone in `` (`null`) is written `` | Meziantou MA0154 |
| [BRO1619](rules/BRO1619.md) | Top-level documentation elements in the order summary, typeparam, param, returns, value, exception, remarks, example, seealso | (none; Roslynator RCS1232 orders `` tags only) |
## StyleCop's open bugs
Every open StyleCop.Analyzers issue on a rule StyleBro replaces was read (2026-10-04: 430 open issues, 97 name such a
rule and aren't feature requests), and each one that describes a behavior StyleBro could share was run against StyleBro
with a throwaway test: 50 open issues and 4 closed as not planned. The others were already covered in
[beyond-stylecop.md](https://github.com/bisforboman/stylebro/blob/main/docs/beyond-stylecop.md) (21) or aren't about behavior StyleBro could share (23: documentation wording,
configuration questions, the IDE, missing-documentation rules StyleBro drops).
| Result | Issues |
|---|---|
| StyleBro doesn't have the bug | 34 (30 open, 4 not planned) |
| StyleBro had it too | 8: 7 fixed on 2026-10-04, 1 kept like StyleCop by the owner's decision ([decisions.md](https://github.com/bisforboman/stylebro/blob/main/docs/decisions.md)) |
| Not applicable, or StyleBro is the same or different by design | 12 |
| Issue | Rule | Bug | StyleBro |
|---|---|---|---|
| [#1488](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1488) | SA1519 -> BRO1515 | Braceless child under a multi-line `if (a &&\n b)` header not reported | Same or different by design: Same as SA1519 by default; `csharp_prefer_braces = when_multiline` reports and fixes it |
| [#1588](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1588) | SA1201 -> BRO1001 | Fix moves a method into `#if DEBUG` | Not shared: Not reported (conditional directives are skipped) |
| [#1620](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1620) | SA1116 -> BRO1107 | Fix doesn't reindent a multi-line lambda argument | Shared, fixed: the lambda body moves with the lambda |
| [#1956](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1956), [#3358](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3358) | SA1102 -> BRO1127 | Comment between query clauses reported, fix deletes it | Not shared: Gaps with comments skipped; nothing deleted |
| [#2081](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2081) | SA1626 -> BRO1602 | `//` line inside a `///` block: fix strips slashes from doc lines | Not shared: Not reported, unchanged |
| [#2112](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2112) | SA1203 -> BRO1001 | Fix across `#region`s produces broken regions | Not shared: Regions are fences; not reported |
| [#2183](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2183) | SA1519 -> BRO1515 | Wrong indentation wrapping consecutive `using`s | Not shared: With `stylebro_allow_consecutive_usings = false`, correctly nested and indented |
| [#2696](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2696) | SA1642 -> BRO1606 | Fix output for empty `` | Not shared: Becomes `Initializes a new instance of the class.`; `` not reported |
| [#2832](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2832) | SA1509 -> BRO1501 | Blank line before `{ k, v }` in a dictionary initializer reported | Shared, fixed: an initializer's entries keep their grouping blank line (owner's decision, 2026-10-04) |
| [#3539](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3539) | SA1509 -> BRO1501 | Blank line before a standalone `{ }` block reported | Same or different by design: matched on purpose (a standalone block after `;` is reported, like SA1509) |
| [#2880](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2880) | SA1100 -> BRO1131 | `base.Get(x)` from an override of `Get(x)` | Not shared: Rewritten to `this.Get(x)`, same method (maintainer agreed it's correct) |
| [#3379](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3379) | SA1100 -> BRO1131 | `base.M(other)` in the override of generic virtual `M` reported | Not shared: Not reported |
| [#2890](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2890) | SA1202/SA1203 -> BRO1001 | SA1202 and SA1203 want opposite orders | Not shared: One sort key; fix converges, compiles |
| [#2939](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2939) | SA1309 -> BRO1303 | Rename `_documentIndex` clashes with `out int documentIndex` (CS0844) | Not shared: References qualified with `this.`; compiles |
| [#2967](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2967) | SA1642 -> BRO1606 | `cref="AuthorizationPolicy{XXX}"` not reported | Not shared: Reported and fixed to `{TIDENT}` (stricter than StyleCop, like classic) |
| [#2984](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2984) | SA1642 -> BRO1606 | `` docs not expanded | Not shared: `` members not reported |
| [#3122](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3122) | SA1627 -> BRO1610 | Empty remarks reported for `` | Not shared: Not reported |
| [#3217](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3217) | SA1139 -> BRO1122 | NullReferenceException on `(ulong)14` in an attribute; FP on `unchecked((int)0xFFFFFF0F)` | Not shared: No crash; attribute case fixed to `14UL`; the `unchecked` cast not reported |
| [#3284](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3284) | SA1114 -> BRO1118 | FP on an argument list with `#if` | Not shared: Not reported |
| [#2917](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2917) | SA1114 -> BRO1118 | Comment line after `(` reported | Not shared: Not reported |
| [#3392](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3392) | SA1515 -> BRO1504 | Comment right after `=>` in a switch-expression arm reported | Shared, fixed: `=>` counts like `{` (owner's decision, 2026-10-04) |
| [#3568](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3568) | SA1513 -> BRO1519 | Blank line demanded between a case block's `}` and the next `case` | Shared, kept: same as SA1513 (owner's decision, 2026-10-04) |
| [#3581](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3581) | SA1503 -> BRO1514 | Fix ignores a custom indentation size | Not shared: `indent_size = 1` honored |
| [#3882](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3882), [#3916](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3916) | SA1135 -> BRO1126 | AD0001 on `using X = string;` / tuple aliases | Not shared: No crash, not reported |
| [#3884](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3884) | SA1135 -> BRO1126 | `using T1 = Tasks.Task;` allowed but `using T2 = System.ValueTuple;` reported | Shared, fixed: a type argument starting with an alias is kept |
| [#3921](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3921) | SA1642 -> BRO1606 | Record struct expected to say "class" | Same or different by design: Records are skipped (documented); plain struct fixed to "struct" correctly |
| [#3953](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3953) | SA1413 -> BRO1401 | Nested initializers need 3 `dotnet format` runs | Not shared: One pass |
| [#3948](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3948) | SA1516 -> BRO1505 | Blank line demanded between a property and its backing field | Same or different by design: By design in both (maintainer: fields first) |
| [#3159](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3159) | SA1516 -> BRO1505 | Usings: blank-line groups handled inconsistently | Not shared: Using+using exempt; not reported |
| [#3932](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3932) (= #3849) | SA1514 -> BRO1513 | Doc comment on the file's first line reported | Not shared: Not reported |
| [#3656](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3656) | many | Fixes write CRLF into LF files | Not shared: All rules on a one-line LF file: no `\r` in the output |
| [#3564](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3564) | SA1501 -> BRO1508 | `catch { }` reported | Same or different by design: Same as StyleCop by design |
| [#3559](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3559) | SA1307 -> BRO1306 | `public static X _instance` not reported | Not shared: Reported, renamed to `Instance` |
| [#3557](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3557) | SA1304 -> BRO1306 | `protected readonly foo` never checked | Same or different by design: StyleCop's defaults matched on purpose (protected fields, see BRO1303) |
| [#3183](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3183) | SA1117 -> BRO1108 | Last-argument lambda exception | Same or different by design: same as StyleCop (maintainer: by design); the fix's indentation bug in the reported variant is #1620's, fixed |
| [#3096](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3096) | SA1623/SA1629 -> BRO1604/BRO1603 | Reported on internal members with documentInternalElements false | Same or different by design: Same design as StyleCop (validity of docs that exist) |
| [#3057](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3057) | SA1312 -> BRO1301 | `var _`, `foreach (var _ ...)`, `using var _` reported | Not shared: Not reported (`_`, `__`, `___`, `_3MonthsFromNow`) |
| [#2947](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2947) | SA1513 -> BRO1519 | `}` of a multi-line interpolation hole reported | Not shared: Not reported |
| [#2859](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2859) | SA1305 -> BRO1310 | Hungarian P/Invoke parameters reported | Shared, fixed: parameters of native methods aren't reported |
| [#2784](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2784) | SA1629 -> BRO1603 | Text ending in `"... ."` gets another period | Shared, fixed: not reported |
| [#2689](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2689) | SA1005 -> BRO1002 | `//-:cnd:noEmit` template markers reported | Shared, fixed: `//-:` and `//+:` aren't reported |
| [#1371](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1371) | SA1515 -> BRO1504 | Comment/doc right after `#region` reported | Not shared: Not reported (`///` exempt, line above a directive exempt). BRO1513 does want a blank line before docs after `#region`, matching SA1514 (probed earlier) |
| [#1450](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1450) | SA1119 -> BRO1405 | `(new[] { 1 }).ToArray()` reported | Same or different by design: Same as StyleCop (maintainers kept it) |
| [#1782](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/1782) | SA1612 -> BRO1611 | Stale + missing `` both reported | Not shared: One BRO1611, fix renames the tag to `value` |
| [#3547](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3547) | SA1407 -> BRO1406 | `&`, `\|`, `^`, `>>>` not checked | Same or different by design: StyleCop's operator families on purpose |
| [#3167](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3167) | SA1408 -> BRO1407 | `a ?? false && b` not reported | Same or different by design: Feature request; same families as StyleCop |
| [#3955](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3955) | SA1201 -> BRO1001 | "A should not follow a field" on C# 14 `extension` blocks | Not shared: Real build + `dotnet format` (SDK 10, C# 14): no BRO1001 in a type with an extension block (skipped, so also a field after a method there isn't sorted), no crash, compiles |
| [#2079](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2079) | SA1309 -> BRO1303 | Rename misses a reference in another class | Same or different by design: The repro reads a private field from another class (doesn't compile) |
| Not planned [#3837](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3837) | SA1106 -> BRO1101 | `public class A;` reported as empty statement | Not shared: Not reported |
| Not planned [#3659](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3659) | SA1400 -> BRO1404/BRO1007 | `static file class X` asked for a modifier | Not shared: Not reported; a part without `file` of a `file` type isn't either (fixed 2026-10-04: BRO1007 added `internal` there, CS9052) |
| Not planned [#3716](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3716) | SA1119 -> BRO1405 | `#if (MyProperty)` (templates) reported | Not shared: Not reported |
| Not planned [#3769](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3769) | SA1300 -> BRO1309 | Record/primary-ctor parameters `name` want PascalCase | Not shared: Not reported |
---
Source: https://bisforboman.github.io/stylebro/stylecop-mapping/
# StyleCop rules and StyleBro
What happens to each StyleCop.Analyzers rule when a project moves to StyleBro. Every rule gets one of three answers:
- **SDK**: the .NET SDK already has an equivalent that `dotnet format` fixes. StyleBro's preset turns it on.
- **StyleBro**: StyleBro provides (or should provide) a rule with a safe automatic fix.
- **Drop**: there is no safe automatic fix, so StyleBro deliberately doesn't cover it.
- **Not yet**: a StyleBro rule (or turning on an SDK setting) looks feasible but isn't done.
Every rule StyleBro doesn't cover is explained, with what it would take to revisit it, in [skipped-rules.md](skipped-rules.md).
Status: **complete: every StyleCop rule is StyleBro, SDK or dropped with a reason (2026-10-07)**. Of 197 rules: 44 SDK, 109 StyleBro (109 done), 42 drop, 0 not yet done, 2 not applicable or variants.
## How this was measured
Scripts and data: [scripts/stylecop-survey](https://github.com/bisforboman/stylebro/tree/main/scripts/stylecop-survey).
- **Inventory**: every diagnostic in StyleCop.Analyzers 1.1.118 and 1.2.0-beta.556, read from the analyzer DLLs, including whether StyleCop itself ships a code fix. 1.2.0-beta.556 adds SA1141, SA1142, SA1316, SA1414 and some variants.
- **Teams keeping it on**: the effective setting for production code in 3 repos that use StyleCop: [OpenTelemetry .NET](https://github.com/open-telemetry/opentelemetry-dotnet) (`ea1e3f3`, StyleCop 1.2.0-beta.556), [Polly](https://github.com/App-vNext/Polly) (`0275bc2`, StyleCop 1.2.0-beta.556), a private 30-project app (StyleCop 1.1.118). Shown as OTel / Polly / App.
- **Diagnostics with every rule on**: each repo built with all StyleCop rules enabled as warnings (and XML docs on), counting unique diagnostics. Shown as OTel / Polly / App. Where a team keeps a rule on, the count is near zero; where it is off, the count shows how much code would change. 10 of OpenTelemetry's 81 projects (examples and some tests) did not compile in the survey (CS1705 in a shallow clone) and are missing from its counts.
- **SDK check**: for 94 rules, a small violating example was formatted with `dotnet format style` and `dotnet format whitespace` (SDK only, not StyleCop's own fixes) using StyleCop-like SDK settings, and StyleCop was run again. "fixed" means the diagnostic was gone. Each rule was checked on one example, so a "fixed" rule can still have cases the SDK handles differently.
## Not yet done, by demand
Rules that look feasible but aren't covered yet, ordered by how many of the 3 teams keep them on, then by how often they fire in App. Details and what each would take: [skipped-rules.md](skipped-rules.md).
| Rule | Title | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | Status |
|---|---|---|---|---|
**Observation.** Polly turns off most of StyleCop's spacing, layout and naming rules and enforces the SDK equivalents instead: 101 IDE rule severities, including IDE0055 (formatting), IDE0011 (braces) and IDE1006 with 10 naming rules, plus `EnforceCodeStyleInBuild`. That is the model StyleBro's preset follows. All three repos turn off SA1200 (using placement) and SA1401 (fields should be private).
**Naming note.** The SDK can detect all of StyleCop's naming rules (IDE1006 with naming rules in `.editorconfig`), but `dotnet format` cannot fix them: it reports "Unable to fix IDE1006. Code fix NamingStyleCodeFixProvider doesn't support Fix All in Solution." A StyleBro rename fix with Fix All would close that gap. Renaming public API breaks callers, so it should start with private and local symbols.
The documentation rules (SA16xx) are mostly not tested yet. The ones where StyleCop has a fix are candidates for the planned BRO16xx rules.
## All rules
### Documentation
| Rule | Title | Default | StyleCop fix | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | SDK check | Proposal |
|---|---|---|---|---|---|---|---|
| SA1600 | Elements should be documented | on | yes | off / on / off | 6,259 / 3,249 / 19,004 | | StyleBro **BRO1601** (done) for overrides and implementations only (``); other missing documentation is not reported (no placeholder stubs, user decision) ([details](skipped-rules.md#sa1600)) |
| SA1601 | Partial elements should be documented | on | yes | on / off / on | 16 / 78 / 0 | | Drop: Missing documentation on a partial element; the only fix is placeholder text (no stubs, user decision). ([details](skipped-rules.md#sa1601)) |
| SA1602 | Enumeration items should be documented | on | yes | on / on / on | 0 / 10 / 530 | | Drop: Missing documentation on enum members; the only fix is placeholder text (no stubs, user decision). 530 findings in the private app. ([details](skipped-rules.md#sa1602)) |
| SA1603 | Documentation should contain valid XML | off | no | off / on / off | 0 / 0 / 0 | | Drop: Off by default, no StyleCop fix; invalid XML needs a person to say what was meant. ([details](skipped-rules.md#sa1603)) |
| SA1604 | Element documentation should have summary | on | no | on / on / on | 0 / 0 / 1 | | Drop: A doc comment without ``; the fix would be placeholder text. ([details](skipped-rules.md#sa1604)) |
| SA1605 | Partial element documentation should have summary | on | no | on / on / on | 0 / 0 / 0 | | Drop: Same as SA1604 for partial elements. ([details](skipped-rules.md#sa1605)) |
| SA1606 | Element documentation should have summary text | on | no | on / on / on | 0 / 0 / 0 | | Drop: An empty ``; the fix would be placeholder text (no stubs, user decision). ([details](skipped-rules.md#sa1606)) |
| SA1607 | Partial element documentation should have summary text | on | no | on / on / on | 0 / 0 / 0 | | Drop: Same as SA1606 for partial elements. ([details](skipped-rules.md#sa1607)) |
| SA1608 | Element documentation should not have default summary | on | no | on / on / on | 0 / 0 / 0 | | Drop: The default "Summary description for X" text has to be replaced by a person. ([details](skipped-rules.md#sa1608)) |
| SA1609 | Property documentation should have value | off | yes | off / off / off | 292 / 171 / 8 | | Drop: Off by default. Missing ``; the fix would be placeholder text (no stubs, user decision). ([details](skipped-rules.md#sa1609)) |
| SA1610 | Property documentation should have value text | on | yes | on / off / on | 0 / 0 / 0 | | Drop: An empty ``; StyleCop's fix writes placeholder text. ([details](skipped-rules.md#sa1610)) |
| SA1611 | Element parameters should be documented | on | no | on / on / on | 8 / 1 / 11 | | Drop: Missing ``; the fix would be placeholder text (no stubs, user decision). ([details](skipped-rules.md#sa1611)) |
| SA1612 | Element parameter documentation should match element parameters | on | no | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1611** (done), with a fix (StyleCop has none): stale tags renamed or removed, tags reordered |
| SA1613 | Element parameter documentation should declare parameter name | on | no | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1612** (done) when the name is certain (one unnamed tag and one undocumented parameter, or all unnamed with one per parameter); StyleCop reports every unnamed tag and has no fix |
| SA1614 | Element parameter documentation should have text | on | no | on / on / off | 0 / 0 / 4 | | Drop: An empty ``; the fix would be placeholder text. ([details](skipped-rules.md#sa1614)) |
| SA1615 | Element return value should be documented | on | yes | on / on / on | 3 / 1 / 6 | | Drop: Missing ``; the fix would be placeholder text. ([details](skipped-rules.md#sa1615)) |
| SA1616 | Element return value documentation should have text | on | yes | on / on / on | 0 / 0 / 0 | | Drop: An empty ``; the fix would be placeholder text. ([details](skipped-rules.md#sa1616)) |
| SA1617 | Void return value should not be documented | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1608** (done), same results and output as StyleCop |
| SA1618 | Generic type parameters should be documented | on | no | on / on / on | 0 / 0 / 1 | | Drop: Missing ``; the fix would be placeholder text. ([details](skipped-rules.md#sa1618)) |
| SA1619 | Generic type parameters should be documented partial class | on | no | on / on / on | 0 / 0 / 0 | | Drop: Same as SA1618 for partial types. ([details](skipped-rules.md#sa1619)) |
| SA1620 | Generic type parameter documentation should match type parameters | on | no | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1613** (done), same positions as StyleCop, with a fix (StyleCop has none): stale tags renamed or removed, tags reordered |
| SA1621 | Generic type parameter documentation should declare parameter name | on | no | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1614** (done) when the name is certain, like BRO1612 |
| SA1622 | Generic type parameter documentation should have text | on | no | on / on / on | 0 / 0 / 0 | | Drop: An empty ``; the fix would be placeholder text. ([details](skipped-rules.md#sa1622)) |
| SA1623 | Property summary documentation should match accessors | on | yes | on / on / off | 0 / 0 / 8 | | StyleBro **BRO1604** (done), same results as StyleCop |
| SA1624 | Property summary documentation should omit accessor with restricted access | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1605** (done), same results as StyleCop |
| SA1625 | Element documentation should not be copied and pasted | on | no | on / on / on | 0 / 0 / 0 | | Drop: Identical text copied between tags has to be rewritten by a person. ([details](skipped-rules.md#sa1625)) |
| SA1626 | Single-line comments should not use documentation style slashes | on | yes | on / on / on | 0 / 0 / 2 | | StyleBro **BRO1602** (done), same results as StyleCop |
| SA1627 | Documentation text should not be empty | on | no | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1610** (done) for empty remarks, with a fix (removal); StyleCop has none |
| SA1628 | Documentation text should begin with a capital letter | off | no | off / on / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. StyleCop 1.2 never reports it (not implemented, probed while building BRO16xx). ([details](skipped-rules.md#sa1628)) |
| SA1629 | Documentation text should end with a period | on | yes | on / on / off | 0 / 0 / 29 | | StyleBro **BRO1603** (done). Differs from StyleCop: text ending with `?`, `!` or `:` is not reported |
| SA1630 | Documentation text should contain whitespace | off | no | off / on / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. Off by default and no diagnostic on a one-word summary (probed). ([details](skipped-rules.md#sa1630)) |
| SA1631 | Documentation should meet character percentage | off | no | off / on / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. Off by default and no diagnostic on a summary of symbols (probed). ([details](skipped-rules.md#sa1631)) |
| SA1632 | Documentation text should meet minimum character length | off | no | off / on / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. Off by default and no diagnostic on a one-letter summary (probed). ([details](skipped-rules.md#sa1632)) |
| SA1633 | File should have header | on | yes | on / off / off | 0 / 0 / 2,632 | fixed | SDK: IDE0073 (`file_header_template`) for a plain header (`xmlHeader: false`); StyleBro **BRO1615** for the XML header |
| SA1634 | File header should show copyright | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1615** (done), one rule for the XML header; same positions and fixed headers as StyleCop, except that the fix keeps other comments (StyleCop's deletes them) |
| SA1635 | File header should have copyright text | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1615** (done), one rule for the XML header; same positions and fixed headers as StyleCop, except that the fix keeps other comments (StyleCop's deletes them) |
| SA1636 | File header copyright text should match | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1615** (done), one rule for the XML header; same positions and fixed headers as StyleCop, except that the fix keeps other comments (StyleCop's deletes them) |
| SA1637 | File header should contain file name | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1615** (done), one rule for the XML header; same positions and fixed headers as StyleCop, except that the fix keeps other comments (StyleCop's deletes them) |
| SA1638 | File header file name documentation should match file name | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1615** (done), one rule for the XML header; same positions and fixed headers as StyleCop, except that the fix keeps other comments (StyleCop's deletes them) |
| SA1639 | File header should have summary | off | yes | off / off / off | 0 / 0 / 0 | | Drop: Off by default; the header's `` has to be written by a person. ([details](skipped-rules.md#sa1639)) |
| SA1640 | File header should have valid company text | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1615** (done), one rule for the XML header; same positions and fixed headers as StyleCop, except that the fix keeps other comments (StyleCop's deletes them) |
| SA1641 | File header company name text should match | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1615** (done), one rule for the XML header; same positions and fixed headers as StyleCop, except that the fix keeps other comments (StyleCop's deletes them) |
| SA1642 | Constructor summary documentation should begin with standard text | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1606** (done), same results as StyleCop (the fix adds a space after the standard sentence) |
| SA1643 | Destructor summary documentation should begin with standard text | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1607** (done), same results as StyleCop (the fix adds a space after the standard sentence) |
| SA1644 | Documentation headers should not contain blank lines | off | no | off / on / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. StyleCop 1.2 never reports it (not implemented, probed while building BRO16xx). ([details](skipped-rules.md#sa1644)) |
| SA1645 | Included documentation file does not exist | off | no | off / on / off | 0 / 0 / 0 | | Drop: Off by default; a missing `` file can't be fixed automatically. ([details](skipped-rules.md#sa1645)) |
| SA1646 | Included documentation XPath does not exist | off | no | off / on / off | 0 / 0 / 0 | | Drop: Off by default; a wrong `` XPath can't be fixed automatically. ([details](skipped-rules.md#sa1646)) |
| SA1647 | Include node does not contain valid file and path | off | no | off / on / off | 0 / 0 / 0 | | Drop: Off by default; an `` without file/path can't be fixed automatically. ([details](skipped-rules.md#sa1647)) |
| SA1648 | inheritdoc should be used with inheriting class | on | no | on / on / on | 0 / 0 / 0 | | Drop: `` where there is nothing to inherit: the fix is real documentation. ([details](skipped-rules.md#sa1648)) |
| SA1649 | File name should match first type name | on | yes | off / on / off | 0 / 0 / 1 | | Drop: Renaming files isn't something `dotnet format` can do. ([details](skipped-rules.md#sa1649)) |
| SA1650 | Element documentation should be spelled correctly | off | no | off / on / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. Off by default and no diagnostic on misspelled words (probed). ([details](skipped-rules.md#sa1650)) |
| SA1651 | Do not use placeholder elements | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1609** (done), same results and output as StyleCop |
### Layout
| Rule | Title | Default | StyleCop fix | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | SDK check | Proposal |
|---|---|---|---|---|---|---|---|
| SA1500 | Braces for multi-line statements should not share line | on | yes | on / on / off | 0 / 0 / 6 | fixed | SDK: IDE0055 (`csharp_new_line_before_open_brace = all`) |
| SA1501 | Statement should not be on a single line | on | yes | on / on / off | 0 / 0 / 1 | fixed | StyleBro **BRO1508** (done). Differs from StyleCop: blocks with a comment inside, and a switch-section block on the line of `switch (x) {`, are not reported |
| SA1502 | Element should not be on a single line | on | yes | on / on / off | 0 / 0 / 356 | fixed | StyleBro **BRO1509** (done), same positions as StyleCop (local functions reported once, as BRO1509) |
| SA1503 | Braces should not be omitted | on | yes | on / off / off | 0 / 0 / 14 | fixed | StyleBro **BRO1514** (done; replaces IDE0011, whose fix breaks multi-targeted projects). Skips comments/directives where the braces go and multi-line strings |
| SA1504 | All accessors should be single-line or multi-line | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1510** (done), same positions as StyleCop; the fix decides per property (StyleCop's applies one direction to the whole project) and keeps comments |
| SA1505 | Opening braces should not be followed by blank line | on | yes | on / on / off | 0 / 0 / 37 | not fixed | StyleBro **BRO1503** (done), same results as StyleCop |
| SA1506 | Element documentation headers should not be followed by blank line | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1511** (done), same positions and output as StyleCop |
| SA1507 | Code should not contain multiple blank lines in a row | on | yes | on / off / on | 0 / 0 / 0 | fixed | StyleBro **BRO1517** (done; replaces IDE2000, whose fix breaks multi-targeted projects). Leaves runs after `{`, before `}` and below a comment to BRO1503/BRO1518/BRO1506 |
| SA1508 | Closing braces should not be preceded by blank line | on | yes | on / off / off | 0 / 46 / 85 | fixed | StyleBro **BRO1518** (done; replaces IDE2002). Leaves blank lines below a comment and in empty blocks to BRO1506/BRO1503 |
| SA1509 | Opening braces should not be preceded by blank line | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1501** (done), same results as StyleCop |
| SA1510 | Chained statement blocks should not be preceded by blank line | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1502** (done), same results as StyleCop |
| SA1511 | While-do footer should not be preceded by blank line | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1512** (done), same positions and output as StyleCop |
| SA1512 | Single-line comments should not be followed by blank line | on | yes | off / off / on | 82 / 46 / 0 | not fixed | StyleBro **BRO1506** (done), same results as StyleCop |
| SA1513 | Closing brace should be followed by blank line | on | yes | on / on / off | 0 / 0 / 119 | fixed | StyleBro **BRO1519** (done; replaces IDE2003). Leaves a comment, documentation or member after `}` to BRO1504/BRO1513/BRO1505 |
| SA1514 | Element documentation header should be preceded by blank line | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1513** (done); documentation right below a comment is not reported (it would conflict with SA1512/BRO1506) |
| SA1515 | Single-line comment should be preceded by blank line | on | yes | on / on / off | 0 / 0 / 31 | not fixed | StyleBro **BRO1504** (done), same results as StyleCop |
| SA1516 | Elements should be separated by blank line | on | yes | on / off / off | 0 / 44 / 5,873 | not fixed | StyleBro **BRO1505** (done), same positions as StyleCop |
| SA1517 | Code should not contain blank lines at start of file | on | yes | on / on / off | 0 / 0 / 11 | fixed | SDK: only as a side effect of IDE0073 (file header); not fixed without `file_header_template` |
| SA1518 | Use line endings correctly at end of file | on | yes | off / off / off | 2 / 40 / 31 | not fixed | StyleBro **BRO1507** (done), same results as StyleCop's default setting |
| SA1519 | Braces should not be omitted from multi-line child statement | on | yes | on / on / on | 0 / 0 / 0 | fixed | StyleBro **BRO1515** (done; replaces IDE0011). Same skips as BRO1514 |
| SA1520 | Use braces consistently | on | yes | on / on / on | 0 / 0 / 0 | fixed | StyleBro **BRO1516** (done; replaces IDE0011). Same skips as BRO1514 |
### Maintainability
| Rule | Title | Default | StyleCop fix | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | SDK check | Proposal |
|---|---|---|---|---|---|---|---|
| SA1119 | Statement should not use unnecessary parenthesis | on | yes | on / on / off | 0 / 0 / 1 | fixed | StyleBro **BRO1405** (done; replaces IDE0047, whose fix breaks multi-targeted projects). Skips parentheses whose removal would change how the code parses (StyleCop's fix can make a generic call) |
| SA1119_p | Statement should not use unnecessary parenthesis | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1405** (done). Not a rule of its own: the Hidden companion of SA1119 that StyleCop reports on the same parentheses' `(` and `)` so the IDE greys them out (it only checks parenthesized expressions, never patterns). BRO1405 reports the same parentheses; the greying isn't ported |
| SA1400 | Access modifier should be declared | on | yes | on / on / off | 0 / 0 / 10 | fixed | StyleBro **BRO1404** (done; replaces IDE0040), same positions and output as StyleCop |
| SA1401 | Fields should be private | on | no | off / off / off | 141 / 48 / 39 | | Drop: Making a public or protected field private changes the public API and breaks callers. 141 / 48 / 39 findings with the rule on. ([details](skipped-rules.md#sa1401)) |
| SA1402 | File may only contain a single type | on | yes | on / off / off | 0 / 34 / 1 | | Drop: Moving a type to its own file isn't something `dotnet format` can do (code fixes can't add documents through it). ([details](skipped-rules.md#sa1402)) |
| SA1403 | File may only contain a single namespace | on | no | on / on / on | 0 / 0 / 0 | | Drop: StyleCop has no fix; splitting namespaces into files has the same problem as SA1402. ([details](skipped-rules.md#sa1403)) |
| SA1404 | Code analysis suppression should have justification | on | yes | on / on / on | 0 / 0 / 0 | | Drop: The justification of a SuppressMessage has to be written by a person. ([details](skipped-rules.md#sa1404)) |
| SA1405 | Debug.Assert should provide message text | on | no | on / on / on | 0 / 0 / 0 | | Drop: StyleCop has no fix; the message for Debug.Assert has to be written by a person. ([details](skipped-rules.md#sa1405)) |
| SA1406 | Debug.Fail should provide message text | on | no | on / on / on | 0 / 0 / 0 | | Drop: StyleCop has no fix; the message for Debug.Fail has to be written by a person. ([details](skipped-rules.md#sa1406)) |
| SA1407 | Arithmetic expressions should declare precedence | on | yes | on / on / off | 0 / 0 / 5 | not fixed | StyleBro **BRO1406** (done; replaces IDE0048, whose fix breaks multi-targeted projects and needed two runs), same positions and output as StyleCop |
| SA1408 | Conditional expressions should declare precedence | on | yes | on / on / off | 0 / 0 / 14 | fixed | StyleBro **BRO1407** (done; replaces IDE0048). Also checks `and`/`or` patterns, like StyleCop's current source (after 1.2.0-beta.556) |
| SA1409 | Remove unnecessary code | off | no | off / on / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. Off by default and no diagnostic for an empty `try { } finally { }` (probed). ([details](skipped-rules.md#sa1409)) |
| SA1410 | Remove delegate parenthesis when possible | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1403** (done), only where BRO1125 doesn't turn the method into a lambda |
| SA1411 | Attribute constructor should not use unnecessary parenthesis | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1402** (done), same positions; the fix also removes the space in `( )` |
| SA1412 | Store files as UTF-8 with byte order mark | off | yes | off / off / off | 876 / 325 / 2,581 | fixed | SDK: `charset = utf-8-bom` (`dotnet format` writes the BOM); off by default in StyleCop and not in the preset; stylebro-migrate sets it when SA1412 is on |
| SA1413 | Use trailing comma in multi-line initializers | on | yes | on / off / off | 0 / 407 / 1,862 | not fixed | StyleBro **BRO1401** (done). Differs from StyleCop: lists with `#if` between their braces are not reported |
### Naming
| Rule | Title | Default | StyleCop fix | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | SDK check | Proposal |
|---|---|---|---|---|---|---|---|
| SA1300 | Element should begin with upper-case letter | on | yes | on / off / off | 0 / 0 / 4 | not fixed | StyleBro **BRO1309** (done), rename with Fix All incl. overrides/implementations; namespaces: **BRO1312** (done, opt-in: off by default, `stylebro-migrate` turns it on with SA1300) |
| SA1301 | Element should begin with lower-case letter | off | no | off / off / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. Off by default and no diagnostic (probed). StyleCop keeps the id for compatibility. ([details](skipped-rules.md#sa1301)) |
| SA1302 | Interface names should begin with I | on | yes | on / off / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1304** (done), rename with Fix All, same results as StyleCop |
| SA1303 | Const field names should begin with upper-case letter | on | yes | on / off / off | 0 / 0 / 4 | not fixed | StyleBro **BRO1306** (done), rename with Fix All |
| SA1304 | Non-private readonly fields should begin with upper-case letter | on | yes | on / off / on | 0 / 1 / 0 | | StyleBro **BRO1306** (done), rename with Fix All |
| SA1305 | Field names should not use Hungarian notation | off | no | off / off / off | 82 / 1 / 14 | | StyleBro **BRO1310** (done, off by default like StyleCop), the prefix removed in the same rename as the other naming rules |
| SA1306 | Field names should begin with lower-case letter | on | yes | on / off / off | 0 / 4 / 11 | not fixed | StyleBro **BRO1303** (done) for private and protected fields; `stylebro_private_field_naming` picks `count` (default) or `_count` |
| SA1307 | Accessible fields should begin with upper-case letter | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1306** (done), rename with Fix All |
| SA1308 | Variable names should not be prefixed | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1307** (done), rename to the complete correct name in one pass |
| SA1309 | Field names should not begin with underscore | on | yes | on / off / off | 0 / 486 / 1,060 | not fixed | StyleBro **BRO1303** (done, private and protected fields) and **BRO1306** (done, constants, static readonly and public/internal fields) |
| SA1310 | Field names should not contain underscore | on | yes | on / on / off | 184 / 0 / 7 | | StyleBro **BRO1308** (done), words joined in the field's casing (MAX_VALUE -> MaxValue) |
| SA1311 | Static readonly fields should begin with upper-case letter | on | yes | on / off / off | 0 / 0 / 4 | not fixed | StyleBro **BRO1306** (done), rename with Fix All |
| SA1312 | Variable names should begin with lower-case letter | on | yes | on / off / off | 0 / 14 / 7 | not fixed | StyleBro **BRO1301** (done), rename with Fix All; also fixes `_name` |
| SA1313 | Parameter names should begin with lower-case letter | on | yes | on / off / off | 0 / 0 / 90 | not fixed | StyleBro **BRO1302** (done), rename with Fix All incl. named arguments and overrides; also fixes `_name` |
| SA1314 | Type parameter names should begin with T | on | yes | on / off / off | 0 / 0 / 0 | not fixed | StyleBro **BRO1305** (done), rename with Fix All, same results as StyleCop |
| SA1316 (1.2 beta) | Tuple element names should use correct casing | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1311** (done), the name renamed with every use and override solution-wide; `stylebro_tuple_element_name_casing` from tupleElementNameCasing |
| SX1309 | Field names should begin with underscore | off | yes | off / off / off | 842 / 0 / 32 | | StyleBro **BRO1303** (done) with `stylebro_private_field_naming = _camelCase`, which stylebro-migrate sets when SX1309 is on |
| SX1309S | Static field names should begin with underscore | off | yes | off / off / off | 19 / 1 / 0 | | StyleBro **BRO1303** (done) with `stylebro_private_mutable_static_field_naming = _camelCase` (the style of private static fields that aren't readonly or const, SX1309S's fields; not set: `stylebro_private_field_naming`'s). stylebro-migrate counts SX1309S as covered when it writes `_camelCase` (always with SX1309); on its own, with instance fields named `count`, SA1309 is off, StyleCop accepts instance fields with and without `_`, and the report lists it as not expressible |
### Ordering
| Rule | Title | Default | StyleCop fix | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | SDK check | Proposal |
|---|---|---|---|---|---|---|---|
| SA1200 | Using directives should be placed correctly | on | yes | off / off / off | 0 / 0 / 14,192 | fixed | StyleBro **BRO1008** (done; replaces IDE0065, whose fix can change what names bind to: CS0104/CS0118 in eShop and Ocelot). Moves the usings only when no name can be looked up differently; skips files with several namespaces, directives before the namespace body, usings on both levels |
| SA1201 | Elements should appear in the correct order | on | yes | on / off / off | 0 / 89 / 839 | | StyleBro **BRO1001** (done) |
| SA1202 | Elements should be ordered by access | on | yes | on / off / off | 0 / 71 / 185 | | StyleBro **BRO1001** (done) |
| SA1203 | Constants should appear before fields | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1001** (done) |
| SA1204 | Static elements should appear before instance elements | on | yes | on / on / off | 0 / 2 / 50 | | StyleBro **BRO1001** (done) |
| SA1205 | Partial elements should declare access | on | yes | on / on / on | 0 / 0 / 0 | fixed | StyleBro **BRO1007** (done; replaces IDE0040, whose fix breaks multi-targeted projects), same positions and output as StyleCop |
| SA1206 | Declaration keywords should follow order | on | yes | on / on / on | 0 / 0 / 0 | fixed | SDK: IDE0036 (`csharp_preferred_modifier_order`, the SDK default order) ([details](skipped-rules.md#sa1206)) |
| SA1207 | Protected should come before internal | on | yes | on / on / on | 0 / 0 / 0 | fixed | SDK: IDE0036 (`csharp_preferred_modifier_order`; `internal protected` -> `protected internal`) |
| SA1208 | System using directives should be placed before other using directives | on | yes | on / on / off | 0 / 0 / 57 | fixed | SDK: using sorting in `dotnet format` (`dotnet_sort_system_directives_first = true`) |
| SA1209 | Using alias directives should be placed after other using directives | on | yes | on / on / off | 0 / 0 / 4 | fixed | SDK: using sorting in `dotnet format` |
| SA1210 | Using directives should be ordered alphabetically by namespace | on | yes | on / on / off | 0 / 0 / 290 | fixed | SDK: using sorting in `dotnet format` |
| SA1211 | Using alias directives should be ordered alphabetically by alias name | on | yes | on / on / on | 0 / 0 / 0 | fixed | SDK: using sorting in `dotnet format` |
| SA1212 | Property accessors should follow order | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1003** (done), same positions and output as StyleCop |
| SA1213 | Event accessors should follow order | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1004** (done), same positions and output as StyleCop |
| SA1214 | Readonly fields should appear before non-readonly fields | on | yes | on / on / off | 0 / 0 / 2 | | StyleBro **BRO1001** (done) |
| SA1216 | Using static directives should be placed at the correct location | on | yes | on / on / on | 0 / 0 / 0 | fixed | SDK: using sorting in `dotnet format` |
| SA1217 | Using static directives should be ordered alphabetically | on | yes | on / on / on | 0 / 0 / 0 | fixed | SDK: using sorting in `dotnet format` |
### Readability
| Rule | Title | Default | StyleCop fix | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | SDK check | Proposal |
|---|---|---|---|---|---|---|---|
| SA1100 | Do not prefix calls with base unless local implementation exists | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1131** (done). Differs from StyleCop: virtual members in types that aren't sealed are not reported (`this.` would dispatch to a derived override) |
| SA1101 | Prefix local calls with this | on | yes | on / off / off | 0 / 4,007 / 17,153 | fixed | SDK: IDE0009 (`dotnet_style_qualification_for_* = true`) |
| SA1102 | Query clause should follow previous clause | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1127** (done), same positions as StyleCop except blank lines around a comment line |
| SA1103 | Query clauses should be on separate lines or all on one line | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1128** (done), always one clause per line (StyleCop's fix picks one line or split, varying between runs) |
| SA1104 | Query clause should begin on new line when previous clause spans multiple lines | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1129** (done), same positions as StyleCop |
| SA1105 | Query clauses spanning multiple lines should begin on own line | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1130** (done), same positions as StyleCop |
| SA1106 | Code should not contain empty statements | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1101** (done). Differs from StyleCop: embedded (`while (x) ;`) and labeled empty statements are not reported |
| SA1107 | Code should not contain multiple statements on one line | on | yes | on / off / off | 0 / 243 / 0 | fixed | SDK: IDE0055 (`csharp_preserve_single_line_statements = false`) |
| SA1108 | Block statements should not contain embedded comments | on | no | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1132** (done), same positions as StyleCop; StyleCop has no fix, StyleBro moves the comment into the block. Not reported: headers spanning several lines (user decision), single-line blocks, comments spanning lines |
| SA1109 | Block statements should not contain embedded regions | off | no | off / on / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. Off by default and no diagnostic for a region between `if (b)` and its block (probed). ([details](skipped-rules.md#sa1109)) |
| SA1110 | Opening parenthesis or bracket should be on declaration line | on | yes | on / on / off | 0 / 0 / 3 | | StyleBro **BRO1109** (done), same results and output as StyleCop |
| SA1111 | Closing parenthesis should be on line of last parameter | on | yes | on / on / off | 0 / 0 / 740 | | StyleBro **BRO1110** (done), same results and output as StyleCop |
| SA1112 | Closing parenthesis should be on line of opening parenthesis | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1116** (done), same positions and output as StyleCop |
| SA1113 | Comma should be on the same line as previous parameter | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1117** (done), same positions and output as StyleCop |
| SA1114 | Parameter list should follow declaration | on | no | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1118** (done), with a fix (StyleCop has none); a comment before the first item is not reported |
| SA1115 | Parameter should follow comma | on | no | on / off / off | 0 / 0 / 0 | | StyleBro **BRO1119** (done), with a fix (StyleCop has none) |
| SA1116 | Split parameters should start on line after declaration | on | yes | on / off / off | 0 / 375 / 266 | not fixed | StyleBro **BRO1107** (done), also checks record and primary-constructor parameters |
| SA1117 | Parameters should be on same line or separate lines | on | no | on / off / off | 0 / 370 / 102 | not fixed | StyleBro **BRO1108** (done), also checks record and primary-constructor parameters; unlike StyleCop, has a fix |
| SA1118 | Parameter should not span multiple lines | on | no | on / on / off | 0 / 0 / 126 | | Drop: StyleCop has no fix. A multi-line argument (other than the first, or a lambda/anonymous object) needs extracting into a variable: a refactoring that names things. 126 findings in the private app. ([details](skipped-rules.md#sa1118)) |
| SA1120 | Comments should contain text | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1120** (done), same positions; the fix removes every empty comment at that end of the group in one run |
| SA1121 | Use built-in type alias | on | yes | on / on / off | 0 / 0 / 0 | fixed | SDK: IDE0049 (`dotnet_style_predefined_type_* = true`) |
| SA1122 | Use string.Empty for empty strings | on | yes | on / off / off | 0 / 7 / 352 | not fixed | StyleBro **BRO1106** (done), same results as StyleCop |
| SA1123 | Do not place regions within elements | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1113** (done), same results and output as StyleCop |
| SA1124 | Do not use regions | on | yes | on / off / off | 0 / 264 / 17 | | StyleBro **BRO1112** (done), off in the preset (StyleCop has it on; opt-in because it's a large one-time change) |
| SA1125 | Use shorthand for nullable types | on | no | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1115** (done), same positions, with a fix (StyleCop has none) |
| SA1126 | Prefix calls correctly | off | no | off / off / off | 0 / 0 / 0 | | Drop: StyleCop never reports it. Off by default and no diagnostic on an unprefixed member call (probed). ([details](skipped-rules.md#sa1126)) |
| SA1127 | Generic type constraints should be on their own line | on | yes | on / on / off | 0 / 0 / 82 | | StyleBro **BRO1111** (done), same results and output as StyleCop |
| SA1128 | Put constructor initializers on their own line | on | yes | on / on / off | 0 / 0 / 408 | not fixed | StyleBro **BRO1105** (done), same results as StyleCop |
| SA1129 | Do not use default value type constructor | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1104** (done). Differs from StyleCop: `new T()` on a type parameter and `new S();` statements are not reported |
| SA1130 | Use lambda syntax | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1125** (done), lambdas checked by binding them in place (same delegate type and overload); StyleCop misses some calls and its fix breaks `var` |
| SA1131 | Use readable conditions | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1103** (done). Differs from StyleCop: comparisons using a type's own operator are not reported |
| SA1132 | Do not combine fields | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1114** (done), same positions; the fix copies attributes and documentation to every field (StyleCop's keeps attributes on the first only) |
| SA1133 | Do not combine attributes | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1102** (done). Differs from StyleCop: lists with comments between attributes are not reported |
| SA1134 | Attributes should not share line | on | yes | on / off / on | 0 / 9 / 0 | fixed | SDK: IDE0055 formatting |
| SA1135 | Using directives should be qualified | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1126** (done), same positions and output as StyleCop; skips names an enclosing namespace would hide |
| SA1136 | Enum values should be on separate lines | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1121** (done), same positions; an enum on one line gets BRO1509's expansion |
| SA1137 | Elements should have the same indentation | on | yes | on / off / off | 0 / 4 / 4 | fixed | SDK: IDE0055 formatting |
| SA1139 | Use literal suffix notation instead of casting | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1122** (done), only when the suffixed literal has exactly the cast's value (StyleCop's fix can change decimals) |
| SA1141 (1.2 beta) | Use tuple syntax | on | yes | on / on / on | 0 / 0 / 0 | | StyleBro **BRO1123** (done), every type-only position (StyleCop: declarations only); StyleCop's fix has no Fix All |
| SA1142 (1.2 beta) | Refer to tuple fields by name | on | yes | on / off / on | 0 / 0 / 0 | | StyleBro **BRO1124** (done), same positions as StyleCop except nameof; StyleCop's fix throws under `dotnet format` |
| SA1414 (1.2 beta) | Tuple types in signatures should have element names | on | no | on / on / on | 0 / 0 / 0 | | Drop: StyleCop has no fix (1.2 only); tuple element names in signatures have to be chosen by a person. ([details](skipped-rules.md#sa1414)) |
| SX1101 | Do not prefix local calls with 'this.' | off | yes | off / on / off | 6,123 / 0 / 6 | | SDK: IDE0003 (`dotnet_style_qualification_for_* = false`); off by default in StyleCop and not enforced by the preset; stylebro-migrate turns IDE0003 on when SX1101 is on |
### Spacing
| Rule | Title | Default | StyleCop fix | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | SDK check | Proposal |
|---|---|---|---|---|---|---|---|
| SA1000 | Keywords should be spaced correctly | on | yes | on / off / off | 0 / 0 / 3,886 | fixed | SDK: IDE0055 formatting |
| SA1001 | Commas should be spaced correctly | on | yes | on / off / on | 0 / 4 / 0 | fixed | SDK: IDE0055 formatting |
| SA1002 | Semicolons should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1003 | Symbols should be spaced correctly | on | yes | on / off / off | 0 / 1 / 16 | fixed | SDK: IDE0055 formatting |
| SA1004 | Documentation lines should begin with single space | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1005** (done), same positions and output as StyleCop |
| SA1005 | Single line comments should begin with single space | on | yes | on / on / off | 0 / 0 / 78 | not fixed | StyleBro **BRO1002** (done), same results as StyleCop 1.2 (1.1.118 also reported `// two spaces`) |
| SA1006 | Preprocessor keywords should not be preceded by space | on | yes | on / on / on | 0 / 0 / 0 | not fixed | StyleBro **BRO1006** (done), same positions and output as StyleCop |
| SA1007 | Operator keyword should be followed by space | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1008 | Opening parenthesis should be spaced correctly | on | yes | on / off / off | 0 / 1 / 17 | fixed | SDK: IDE0055 formatting |
| SA1009 | Closing parenthesis should be spaced correctly | on | yes | on / off / off | 0 / 8 / 1,108 | fixed | SDK: IDE0055 formatting |
| SA1010 | Opening square brackets should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1011 | Closing square brackets should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1012 | Opening braces should be spaced correctly | on | yes | on / off / off | 0 / 0 / 120 | fixed | SDK: IDE0055 formatting |
| SA1013 | Closing braces should be spaced correctly | on | yes | on / off / off | 0 / 0 / 58 | fixed | SDK: IDE0055 formatting |
| SA1014 | Opening generic brackets should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1015 | Closing generic brackets should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1016 | Opening attribute brackets should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1017 | Closing attribute brackets should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1018 | Nullable type symbols should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1019 | Member access symbols should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1020 | Increment decrement symbols should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1021 | Negative signs should be spaced correctly | on | yes | on / off / off | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1022 | Positive signs should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1023 | Dereference and access of symbols should be spaced correctly | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1024 | Colons Should Be Spaced Correctly | on | yes | on / off / off | 0 / 0 / 21 | fixed | SDK: IDE0055 formatting |
| SA1025 | Code should not contain multiple whitespace in a row | on | yes | on / off / off | 0 / 4 / 91 | fixed | SDK: IDE0055 formatting |
| SA1026 | Code should not contain space after new or stackalloc keyword in implicitly typed array allocation | on | yes | on / off / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting |
| SA1027 | Use tabs correctly | on | yes | on / on / on | 0 / 0 / 0 | fixed | SDK: IDE0055 formatting (`indent_style = space`) |
| SA1028 | Code should not contain trailing whitespace | on | yes | on / on / off | 0 / 0 / 11 | fixed | SDK: IDE0055 formatting |
### Special
| Rule | Title | Default | StyleCop fix | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) | SDK check | Proposal |
|---|---|---|---|---|---|---|---|
| SA0001 | XML comment analysis disabled | on | no | on / off / off | 0 / 0 / 0 | | Not applicable: StyleCop's own setup diagnostic: XML comment analysis is disabled (no GenerateDocumentationFile). ([details](skipped-rules.md#sa0001)) |
| SA0002 | Invalid settings file | on | no | on / on / on | 0 / 0 / 0 | | Not applicable: StyleCop's own diagnostic for an invalid stylecop.json. ([details](skipped-rules.md#sa0002)) |
---
Source: https://bisforboman.github.io/stylebro/skipped-rules/
# Skipped StyleCop rules
Every StyleCop.Analyzers rule StyleBro doesn't cover, with why and what it would take to revisit it, so the
decisions can be picked up again later. Rules that are covered but behave differently are at the end. The full
picture, including covered rules, is in [stylecop-mapping.md](stylecop-mapping.md).
## How to revisit a rule
1. Check the demand: how many surveyed teams keep the rule on, and how often it fires (the numbers in each entry;
the repos are listed in [stylecop-mapping.md](stylecop-mapping.md#how-this-was-measured)).
2. If the SDK might cover it, add a case to `scripts/stylecop-survey/sdk-check-cases.ps1` and run
`Test-SdkCoverage.ps1`. Then check it doesn't change code StyleCop accepts: `Measure-MigrationDelta.ps1` on a
StyleCop-clean repo (that's how the preset's SA1501/SA1502 and IDE0040 settings were caught).
3. For a StyleBro rule, follow the design rules in CLAUDE.md (fix + Fix All, idempotent, skip unsafe cases), add a
parity set to `parity/parity.psd1` and run `Compare-WithStyleCop.ps1` against StyleCop 1.2.
4. Move the entry out of `skipped.psd1` (into `decisions.psd1`) and regenerate.
## Summary
44 rules: 0 candidate, 0 SDK not on yet, 32 drop, 10 not in StyleCop, 0 variant, 2 n/a.
| Rule | Title | Status | Teams keeping it on (OTel / Polly / App) | Diagnostics (OTel / Polly / App) |
|---|---|---|---|---|
| [SA1118](#sa1118) | Parameter should not span multiple lines | drop | on / on / off | 0 / 0 / 126 |
| [SA1401](#sa1401) | Fields should be private | drop | off / off / off | 141 / 48 / 39 |
| [SA1402](#sa1402) | File may only contain a single type | drop | on / off / off | 0 / 34 / 1 |
| [SA1403](#sa1403) | File may only contain a single namespace | drop | on / on / on | 0 / 0 / 0 |
| [SA1404](#sa1404) | Code analysis suppression should have justification | drop | on / on / on | 0 / 0 / 0 |
| [SA1405](#sa1405) | Debug.Assert should provide message text | drop | on / on / on | 0 / 0 / 0 |
| [SA1406](#sa1406) | Debug.Fail should provide message text | drop | on / on / on | 0 / 0 / 0 |
| [SA1414](#sa1414) | Tuple types in signatures should have element names | drop | on / on / on | 0 / 0 / 0 |
| [SA1601](#sa1601) | Partial elements should be documented | drop | on / off / on | 16 / 78 / 0 |
| [SA1602](#sa1602) | Enumeration items should be documented | drop | on / on / on | 0 / 10 / 530 |
| [SA1603](#sa1603) | Documentation should contain valid XML | drop | off / on / off | 0 / 0 / 0 |
| [SA1604](#sa1604) | Element documentation should have summary | drop | on / on / on | 0 / 0 / 1 |
| [SA1605](#sa1605) | Partial element documentation should have summary | drop | on / on / on | 0 / 0 / 0 |
| [SA1606](#sa1606) | Element documentation should have summary text | drop | on / on / on | 0 / 0 / 0 |
| [SA1607](#sa1607) | Partial element documentation should have summary text | drop | on / on / on | 0 / 0 / 0 |
| [SA1608](#sa1608) | Element documentation should not have default summary | drop | on / on / on | 0 / 0 / 0 |
| [SA1609](#sa1609) | Property documentation should have value | drop | off / off / off | 292 / 171 / 8 |
| [SA1610](#sa1610) | Property documentation should have value text | drop | on / off / on | 0 / 0 / 0 |
| [SA1611](#sa1611) | Element parameters should be documented | drop | on / on / on | 8 / 1 / 11 |
| [SA1614](#sa1614) | Element parameter documentation should have text | drop | on / on / off | 0 / 0 / 4 |
| [SA1615](#sa1615) | Element return value should be documented | drop | on / on / on | 3 / 1 / 6 |
| [SA1616](#sa1616) | Element return value documentation should have text | drop | on / on / on | 0 / 0 / 0 |
| [SA1618](#sa1618) | Generic type parameters should be documented | drop | on / on / on | 0 / 0 / 1 |
| [SA1619](#sa1619) | Generic type parameters should be documented partial class | drop | on / on / on | 0 / 0 / 0 |
| [SA1622](#sa1622) | Generic type parameter documentation should have text | drop | on / on / on | 0 / 0 / 0 |
| [SA1625](#sa1625) | Element documentation should not be copied and pasted | drop | on / on / on | 0 / 0 / 0 |
| [SA1639](#sa1639) | File header should have summary | drop | off / off / off | 0 / 0 / 0 |
| [SA1645](#sa1645) | Included documentation file does not exist | drop | off / on / off | 0 / 0 / 0 |
| [SA1646](#sa1646) | Included documentation XPath does not exist | drop | off / on / off | 0 / 0 / 0 |
| [SA1647](#sa1647) | Include node does not contain valid file and path | drop | off / on / off | 0 / 0 / 0 |
| [SA1648](#sa1648) | inheritdoc should be used with inheriting class | drop | on / on / on | 0 / 0 / 0 |
| [SA1649](#sa1649) | File name should match first type name | drop | off / on / off | 0 / 0 / 1 |
| [SA1109](#sa1109) | Block statements should not contain embedded regions | not in StyleCop | off / on / off | 0 / 0 / 0 |
| [SA1126](#sa1126) | Prefix calls correctly | not in StyleCop | off / off / off | 0 / 0 / 0 |
| [SA1301](#sa1301) | Element should begin with lower-case letter | not in StyleCop | off / off / off | 0 / 0 / 0 |
| [SA1409](#sa1409) | Remove unnecessary code | not in StyleCop | off / on / off | 0 / 0 / 0 |
| [SA1628](#sa1628) | Documentation text should begin with a capital letter | not in StyleCop | off / on / off | 0 / 0 / 0 |
| [SA1630](#sa1630) | Documentation text should contain whitespace | not in StyleCop | off / on / off | 0 / 0 / 0 |
| [SA1631](#sa1631) | Documentation should meet character percentage | not in StyleCop | off / on / off | 0 / 0 / 0 |
| [SA1632](#sa1632) | Documentation text should meet minimum character length | not in StyleCop | off / on / off | 0 / 0 / 0 |
| [SA1644](#sa1644) | Documentation headers should not contain blank lines | not in StyleCop | off / on / off | 0 / 0 / 0 |
| [SA1650](#sa1650) | Element documentation should be spelled correctly | not in StyleCop | off / on / off | 0 / 0 / 0 |
| [SA0001](#sa0001) | XML comment analysis disabled | n/a | on / off / off | 0 / 0 / 0 |
| [SA0002](#sa0002) | Invalid settings file | n/a | on / on / on | 0 / 0 / 0 |
## Dropped: no safe automatic fix
### SA1118: Parameter should not span multiple lines
Readability rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 126.
**Why:** StyleCop has no fix. A multi-line argument (other than the first, or a lambda/anonymous object) needs extracting into a variable: a refactoring that names things. 126 findings in the private app.
**To revisit:** Only if a deterministic extraction is acceptable (variable name from the parameter name). Probably stays a human job.
### SA1401: Fields should be private
Maintainability rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): off / off / off. Diagnostics with every rule on (OTel / Polly / App): 141 / 48 / 39.
**Why:** Making a public or protected field private changes the public API and breaks callers. 141 / 48 / 39 findings with the rule on.
**To revisit:** Only for private nested types or `internal` fields without InternalsVisibleTo, converting to a property. Probably not worth it.
### SA1402: File may only contain a single type
Maintainability rule. StyleCop: on by default, has a code fix. Teams keeping it on (OTel / Polly / App): on / off / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 34 / 1.
**Why:** Moving a type to its own file isn't something `dotnet format` can do (code fixes can't add documents through it).
**To revisit:** Would need a separate tool (stylebro-migrate could do it as a one-off).
### SA1403: File may only contain a single namespace
Maintainability rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** StyleCop has no fix; splitting namespaces into files has the same problem as SA1402.
**To revisit:** Same as SA1402.
### SA1404: Code analysis suppression should have justification
Maintainability rule. StyleCop: on by default, has a code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** The justification of a SuppressMessage has to be written by a person.
**To revisit:** Nothing automatic.
### SA1405: Debug.Assert should provide message text
Maintainability rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** StyleCop has no fix; the message for Debug.Assert has to be written by a person.
**To revisit:** A generated message (the condition's text, like CallerArgumentExpression) is possible but adds little.
### SA1406: Debug.Fail should provide message text
Maintainability rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** StyleCop has no fix; the message for Debug.Fail has to be written by a person.
**To revisit:** Nothing automatic.
### SA1414: Tuple types in signatures should have element names
Readability rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** StyleCop has no fix (1.2 only); tuple element names in signatures have to be chosen by a person.
**To revisit:** Nothing automatic.
### SA1601: Partial elements should be documented
Documentation rule. StyleCop: on by default, has a code fix. Teams keeping it on (OTel / Polly / App): on / off / on. Diagnostics with every rule on (OTel / Polly / App): 16 / 78 / 0.
**Why:** Missing documentation on a partial element; the only fix is placeholder text (no stubs, user decision).
**To revisit:** Revisit with the stub decision.
### SA1602: Enumeration items should be documented
Documentation rule. StyleCop: on by default, has a code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 10 / 530.
**Why:** Missing documentation on enum members; the only fix is placeholder text (no stubs, user decision). 530 findings in the private app.
**To revisit:** Revisit with the stub decision.
### SA1603: Documentation should contain valid XML
Documentation rule. StyleCop: off by default, no code fix. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default, no StyleCop fix; invalid XML needs a person to say what was meant.
**To revisit:** A narrow fix is possible: escape a lone `&` or `<` in text. Probe how often that's the cause.
### SA1604: Element documentation should have summary
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 1.
**Why:** A doc comment without ``; the fix would be placeholder text.
**To revisit:** Revisit with the stub decision. If the comment has plain text outside any tag, wrapping it in `` is a real fix: worth a probe.
### SA1605: Partial element documentation should have summary
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Same as SA1604 for partial elements.
**To revisit:** With SA1604.
### SA1606: Element documentation should have summary text
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** An empty ``; the fix would be placeholder text (no stubs, user decision).
**To revisit:** Revisit with the stub decision.
### SA1607: Partial element documentation should have summary text
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Same as SA1606 for partial elements.
**To revisit:** With SA1606.
### SA1608: Element documentation should not have default summary
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** The default "Summary description for X" text has to be replaced by a person.
**To revisit:** Nothing automatic.
### SA1609: Property documentation should have value
Documentation rule. StyleCop: off by default, has a code fix. Teams keeping it on (OTel / Polly / App): off / off / off. Diagnostics with every rule on (OTel / Polly / App): 292 / 171 / 8.
**Why:** Off by default. Missing ``; the fix would be placeholder text (no stubs, user decision).
**To revisit:** Revisit with the stub decision. A `` derived from the summary ("Gets the name." -> "The name.") is conceivable.
### SA1610: Property documentation should have value text
Documentation rule. StyleCop: on by default, has a code fix. Teams keeping it on (OTel / Polly / App): on / off / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** An empty ``; StyleCop's fix writes placeholder text.
**To revisit:** Removing the empty tag would be a real fix, but trades one diagnostic for SA1609 (off by default). Decide with SA1609.
### SA1611: Element parameters should be documented
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 8 / 1 / 11.
**Why:** Missing ``; the fix would be placeholder text (no stubs, user decision).
**To revisit:** Revisit with the stub decision.
### SA1614: Element parameter documentation should have text
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 4.
**Why:** An empty ``; the fix would be placeholder text.
**To revisit:** Revisit with the stub decision.
### SA1615: Element return value should be documented
Documentation rule. StyleCop: on by default, has a code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 3 / 1 / 6.
**Why:** Missing ``; the fix would be placeholder text.
**To revisit:** Revisit with the stub decision.
### SA1616: Element return value documentation should have text
Documentation rule. StyleCop: on by default, has a code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** An empty ``; the fix would be placeholder text.
**To revisit:** Revisit with the stub decision.
### SA1618: Generic type parameters should be documented
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 1.
**Why:** Missing ``; the fix would be placeholder text.
**To revisit:** Revisit with the stub decision.
### SA1619: Generic type parameters should be documented partial class
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Same as SA1618 for partial types.
**To revisit:** With SA1618.
### SA1622: Generic type parameter documentation should have text
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** An empty ``; the fix would be placeholder text.
**To revisit:** Revisit with the stub decision.
### SA1625: Element documentation should not be copied and pasted
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Identical text copied between tags has to be rewritten by a person.
**To revisit:** Nothing automatic.
### SA1639: File header should have summary
Documentation rule. StyleCop: off by default, has a code fix. Teams keeping it on (OTel / Polly / App): off / off / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default; the header's `` has to be written by a person.
**To revisit:** Nothing automatic.
### SA1645: Included documentation file does not exist
Documentation rule. StyleCop: off by default, no code fix. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default; a missing `` file can't be fixed automatically.
**To revisit:** Nothing automatic.
### SA1646: Included documentation XPath does not exist
Documentation rule. StyleCop: off by default, no code fix. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default; a wrong `` XPath can't be fixed automatically.
**To revisit:** Nothing automatic.
### SA1647: Include node does not contain valid file and path
Documentation rule. StyleCop: off by default, no code fix. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default; an `` without file/path can't be fixed automatically.
**To revisit:** Nothing automatic.
### SA1648: inheritdoc should be used with inheriting class
Documentation rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** `` where there is nothing to inherit: the fix is real documentation.
**To revisit:** Nothing automatic. (BRO1601 only adds `` where there is something to inherit.)
### SA1649: File name should match first type name
Documentation rule. StyleCop: on by default, has a code fix. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 1.
**Why:** Renaming files isn't something `dotnet format` can do.
**To revisit:** Would need a separate tool (stylebro-migrate could do it as a one-off, with `git mv`).
## Dropped: StyleCop never reports it
### SA1109: Block statements should not contain embedded regions
Readability rule. StyleCop: off by default, no code fix; SDK check: StyleCop reports nothing. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default and no diagnostic for a region between `if (b)` and its block (probed).
**To revisit:** Nothing to port. Regions are BRO1112/BRO1113.
### SA1126: Prefix calls correctly
Readability rule. StyleCop: off by default, no code fix; SDK check: StyleCop reports nothing. Teams keeping it on (OTel / Polly / App): off / off / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default and no diagnostic on an unprefixed member call (probed).
**To revisit:** Nothing to port.
### SA1301: Element should begin with lower-case letter
Naming rule. StyleCop: off by default, no code fix; SDK check: StyleCop reports nothing. Teams keeping it on (OTel / Polly / App): off / off / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default and no diagnostic (probed). StyleCop keeps the id for compatibility.
**To revisit:** Nothing to port.
### SA1409: Remove unnecessary code
Maintainability rule. StyleCop: off by default, no code fix; SDK check: StyleCop reports nothing. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default and no diagnostic for an empty `try { } finally { }` (probed).
**To revisit:** Nothing to port.
### SA1628: Documentation text should begin with a capital letter
Documentation rule. StyleCop: off by default, no code fix. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** StyleCop 1.2 never reports it (not implemented, probed while building BRO16xx).
**To revisit:** Nothing to port. A capitalization fix would be easy if a team wants it.
### SA1630: Documentation text should contain whitespace
Documentation rule. StyleCop: off by default, no code fix; SDK check: StyleCop reports nothing. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default and no diagnostic on a one-word summary (probed).
**To revisit:** Nothing to port.
### SA1631: Documentation should meet character percentage
Documentation rule. StyleCop: off by default, no code fix; SDK check: StyleCop reports nothing. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default and no diagnostic on a summary of symbols (probed).
**To revisit:** Nothing to port.
### SA1632: Documentation text should meet minimum character length
Documentation rule. StyleCop: off by default, no code fix; SDK check: StyleCop reports nothing. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default and no diagnostic on a one-letter summary (probed).
**To revisit:** Nothing to port.
### SA1644: Documentation headers should not contain blank lines
Documentation rule. StyleCop: off by default, no code fix. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** StyleCop 1.2 never reports it (not implemented, probed while building BRO16xx).
**To revisit:** Nothing to port.
### SA1650: Element documentation should be spelled correctly
Documentation rule. StyleCop: off by default, no code fix; SDK check: StyleCop reports nothing. Teams keeping it on (OTel / Polly / App): off / on / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** Off by default and no diagnostic on misspelled words (probed).
**To revisit:** Nothing to port.
## Not applicable
### SA0001: XML comment analysis disabled
Special rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / off / off. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** StyleCop's own setup diagnostic: XML comment analysis is disabled (no GenerateDocumentationFile).
**To revisit:** Nothing to port. stylebro-migrate already detects the same condition and keeps the XML documentation rules (BRO1603-BRO1611) off.
### SA0002: Invalid settings file
Special rule. StyleCop: on by default, no code fix. Teams keeping it on (OTel / Polly / App): on / on / on. Diagnostics with every rule on (OTel / Polly / App): 0 / 0 / 0.
**Why:** StyleCop's own diagnostic for an invalid stylecop.json.
**To revisit:** Nothing to port. stylebro-migrate parses stylecop.json leniently (comments, trailing commas).
## Covered, with known differences
### SA1206: Declaration keywords should follow order
**Covered by:** SDK: IDE0036 (`csharp_preferred_modifier_order`, the SDK default order)
**Difference:** IDE0036 (`csharp_preferred_modifier_order`) enforces the whole modifier order, while StyleCop only wants the access modifier first and `static` next. Measured on six repos (2026-10-02): IDE0036 reordered nothing that StyleCop accepts (Polly, OpenTelemetry, the private app, FFMpegCore, Serilog 0; Newtonsoft.Json 27 lines, all SA1206 violations such as `public new static`).
**To revisit:** Only if a team reports reorders StyleCop accepted (`async override` vs `override async`); then a BRO rule that only moves access modifiers and `static`.
### SA1600: Elements should be documented
**Covered by:** StyleBro **BRO1601** (done) for overrides and implementations only (``); other missing documentation is not reported (no placeholder stubs, user decision)
**Difference:** BRO1601 adds `` to overrides and implementations; other missing documentation isn't reported (no stubs, user decision).
**To revisit:** Revisit with the stub decision (also SA1601, SA1602, SA1604-SA1607, SA1609-SA1611, SA1614-SA1616, SA1618, SA1619, SA1622).