In this article, I’ll help you understand how to ensure modern C# code style with .NET 5 SDK and EditorConfig. It will save you hours of reading documentation and GitHub issues. Basically, it’s an article I would have liked to read when I first started building our .editorconfig
for .NET 5 in Mews.
What is EditorConfig and why does it exist?
Simply put, EditorConfig is a text file named .editorconfig
to enforce project-wide consistency in code base, no matter what IDE or editor is used. Consistency is a very important aspect of software development, especially when it comes to large ongoing projects like Mews.
For many years, Visual Studio has had a comprehensive setting for code style, see Options -> Text Editor -> C# -> Code Style. It’s nice to have these options, but they can be tricky.
Imagine a situation in which you cooperate with a large group of developers on a certain project, e.g., an open-source project.
Sooner or later, a contributor will try to push some code style violation. Tabs vs. spaces is the most notorious one.
You can kindly ask your contributors to change their IDE settings. If they contribute to multiple projects with different code styles, they would have to constantly change their IDE settings to be compliant with the rest of the project’s code base.
Eventually, this IDE setting juggling will result in a push with a code style violation, not to mention the contributor’s frustration. That’s why EditorConfig was born.
Another solid reason for using .editorconfig
in the .NET ecosystem is that C# is a syntax rich programming language. Too rich.
Microsoft has been developing C# and .NET for more than two decades now. In addition, Microsoft has a strict policy about backwards compatibility. You can take some C# 1.0 code from 2001 and compile it with the latest Roslyn compiler- but the price for this backwards compatibility is consistency.
Over the years of C# development, certain things can be written in three, or even more ways, e.g., a test for null. Code consistency can easily get out of hand when there’s a different level of C# knowledge among developers.
.NET analyzers, including 3rd party tools like StyleCop (which is one of the most popular), can help in enforcing code consistency, but they must be properly configured. This is a scenario where .editorconfig
becomes handy again.
State of the art before .NET 5
You can find three groups of settings in an editorconfig file. The whitespace handling (end of line, tabs etc.), code quality rules (marked as CAxxxx) and code style rules. The last two groups are organized into the following categories and subcategories:
- Code quality rules
- Design rules
- Documentation rules
- Globalization rules
- Portability and interoperability rules
- Maintainability rules
- Naming rules
- Performance rules
- Single File rules
- Reliability rules
- Security rules
- Code style rules
- Language rules
- .NET style rules (prefix dotnet_style_)
- C# style rules (prefix csharp_style_)
- Visual Basic style rules (prefix visual_basic_style_)
- Unnecessary code rules
- Formatting rules
- Naming rules
- Miscellaneous rules
- Usage rules
The syntax for whitespacing varies but can be considered as consistent.
The code quality rules syntax is straightforward: dotnet_diagnostic.CA1000.severity = error
. This tells your IDE analysis engine to thread CA1000 (declaration of static members on a generic type) as an error. Here’s the index of all code quality rules.
The situation with code style rules syntax is… Complicated. Most code style rules have associated options to customize the preferred style. These options have various syntax. As an example, we can take csharp_style_expression_bodied_methods = true:error
. This forces your IDE to use expression bodied methods whenever possible, because severity is set to error.
In addition, these code style rules also have a different method for setting the rule’s severity – dotnet_diagnostic.IDERuleID.severity = severity
where RuleID is xxxx number of a dotnet diagnostic rule associated with the corresponding code style rule. For our example, dotnet_diagnostic.IDE0022
is the corresponding dotnet diagnostic rule. Here’s the index of all code style rules.
As you can see, the building of .editorconfig
for .NET could be a matter of a few lines of code for EOL to a few days spent on reading the code analysis documentation.
Visual Studio 2019 doesn’t provide much when it comes to the management of .editorconfig
. You can generate an editorconfig file based on the current settings or you can add an editorconfig file to your solution from a template, but that’s it. You have to consider .editorconfig
in Visual Studio 2019 as a simple text file. You’re responsible for implementing the correct syntax yourself, and you must know the implementation details. This is probably the biggest problem with .editorconfig
, and Microsoft’s engineers know that.
Visual Studio 2022 provides convenient UI for .editorcofig
editing, but this UI cannot be used yet. More on that later.
Jetbrain Rider provides the same deal as Visual Studio 2019, but with UI for editing code style only and the generated syntax doesn’t use the dotnet diagnostic rule set. On the other hand, Before/After previews are sometimes confusing or show nothing.
Then again, existing .editorconfig
syntax was never adopted by CLI tooling. This decision created more than one problem for the whole .NET ecosystem.
If a contributor is careless and ignores IDE errors, they can commit a code with a code style violation and try to merge it.
There was no way to enforce code style on the level of command line build, e.g., as a part of the continuous integration process.
.NET 5 is a game changer here, but not everyone is going to like it.
EditorConfig in .NET 5 SDK and beyond
If you were wondering why there are two different methods for setting the severity of the code style rules, the CLI tooling is the answer. The severity level from option_name = option_value:severity
is not recognised by CLI, only by IDEs. Microsoft’s compiler team rejected adjustment of MSBuild to respect this syntax and enforced the use of the dotnet_diagnostic
syntax.
Unfortunately, the dotnet_diagnostic
syntax is not recognized by Rider. The documentation says otherwise, but the documentation is lying.
In addition, Visual Studio 2022’s EditorConfig UI tool doesn’t emit the dotnet_diagnostic
syntax when severity is updated. A fix for this bug is constantly pushed further and further back by the Visual Studio engineering team. That means the safe use of this interface cannot yet be predicted at this point.
Unfortunately, using the dotnet_diagnostic
ruleset in .editorconfig
isn’t enough to enforce the code style validation in command line build. You must explicitly instruct the compiler to do so by adding EnforceCodeStyleInBuild = true
to every single project (edit a *.csproj
file) in which you want to enforce the code style validation.
Microsoft had a plan to make the syntax option_name = option_value:severity
obsolete in favor of the dotnet_diagnostic
syntax, but there was a quite expected customer pushback. The result from this is a new approach for handling IDE code style analyzers in command line builds – AnalysisContext.MinimumReportedSeverity
.
By default, all rules will report severity warning
in command line builds and hidden
for IDEs. In doing so IDE code style analyzers will use the MinimumReportedSeverity
and option_name = option_value:severity
entries in .editorconfig
to decide if they should execute or turn off completely.
If you feel confused by all these changes, unfortunately, I’m not done yet. There’s a new kid on the block – Global AnalyzerConfig.
Global AnalyzerConfig
The Global AnalyzerConfig – a .globalconfig
file is a new place for storing the configuration of any dotnet analyzer.
The Global AnalyzerConfig file is an agnostic file-system for overcoming the limitation of EditorConfig files, where their location matters. The Global AnylyzerConfig file allows NuGet and SDKs to ship compilation level analysis configuration for all project files. FuncSharp is one possible case where Global AnalyzerConfig could be used.
Unlike .editorconfig
, the name .globalconfig
is not mandatory, but this file is applied implicitly. The name could be whatever you want, until the file with a custom name has a valid dotnet_diagnostic
syntax and a top-level entry is_global = true
to differentiate from regular EditorConfig files, and it’s registered in a *.csproj
file by the following syntax: <GlobalAnalyzerConfigFiles Include="<path_to_global_analyzer_config>" />
.
Just like .editorconfig
, there could be more than one .globalconfig
in your project’s solution structure. If there’s a conflict between EditorConfig and Global AnalzyerConfig in analyzer setup, the entry from .editorconfig
wins. Two .globalconfig
generates a warning in .NET 5 SDK. A new syntax global_level = xx
has been introduced in .NET 6 SDK to solve these conflicts.
An example of .globalconfig
can be found in PowerShell GitHub repo. If you’re confused about when to use which file, look for design guidelines here. There’s no UI tool for editing the Global AnylyzerConfig file.
The Global AnylyzerConfig file support in Rider is unknown, but ReSharper should support Global AnylyzerConfig files in version 2021.3.
Code style syntax summary
Long story short, managing diagnostics and C# code style can be a challenging, ongoing, complicated, tedious task, especially in a company like Mews with freedom of IDE choice.
To review all the variants of code style configuration, I created a simple console application targeting .NET 5. I chose one code style setting and experimented with different settings to track responses from tooling. Here’s what I found:
Setup configuration | Response | |||||
---|---|---|---|---|---|---|
VS vanilla 16.9 | VS + R# 16.9 | VS vanilla 16.11, 17.0.1, 17.1.0 | Rider 2021.2.1 | CLI MS Build 16.9 | CLI MS Build 16.11 | |
csharp_style_expression_bodied_methods = true | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ |
csharp_style_expression_bodied_methods = true:error | ❎ | ❎ | ❎ | ❎ | ✖ | ✖ |
dotnet_diagnostic.IDE0022.severity = error | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ |
csharp_style_expression_bodied_methods = true dotnet_diagnostic.IDE0022.severity = error | ❎ | ❎ | ❎ | ✖ | ✖ | ✖ |
csharp_style_expression_bodied_methods = true:error dotnet_diagnostic.IDE0022.severity = error | ❎ | ❎ | ❎ | ❎ | ✖ | ✖ |
csharp_style_expression_bodied_methods = true EnforceCodeStyleInBuild = true | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ |
dotnet_diagnostic.IDE0022.severity = error EnforceCodeStyleInBuild = true | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ |
csharp_style_expression_bodied_methods = true:error EnforceCodeStyleInBuild = true | ❎ | ❎ | ❎ | ❎ | ✖ | ✖ |
csharp_style_expression_bodied_methods = true dotnet_diagnostic.IDE0022.severity = error EnforceCodeStyleInBuild = true | ✖ | ✖ | ❎ | ✖ | ✖ | ❎ |
csharp_style_expression_bodied_methods = true:error dotnet_diagnostic.IDE0022.severity = error EnforceCodeStyleInBuild = true | ❎ | ❎ | ❎ | ❎ | ❎ | ❎ |
✖ None
❎ Error
Visual Studio emits three different versions of the same error based on the rule syntax, VS version, and the presence of ReSharper.
Conclusion
The C# code style analysis configuration is a non-trivial task which requires non-negligible ongoing effort. You should consider your workflow (a presence CI/CD mainly), your product (an application or NuGet library), and used IDEs and SDKs in the design of your .editorconfig
.
You should use well structured .editorconfig
with up-to-date syntax along with CSPROJ configuration and use .globalconfig
when it makes sense.
To support all major IDEs (Visual Studio and Rider) and keep freedom of IDE choice, you must maintain old and new code style syntax, preferably with the same severity setup between each other, and update your *.csproj
files in order to support code style check in command line builds.
If you want to get inspired by others in designing your .editorconfig
, this GitHub project is a good place to start.