Team-shared Nuget package with StyleCop and other Roslyn analyzers

In the previous article StyleCop with team-shared rules and auto-update I've covered how to package StyleCop analyzers as a Nuget package, and distribute it to the team with automatic updates. While that approach is still valid, the part related to the Nuget project itself is overcomplicated and cumbersome. This time I'd like to show a simpler and easier way how to package any Roslyn analyzers including StyleCop and all related settings.

The idea of the packaging analyzers using Nuget is that a developer installs only a single team-shared package, and a set of selected and already configured analyzers becomes active. Then, if some analyzers are added, updated or their configuration is changed, it is enough just to update a single package. The whole development team uses the same set of analyzers and is in sync with all their settings.

I'd like to demostrate how to include the folowing analyzers into a single package:

The structure of the Nuget project:

/MyTeam.TeamAnalyzers
|- MyCompany.MyTeam.TeamAnalyzers.props
|- MyTeam.TeamAnalyzers.csproj
|- MyTeam.TeamAnalyzers.ruleset
|- stylecop.json

In the current approach we don't use .nuspec file as it is possible to declare everything needed directly in the .csproj file. This helps a lot, as maintaining the project and updating becomes super easy: just open Nuget UI in the IDE and click on the Update button.

Let's go through each file.

MyTeam.TeamAnalyzers.ruleset describes which rules are enabled/disabled for each analyzers package. For example:

<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="MyTeam TeamAnalyzers" ToolsVersion="15.0">
  <Rules AnalyzerId="Microsoft.VisualStudio.Threading.Analyzers"
         RuleNamespace="Microsoft.VisualStudio.Threading.Analyzers">
    <Rule Id="VSTHRD200" Action="None" />
  </Rules>
  <Rules AnalyzerId="StyleCop.Analyzers"
         RuleNamespace="StyleCop.Analyzers">
    <Rule Id="SA0001" Action="None" />
    <Rule Id="SA1305" Action="Warning" />
    <!-- ... -->
  </Rules>
</RuleSet>

Read more about how to configure analyzers on the Microsoft documentation.

stylecop.json contains settings specific to StyleCop analyzers. For example:

{
  "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
  "settings": {
    "orderingRules": {
      "usingDirectivesPlacement": "outsideNamespace",
      "systemUsingDirectivesFirst": true
    },
    "namingRules": {
      "allowCommonHungarianPrefixes": true
    }
  }
}

Read more about how to configure StyleCop in their documentation on GitHub.

The two files above are straightforward and work exactly according to the documentation. Nothing special, they just must be included in the target project with the analyzers.

The tricky things are in the rest two files.

MyTeam.TeamAnalyzers.csproj contains the list of packages to be installed and the instructions on how additional files must be included in the Nuget package.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <PackageId>MyCompany.MyTeam.TeamAnalyzers</PackageId>
    <Version>1.0.0</Version>
    <Title>MyCompany MyTeam TeamAnalyzers</Title>
    <Authors>MyCompany MyTeam</Authors>
    <Description>A set of team analyzers and their settings.</Description>
    <Copyright>MyCompany</Copyright>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0">
      <PrivateAssets>none</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.1.46">
      <PrivateAssets>none</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406">
      <PrivateAssets>none</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <!-- The name of the .props file and the PackageId must be equal -->
    <None Include="MyCompany.MyTeam.TeamAnalyzers.props">
      <Pack>true</Pack>
      <PackagePath>build</PackagePath>
    </None>
    <None Include="MyTeam.TeamAnalyzers.ruleset">
      <Pack>true</Pack>
      <PackagePath>/</PackagePath>
    </None>
    <None Include="stylecop.json">
      <Pack>true</Pack>
      <PackagePath>/</PackagePath>
    </None>
  </ItemGroup>

</Project>

The PropertyGroup describes the Nuget metadata.

The first ItemGroup contains the references of the packages with analyzers. Pay attention to the PrivateAssets and IncludeAssets attributes.

The second ItemGroup contains the additional files to be included in the package. Two files with configuration will be added to the root of the package. And the .props file is a glue that makes this configuration work: it must be added exactly into the build folder of the package. In this case, according to documentation, the .props file is used during the build of the target project.

The last file MyCompany.MyTeam.TeamAnalyzers.props describes how to add the configuration files in the target project:

<Project>
  <PropertyGroup>
    <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)..\MyTeam.TeamAnalyzers.ruleset</CodeAnalysisRuleSet>
  </PropertyGroup>
  <ItemGroup>
    <AdditionalFiles Include="$(MSBuildThisFileDirectory)..\stylecop.json" Link="stylecop.json" />
  </ItemGroup>
</Project>

That's it! Nothing extra, super easy to add new packages with additional analyzers or remove existing ones, fast and painless updating of the packages.