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.
- 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. stylebro-migrate format --verify-no-changesfails (exit code 2) when formatting would change any file. It also covers whitespace formatting, which isn't a build warning. Unlike plaindotnet format --verify-no-changes, it doesn't fail on 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 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.
Fixing a failure is the same everywhere: run stylebro-migrate format (or dotnet format, see
the notes on other analyzers) locally and commit the result.
Setup¶
<!-- Directory.Build.props -->
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- Report the built-in .NET rules (IDE0055, IDE0036, ...) on build too. -->
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StyleBro.Analyzers" Version="0.7.0-alpha.1" PrivateAssets="all" />
</ItemGroup>
</Project>
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 <WarningsNotAsErrors>: dotnet format then skips
those rules entirely (see BRO1001).
GitHub Actions¶
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¶
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 warnmakesdotnet formatapply and check everything at warning level, StyleBro's default.--no-restoreafter a build saves a second restore;dotnet formatneeds a restored solution either way.--reportwritesformat-report.json: every file and linedotnet formatwould change and why (rule id orWHITESPACE), kept findings included. Uploading it on failure shows what to fix without rerunning anything.- How
--verify-no-changestells kept findings apart: whendotnet format --verify-no-changesreports findings,stylebro-migrate formatformats 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_linein.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 formatkeeps each file's own ending when the file used one throughout, and--verify-no-changesignores findings that only swap it. Settingend_of_line(stylebro-migrate initdoes when your files agree) picks one ending for every file instead. - Without the tool: plain
dotnet format --verify-no-changes --severity warnworks too, but it fails on kept findings. Suppress or baseline them first. - Other analyzers and compiler fixes. Plain
dotnet format --verify-no-changesalso fails on every other analyzer package's fixable warnings and on compiler fixes (CS8618'srequired), and locally plaindotnet formatapplies them, which can change behavior or break the build. To check and fix only StyleBro's rules and the built-in rulesstylebro-migrate initturns 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) anddotnet 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-changesand 57 minutes (about 9 GB of memory) for its first fullformat. 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 failstylebro-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). - 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. - A large existing codebase: commit a baseline so CI fails only on new violations. A baseline can't
hide whitespace formatting; either run
dotnet format whitespaceonce, or check onlydotnet format styleanddotnet format analyzersin CI until you do. - Several target frameworks: use
stylebro-migrate formatinstead ofdotnet 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 and dotnet/roslyn#86001). Its whitespace pass only formats the first framework's code (dotnet/sdk#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) 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.