Sharing coding style and Roslyn analyzers across projects
This post is part of the series 'Coding style'. Be sure to check out the rest of the blog posts of the series!
- How to enforce a consistent coding style in your projects
- Enforce .NET code style in CI with dotnet format
- Running GitHub Super-Linter in Azure Pipelines
- Sharing coding style and Roslyn analyzers across projects (this post)
I've written many times about enforcing a coding style and enabling static analysis in a project. Feel free to check the post of this series for more details.
But, what if you now want to share a coding style and Roslyn analyzers across multiple projects in a company?
Recent versions of .NET and Roslyn have made it possible to share a coding style using .NET packages. Let's see how it works.
#NuGet packages and MSBuild imports
The .NET SDK imports .props
and .targets
files from NuGet packages. For instance, if the package is named MyPackage
, the SDK imports build/MyPackage.props
and build/MyPackage.targets
. So, you can add any MSBuild properties and items to the project that references the package.
For instance, you can include common project configuration properties in the package.
<Project>
<PropertyGroup>
<ReportAnalyzer>true</ReportAnalyzer>
<Features>strict</Features>
<Deterministic>true</Deterministic>
<NoWarn>$(NoWarn);CA1014</NoWarn>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>All</AnalysisMode>
<AnalysisLevel>preview</AnalysisLevel>
<TreatWarningsAsErrors Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>
You can also reference a global editorconfig file using the EditorConfigFiles
item.
<ItemGroup>
<EditorConfigFiles Include="global.editorconfig" />
</ItemGroup>
Finally, you can reference Roslyn analyzers by using package dependencies in the NuSpec files.
<dependencies>
<dependency id="Meziantou.Analyzer" version="1.0.700" />
<dependency id="Microsoft.VisualStudio.Threading.Analyzers" version="17.1.46" />
<dependency id="Microsoft.CodeAnalysis.BannedApiAnalyzers" version="3.3.3" />
</dependencies>
#Building the package
Folder structure:
/MyPackage.nuspec
/src/build/MyPackage.props
/src/build/MyPackage.targets
/src/files/NamingConvention.editorconfig
/src/files/Analyzers.editorconfig
/src/files/BannedSymbols.txt
Let's start with the nuspec file:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<!-- Package metadata -->
<id>MyPackage</id>
<developmentDependency>true</developmentDependency>
<description>A package to configure .NET coding style and static analysis</description>
<!-- The version is set when building the package -->
<version>$version$</version>
<!-- Add analyzers -->
<dependencies>
<dependency id="Meziantou.Analyzer" version="1.0.700" />
<dependency id="Microsoft.VisualStudio.Threading.Analyzers" version="17.1.46" />
<dependency id="Microsoft.CodeAnalysis.BannedApiAnalyzers" version="3.3.3" />
</dependencies>
</metadata>
<!-- Add props / targets / editorconfig files to the package -->
<files>
<file src="src\**" target="" />
</files>
</package>
You can configure the project using the props / target files. The following files are examples, you can use them as a starting point.
<Project>
<PropertyGroup>
<ReportAnalyzer>true</ReportAnalyzer>
<Features>strict</Features>
<Deterministic>true</Deterministic>
<NoWarn>$(NoWarn);CA1014</NoWarn>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>All</AnalysisMode>
<AnalysisLevel>preview</AnalysisLevel>
<TreatWarningsAsErrors Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>
<Project>
<!-- Register the editorconfig files to the project -->
<ItemGroup>
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)\..\files\*.editorconfig" />
</ItemGroup>
<!-- Banned Symbols -->
<PropertyGroup>
<IncludeDefaultBannedSymbols Condition="$(IncludeDefaultBannedSymbols) == ''">true</IncludeDefaultBannedSymbols>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\..\files\BannedSymbols.txt"
Condition="$(IncludeDefaultBannedSymbols) == 'true'"
Visible="false" />
</ItemGroup>
</Project>
You can configure the project using global editorconfig file. You can create as many editorconfig files as you want. For instance, you can create a file for the naming convention, and another file to configure the Roslyn analyzers.
is_global = true
global_level = -1
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# ... other naming rules
Configure the Roslyn analyzers using a global editorconfig file:
is_global = true
global_level = 0
dotnet_code_quality_unused_parameters=all:warning
dotnet_diagnostic.IDE0059.severity = warning
csharp_style_unused_value_assignment_preference = discard_variable
dotnet_diagnostic.CA1063.severity = none
dotnet_diagnostic.CA1303.severity = none
dotnet_diagnostic.CA1305.severity = none
dotnet_diagnostic.CA1816.severity = none
dotnet_diagnostic.MA0004.severity = warning
# ... other rules
As the project use Microsoft.CodeAnalysis.BannedApiAnalyzers
, let's create a default banned symbols file:
P:System.DateTime.Now;Use System.DateTime.UtcNow instead
P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead
M:System.IO.File.GetCreationTime(System.String);Use GetCreationTimeUtc instead
Finally, you can build the package using the nuget pack
command. You can download nuget.exe
on nuget.org.
nuget pack MyPackage.nuspec -ForceEnglishOutput -Version 1.0.0
#Additional resources
- Configuration files for code analysis rules
- Editorconfig vs Global analyzerconfig
- Meziantou.DotNet.CodingStandard
- Allow package authors to define build assets transitive behavior
Do you have a question or a suggestion about this post? Contact me!