Sharing coding style and Roslyn analyzers across projects
I've written many times about enforcing a coding style and enabling static analysis in a project:
- How to enforce a consistent coding style in your projects
- Enforce .NET code style in CI with dotnet format
- The Roslyn analyzers I use in my projects
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!