Gotcha with .globalconfig and Visual Studio

The Goal: gradually introduce analyzer checks

The premise of this post is to introduce .NET analyzer checks into a mono-repo for different teams.
The approach must not cause any additional build warnings until the teams opt-in their projects.

As good .NET citizens we will not put .NET-specific analyzer rules in our .editorconfig, but use the recommended global AnalyzerConfig file.

What the heck is a global AnalyzerConfig file?

In addition to .editorconfig files, you can provide so-called global AnalyzerConfig files.
Although global global AnalyzerConfig files have been around since .NET 5 SDK, I personally have not seen wide adoption.

I will not repeat all the differences between the two, but instead point to the official documentation and this well-written comparison.

In short: .editorconfig is for syntax, global AnalyzerConfig files are for code analysis.

The naive approach has a surprise for you

Alright, this sounds simple: add a global AnalyzerConfig file and then let each team opt into using it by setting the EnableNETAnalyzers property in their project.
By using the default file name .globalconfig, the project should then pick it up automatically.

Sure enough, this works for the CLI build: without EnableNETAnalyzers set, the build does not care about the .globalconfig, and with EnableNETAnalyzers set, the build will pick it up automatically because of its file name.

Yay!

But, wait a minute.
Why do I get build warnings in my Visual Studio, even though I did not set EnableNETAnalyzers ?
I did not opt in, so I do not want any warnings!

Well, it seems like Visual Studio itself (tested with VS 2022 17.5) will enforce a .globalconfig independent of whether the project has EnableNETAnalyzers set or not.
It will thus show warnings even though the CLI build does not.

This is bad developer experience, so what do we do?

Getting consistent behavior in CLI and VS

The file needs to have a different name as to not be picked up by VS automatically.
Let’s call it code-style.config.

Now, when we do set EnableNETAnalyzers, the file will also not be picked up automatically.
We must explicitly add it to the GlobalAnalyzerConfigFiles item.
To opt-in a project, the settings we must add now look like this:

<PropertyGroup>
  <EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
<ItemGroup>
  <GlobalAnalyzerConfigFiles Include="../../code-style.config" />
</ItemGroup>

While this may be fine for developers who actively author csproj files and know a bit of MSBuild, it may be offputting for many others.

Simplifying usage to ease adoption

We thus put all of the above in a global code-style.props file right next to the global code-style.config file (following the rules for .props files)

<Project>
  <PropertyGroup>
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
  </PropertyGroup>
  <ItemGroup>
    <GlobalAnalyzerConfigFiles Include="$(MSBuildThisFileDirectory)code-style.config" />
  </ItemGroup>
</Project>

The teams now only have to add one line to their project to opt in:

<Import Project="../../code-style.props" />

Alright, that looks good enough.

For good measure, add a comment in the code-style.props and/or code-style.config about the reason for this seemingly overcomplicated approach, so that future readers do not have to be surprised again.