Featured image of post Enforcing .NET code style rules at compile time

Enforcing .NET code style rules at compile time

Code quality in .NET - Part 2

This post is part of a series:
Part  2  -  Enforcing .NET code style rules at compile time

Introduction Link to this section

Static code analysis is a great tool for spotting some kinds of error in your code, for example, not disposing of objects that implement IDisposable. Also, it helps to enforce and validate if the code written is following a defined standard, for example, using PascalCase for class names and camelCase for parameter names.

In this post I’ll show how to use Roslyn Analyzers with C# to enforce some standards of code quality and code style on your code, throwing errors at compile time if any rules are not being respected and not allowing the code to be pushed to protected branches of the repository.

Roslyn Analyzers Link to this section

Roslyn is the compiler platform for .NET. Roslyn Analyzers are static code analysis tools for Roslyn. They inspect your code for style, quality, maintainability, and practices that are likely to cause bugs. They work based on predefined rules that can have their severity configured in the EditorConfig file.

.NET 5 and later have the analyzers enabled by default. To enable them in earlier versions of .NET, you can set the property EnableNETAnalyzers to true on project files that uses a project SDK or install them as a nuget package:

Setting EnableNETAnalyzers on the project file Link to this section

1
2
3
<PropertyGroup>
  <EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>

Installing as a nuget package Link to this section

Install-Package Microsoft.CodeAnalysis.NetAnalyzers

Enabling more analyzers Link to this section

By default, only some rules are enabled, but we can configure this with the AnalysisMode property in the project file:

1
2
3
<PropertyGroup>
  <AnalysisMode>Recommended</AnalysisMode>
</PropertyGroup>

The AnalysisMode property values allowed are different for .NET 6 and .NET 5 SDKs. Details here.

How to enable .NET Analyzers in VS Code Link to this section

.NET Analyzers work by default in Visual Studio, but they have to be enabled in VS Code.

1 - Navigate to File > Preferences > Settings.

2 - Navigate to Extensions > C# configuration or search for omnisharp.enableRoslynAnalyzers.

3 - Check the Omnisharp: Enable Roslyn Analyzers option.

4 - Navigate to Extensions > C# configuration or search for omnisharp.enableEditorConfigSupport.

5 - Check the Omnisharp: Enable Editor Config Support option.

6 - Restart C#/Omnisharp extension or VS Code.

Types of rules Link to this section

.NET Analyzers have many categories of rules, but here I’ll list just a few to explain how they interact with Visual Studio’s features.

  • Standard formatting: Default Editorconfig options, like indent size and tabs or spaces;

  • Code Style - .NET Formatting: Language specific indentation, whitespaces, and wrapping. For instance, use spaces before parentheses in method definitions.

  • Code Style - .NET Language: C# and Visual Basic specific rules. Examples: using var instead of an explicit type, prefer auto properties instead of backing fields.

  • Code Style - Naming Conventions: Rules about the naming of code elements, like enforcing PascalCase for classes’ names and Async at the end of async methods’ names.

  • Code Style - Unnecessary code: Rules for code that is unreachable or unused variables, fields, etc.

  • Code Quality: Rules to improve code quality. These rules help identify code that are likely to cause bugs or security problems. Examples: Do not declare static members on generic types, and Enums should have zero value.

The table below shows in which features of Visual Studio the fixes for these types of rules are applied on.

Fixes applied on🖹 Format🧹 Code Cleanup💡 Code Fix
Standard Formatting✔️✔️✔️
.NET Formatting✔️✔️✔️
.NET Language✔️✔️
Naming Conventions✔️
Unnecessary Code❗✔️
Code Quality❗

❗ Only some rules have fixes applied.

💡 In the previous post of this series, I explain how to configure Visual Studio to apply this rules on Code Cleanup and how to auto execute Code Cleanup on file save.

Enforcing rules in our code Link to this section

Rules are configured in the EditorConfig file (that I explained in a Part 1 of this series) and their severity can be defined in three levels. Conflicts in the rules are resolved in the following order:

  1. Specific rules
  2. Category rules
  3. All analyzers rules

In the example below, Naming rules violations (IDE1006) will be considered Warning, because it is defined for the specific rule:

1
2
3
4
5
6
# Defines that all analyzers rules are suggestions
dotnet_analyzer_diagnostic.severity = suggestion
# Defines that all Code Style analyzers rules are errors
dotnet_analyzer_diagnostic.category-Style.severity = error
# Defines that the rule IDE1006 is a warning
dotnet_diagnostic.IDE1006.severity = warning

1. Generate an EditorConfig file from Visual Studio Link to this section

First, we need to create an EditorConfig file with the configuration of the rules we will use as standards.

Visual Studio has a tool to help you configure the code style rules of your EditorConfig file, showing a snippet of code of how the rules work.

  1. Go to Tools > Options > Text Editor > C# > Code Style;
  2. Configure your Code Style preferences in the General, Formatting and Naming sub-menus. ⚠️ Don’t bother setting the severities here; some of them are only respected by Visual Studio and are not enforced on build and other IDEs;
  3. Back in the General sub-menu, click Generate .editoconfig file from settings and save it in the folder your solution file is in (.sln).

Configuring code style rules for the EditorConfig file

⚠️ If you are not using Visual Studio, you can use a sample and change it to your preferences, like the one from Roslyn.

Next, we set the AnalysisMode property in all our project files.

For .NET 6 SDK and later, set it to Recommended or All.

1
2
3
<PropertyGroup>
  <AnalysisMode>Recommended</AnalysisMode>
</PropertyGroup>

3. Set severity Error for all analyzers rules Link to this section

In our EditorConfig, include this line to set severity to error for all rules.

# Set severity = error for all analyzers
dotnet_analyzer_diagnostic.severity = error

4. Correct the errors and override the severity for rules you don’t want to use Link to this section

If you are enabling the analyzers in an existing project, many errors will be shown. Correct them and override their severity if they don’t apply for you or you won’t correct them at the moment.

💡 In the previous post of this series, I explain how to add fixers to Visual Studio’s Code Cleanup. You can customize it to fix some rules violations.

Setting rules severity directly in EditorConfig file Link to this section

1
2
3
4
5
6
7
# Other rules ...

# Set severity = none to the rules that are not important for me
dotnet_diagnostic.IDE0075.severity = none

# Set severity = warning to the rules that need to be resolved later
dotnet_diagnostic.IDE0047.severity = warning

Setting rules severity from Visual Studio’s Error List Link to this section

For errors showing up in the Error List, you can right click on the rule and click on Set severity > Choose a severity. The severity configuration will be added to the EditorConfig file.

Setting severity for a rule

Setting rules severity from Visual Studio’s Solution Explorer Link to this section

From Solution Explorer, open the Dependencies > Analyzers node below your project, then right click on the rule and click on Set severity > Choose a severity. The severity configuration will be added to the EditorConfig file.

Setting severity for a rule

5. Enforce the rules on build Link to this section

Enabling the analyzers only shows the messages in our IDE. To really enforce those rules, we have to inform the compiler to fail in case of rules violations, blocking changes that are not compliant to the standard to be merged into protected branches of the repository.

To do this, we need to enable the property EnforceCodeStyleInBuild in all our project files.

1
2
3
<PropertyGroup>
  <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

Examples of rules being enforced Link to this section

Rules being enforced on Visual Studio Link to this section

Rules being enforced on VS Code Link to this section

Rules being enforced on dotnet build command Link to this section

Creating additional naming conventions Link to this section

Here are some naming conventions of the C# language:

SymbolsConventionExample
class/record/structPascalCasePhysicalAddress
interface“I”+PascalCaseIWorkerQueue
public membersPascalCaseStartEventProcessing
private/internal fields“_"+camelCase_workerQueue
static fields“s_"+camelCases_workerQueue
local variables *️camelCaseisValid
parameterscamelCasename
async methodsPascalCase+“Async”GetStringAsync

More details here.

By default, Visual Studio doesn’t create naming conventions for static fields, local variables, parameters and async methods. If we want to use them, we have to manually set those rules, as shown below.

*️ Not specified in the docs, but Roslyn uses this convention.

Creating the naming convention for async methods Link to this section

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
dotnet_naming_rule.async_methods_should_be_pascalcase_async.severity = error
dotnet_naming_rule.async_methods_should_be_pascalcase_async.symbols = async_methods
dotnet_naming_rule.async_methods_should_be_pascalcase_async.style = pascalcase_async

dotnet_naming_symbols.async_methods.applicable_kinds = method
dotnet_naming_symbols.async_methods.applicable_accessibilities = *
dotnet_naming_symbols.async_methods.required_modifiers = async

dotnet_naming_style.pascalcase_async.required_suffix = Async
dotnet_naming_style.pascalcase_async.capitalization = pascal_case

Creating the naming convention for local variables and parameters Link to this section

1
2
3
4
5
6
7
dotnet_naming_rule.locals_and_parameters_should_be_pascal_case.severity = error
dotnet_naming_rule.locals_and_parameters_should_be_pascal_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_and_parameters_should_be_pascal_case.style = camel_case

dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local

dotnet_naming_style.camel_case.capitalization = camel_case

How to ignore the CA1707 rule (Identifiers should not contain underscores) on test projects Link to this section

Some conventions for naming test methods use underscore. If that is your case, you will receive a violation for the CA1707 rule. To disable the rule only on the test project, create a file named GlobalSuppressions.cs in the root of your test project with the content below.

1
2
3
using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Not applicable for test names", Scope = "module")]

Third-party analyzers Link to this section

There are third-party analyzers that can have additional rules that can be useful. These are some:

Tumblr
Pinterest
LinkedIn
Reddit

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus