StyleCop with team-shared rules and auto-update
The post covers how to implement StyleCop analyzers with automatically updating team-shared rules and settings. The described approach works both in Visual Studio and Rider and on any CI.
The main idea is to have code analyzers and their settings in every project. They should be added automatically whenever a new project is created. When a version of analyzers is updated or settings are changed, it should be also reflected in every project automatically.
The ideal solution to fulfill all requirements is to create a NuGet package that installs StyleCop analyzers into projects and puts their settings into the solution directory. This way code analysis happens during the development and on CI during the build.
The next step would be to solve how to distribute the package and have it in every solution.
And the last, make sure that the result of code analysis is shown as errors on CI, and as warnings - on developers' machines. So we don't slow down the development process, but still, guarantee a clean code in repositories.
1. Creating NuGet package with StyleCop analyzers
Let's create a usual .nuspec file that describes what analyzer packages we'd like to install into the target project and what settings to copy when our NuGet package is installed.
<?xml version="1.0"?>
<package>
<metadata>
<id>SharedCodeAnalyzers</id>
<version>1.0.0</version>
<description>Team-shared code analyzers and settings.</description>
<authors>https://kmyr.pro</authors>
<dependencies>
<group targetFramework="netcoreapp3.1">
<dependency id="StyleCop.Analyzers" version="1.1.118" />
</group>
</dependencies>
</metadata>
</package>
The project contains the only dependency on the StyleCop.Analyzers
package. These analyzers will appear in the target project where our NuGet package is installed.
The first thing is done. Now we need to provide a team-shared configuration of the analysis. It can be done in two places: .ruleset file that specifies what rules are enabled, and the stylecop.json that configures exactly how StyleCop rules behave.
The first one must be added to each project using the instruction:
<PropertyGroup>
<CodeAnalysisRuleSet>SharedCodeAnalyzers.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
The second one we need just to include as an additional project file:
<ItemGroup>
<AdditionalFiles Include="stylecop.json" Link="stylecop.json" />
</ItemGroup>
Of course, we will not do it manually. For this task, we can utilize .props files supported by MSBuild. In the SharedCodeAnalyzers.props
we put the instructions described above. And then we just need to add the file to the project.
Let's add the additional content to the SharedCodeAnalyzers.nuspec
:
<?xml version="1.0"?>
<package>
<metadata>
<id>SharedCodeAnalyzers</id>
<version>1.0.0</version>
<description>Team-shared code analyzers and settings.</description>
<authors>https://kmyr.pro</authors>
<dependencies>
<group targetFramework="netcoreapp3.1">
<dependency id="StyleCop.Analyzers" version="1.1.118" />
</group>
</dependencies>
</metadata>
<files>
<file src="stylecop.json" />
<file src="SharedCodeAnalyzers.ruleset" />
<file src="SharedCodeAnalyzers.props" target="build" />
</files>
</package>
After installing the NuGet package stylecop.json
and SharedCodeAnalyzers.ruleset
appear in the cache folder (C:\Users\<user>\.nuget\packages
by default), and SharedCodeAnalyzers.props
is added automatically to the project because we specified target="build".
Now, let's describe what MSBuild should do with the configuration files:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CodeAnalysisRuleSet>
$(MSBuildThisFileDirectory)..\SharedCodeAnalyzers.ruleset
</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)..\stylecop.json"
Link="stylecop.json" />
</ItemGroup>
</Project>
That's it. To summarize, we described a NuGet package SharedCodeAnalyzers
using SharedCodeAnalyzers.nuspec
file. It includes a dependency on StyleCop.Analyzers
which will be added to the project when the package is installed.
We provided two configuration files: SharedCodeAnalyzers.ruleset
that configures code analyzers and stylecop.json
that configures StyleCop behavior. They do not appear in the project directory. Instead, they are placed in the package cache folder.
Additionally, we included SharedCodeAnalyzers.props
file that is used automatically and contains instructions for adding configuration files to the project.
2. Installing the package with analyzers into every project
The easiest way to have our NuGet package in every project automatically is to use .props
file again:
<?xml version="1.0" encoding="utf-8"?>
<Project>
<ItemGroup>
<PackageReference Include="SharedCodeAnalyzers" Version="*" />
</ItemGroup>
</Project>
It should be placed in every repository near the .sln
file. The effect will be that SharedCodeAnalyzers
is added to every solution project automatically.
Obviously, we'd like to see always the latest version added to the project. That's why we specify Version="*"
in the .props
file.
However, there are two difficulties. The first one, we need to tweak the nuget
to always take the latest version when *
is used. The solution is to place a nuget.config
near the .sln
as well with the following content:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="dependencyVersion" value="Highest" />
</config>
</configuration>
The second difficulty is that nuget restore
takes only the latest locally cached version instead of checking the server. That's not what we want.
I don't have a solution for development machines rather than pressing the Update button in the IDE. In fact, it will just cache and use the new version without modifications in the .csproj
.
As for CI builds, the trick is to add the --force
flag:
dotnet build --force Solution.sln
or do a full cleanup before build.
3. Show code analysis warnings as errors on CI
During the development all code analysis results should be shown just as warnings. This is a default behavior expected from the configured .ruleset
.
However, on CI the same warnings should be treated as errors.
Such behavior can be configured in the .csproj
using switch TreatWarningsAsErrors:
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
All we need is just to add the condition to the SharedCodeAnalyzers.props
to understand do we build locally or on CI:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
<PropertyGroup Condition="'$(CI_BUILD)' == 'true'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
When environment variable CI_BUILD
is set, the TreatWarningsAsErrors
switch is enabled.
The build on CI then looks like:
CI_BUILD=true
dotnet build --force Solution.sln
Summary
The described approach can be used to distribute any Rolsyn-based analyzers (not only StyleCop) and their settings across the team.
The required references to analyzers' packages and the settings files are conveniently packed into a single NuGet package.
If a new version of analyzers is released, or team wants to modify some rules, just update the NuGet package.
Having a .props
file in the solution root makes sure that analyzers are installed and up to date.
As an additional idea for consideration: add a git pre-push hook to check that .props
file is in place, and do a local build to avoid errors on CI.