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),
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);
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.
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 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 <ids> 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:
<!-- Directory.Packages.props, before -->
<GlobalPackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.556" />
<!-- after -->
<GlobalPackageReference Include="StyleBro.Analyzers" Version="0.7.0-alpha.1" />
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 (<StyleBroPreset>none</StyleBroPreset>) 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 (<PackageReference Remove="StyleCop.Analyzers" />, 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):
<PackageReference Remove="StyleCop.Analyzers" />
<PackageReference Remove="StyleBro.Analyzers" />
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 shows the pair);
--diff writes that pair too.
Old-style projects (packages.config, StyleCop as <Analyzer Include="..\packages\StyleCop.Analyzers.1.0.2\...">):
removing StyleCop means removing its packages.config entry and the <Analyzer> items. StyleBro goes in the same way:
a packages.config entry, its <Analyzer> 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.
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.
<Import Project="$(MSBuildThisFileDirectory)shared-infrastructure\msbuild\props\SixLabors.Global.props" />
<!-- StyleBro replaces StyleCop (referenced in the shared infrastructure). -->
<ItemGroup>
<PackageReference Remove="StyleCop.Analyzers" />
<PackageReference Include="StyleBro.Analyzers" Version="0.7.0-alpha.1" PrivateAssets="all" />
</ItemGroup>
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. A small StyleCop project migrated step by step, with
real output: samples/StyleCopMigration.
What it reads¶
- Only where StyleCop runs. When some projects reference StyleCop.Analyzers (in the project, the
Directory.Build.props/.targetsorDirectory.Packages.propsit gets, or what those import) and others don't (tests, samples, benchmarks, a project with aRemoveitem), 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.editorconfigthat covers all of them counts as the base (asrc/.editorconfigthat 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 tellsstylebro-migrate formatto 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).
*.rulesetfiles, then*.globalconfigfiles, then the root.editorconfig's sections for all C# files. Later sources win, like in the compiler:dotnet_diagnostic.SAxxxx.severityanddotnet_analyzer_diagnostic.category-StyleCop.CSharp.*.severitykeys, and ruleset<Rule Id="SAxxxx">actions. Also like in the compiler, a bulk setting (a category, ordotnet_analyzer_diagnostic.severityfor every rule) doesn't override a rule's own entry in a ruleset or another config. A ruleset's<Include>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<CodeAnalysisRuleSet>its project file, itsDirectory.Build.props/.targetsor what they import select (the last one counts), itsstylecop.jsonthe one it adds as<AdditionalFiles>. 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 thestylecop.jsonmost projects use; projects whose ruleset orstylecop.jsongives 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(forGenerateDocumentationFileand the StyleCop version), the rulesets they select, and astylecop.jsonadded as<AdditionalFiles>. They're only read. - Sub-directory
.editorconfigfiles 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 asnone: a rule the root turns on by its id would win over the folder's bulk setting. A folder whose.editorconfigsaysroot = truegets 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
.gitfile or folder) are otherwise skipped: their settings, code and project files belong to another repository, and nothing in them is changed (stylebro-migrate formatexcludes them too). Files a project links from a submodule are another matter: see excluding vendored code. - The StyleCop.Analyzers version: from a
PackageReferenceorPackageVersion, apackages.configentry, or an<Analyzer>hint path into apackagesfolder (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 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 = falseforif(x),csharp_space_around_binary_operatorsnone, orignorewhen the operators are mixed,csharp_space_after_cast, the parentheses options); where no option expresses the code (new (), mixedif(x)/if (x), a few outliers), IDE0055 stays on and the report names whatformatchanges. - 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), butdotnet formatdoes 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 namedcount, 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) writescharset = utf-8-bom. stylecop.json:elementOrder,usingDirectivesPlacement,systemUsingDirectivesFirst,blankLinesBetweenUsingGroups,allowBuiltInTypeAliases, indentation, the file header settings,documentationCultureandexcludeFromPunctuationCheck.
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 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.jsonmakes moot don't count: SA1203 whenelementOrderleaves outconstant, and so on. WhenelementOrderleaves outkindoraccessibility, BRO1001 stays off: it always sorts by both. - Private field naming. BRO1303 uses
_camelCasewhen SA1309 (no leading underscore) is off and most of the repository's private fields start with_, andcamelCaseotherwise. Private constants andstatic readonlyfields are pinned to StyleCop's PascalCase (stylebro_private_static_field_naming = PascalCase, BRO1306), 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-casedotnet_naming_rulefor 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 = truekeeps constants,static readonlyand 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.severityline,noneincluded (also the rules beyond StyleCop and those off by default): a bulkdotnet_analyzer_diagnostic.severity = warningin your.editorconfigwould otherwise turn them on. - File header. With StyleCop's XML header and a
companyName, BRO1615 is on and IDE0073 off; withxmlHeader: false, IDE0073 (withfile_header_template) and BRO1615 off. Never both: each would add its header above the other's on every run. Without acompanyNameBRO1615 stays off (StyleCop's default company,PlaceholderCompany, is nobody's real header). An.editorconfigvalue ends at a;or#and loses its leading spaces, so the header settings are written with escapes BRO1615 reads back:\nfor a line break,\u003Bfor;,\u0023for#,\u005Cfor a backslash,\u0020for a space at either end (a GPL text'sWARRANTY;used to cut the rest of the license off). IDE0073'sfile_header_templatehas no escapes: a plain header with a;or#leaves IDE0073 off, and the report says why. - Line endings.
end_of_linewhen your C# files clearly use one ending (judged likeinitdoes; files git converts on checkout don't count, and when they're the majority nothing is written) and your.editorconfigdoesn't set it: without itformatwrites the OS's ending into the lines it changes. When your.editorconfigsetsend_of_linebut git converts the files on checkout (a.gitattributestextattribute withouteol, orcore.autocrlf), the endings are kept out of it:end_of_line = unsetfor 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 (detected like
initdoes, see getting started), IDE0055 and the same layout rulesinitturns 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--writeagain 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.editorconfigover 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,documentInternalElementsanddocumentPrivateElementsbecome BRO1601'sstylebro_document_*settings. - Documentation language and punctuation. A
documentationCultureother than English (de-DE,fr-FR; anyen-*counts as English) turns off BRO1604-BRO1607, which write English summary sentences (StyleCop checks translated ones); the report says so.excludeFromPunctuationCheck, when it differs from StyleCop's default (["seealso"]), becomes BRO1603'sstylebro_exclude_from_punctuation_check.
Every key the preset sets is written, and --write turns the preset off (<StyleBroPreset>none</StyleBroPreset> 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 (<NoWarn> in .csproj, .props,
.targets) get the replacing rules too, so code your team deliberately exempted
stays exempt:
#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
<NoWarn>$(NoWarn);SA1123;SA1600;BRO1113;BRO1601</NoWarn>
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).
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.
Coming from StyleSharp¶
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 <Analyzer> 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], <NoWarn>) 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'sapi/qualityprofiles/backup?language=cs&qualityProfile=<name>, or Quality Profiles > Back up in the UI; itscsharpsquidrules are on). Without it, when a project referencesSonarAnalyzer.CSharp(PackageReference,GlobalPackageReference, central package versions orpackages.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 (
<Rules AnalyzerId="SonarAnalyzer.CSharp">): your own, SonarLint's.sonarlint/*csharp.ruleset, and the scanner's.sonarqube/conf/Sonar-cs.rulesetwhen 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
.editorconfigfiles 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.initwrites 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 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 with stylebro_upper_case_literal_suffixes = lone_l (only a suffix that is just l, like S818) |
| S927 (parameter names match the base) | BRO1313 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 ifs) |
BRO1149 with stylebro_allow_nested_dynamic_ifs = true (conditions with a dynamic part aren't merged, like S1066) |
| S1116 (empty statements) | BRO1101 with stylebro_allow_semicolon_after_closing_brace = true (the ; after a type's } stays, like S1116) |
| S1659 (one variable per declaration) | BRO1142 |
| S1939 (redundant base types) | BRO1408 |
S2971 (LINQ predicate into Count/Any/...) |
BRO1150 |
S3878 (arrays for params) |
BRO1151 with stylebro_params_arrays = except_object_and_jagged (not for params object[], params Array[] or jagged arrays, like S3878) |
| S4136 (overloads together) | BRO1001's stylebro_keep_overloads_together = same_access (overloads with the same access, like S4136), only when BRO1001 is on |
S8969 (redundant !) |
BRO1147 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): #pragma warning disable
S1659 gets BRO1142, [SuppressMessage("...", "S927:...")] a sibling for BRO1313, <NoWarn> 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 <NoWarn> 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.
Limits¶
- Global configs (
.globalconfig) apply to every project here; with several, the strictest wins. Per-project rulesets andstylecop.jsonfiles are mapped (above), except for projects that share a folder. - StyleCop's XML file header (
<copyright file=...>, the default for SA1633) becomes BRO1615 with stylecop.json'scompanyName,copyrightText(customvariablesfilled in) andheaderDecoration; a plain header ("xmlHeader": false) becomes IDE0073'sfile_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.