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.
This is part two in a three-part series on setting up a uniform formatting standard in your code editor. Read part 1 here.
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:
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
Install-Package Microsoft.CodeAnalysis.NetAnalyzers
By default, only some rules are enabled, but we can configure this with the AnalysisMode
property in the project file:
<PropertyGroup>
<AnalysisMode>Recommended</AnalysisMode>
</PropertyGroup>
The AnalysisMode
property values allowed are different for .NET 6 and .NET 5 SDKs. Details here.
.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.
.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.
Rules are configured in the EditorConfig file (that I explained in the Part 1 of this series) and their severity can be defined in three levels. Conflicts in the rules are resolved in the following order:
Specific rules
Category rules
All analyzers rules
In the example below, Naming rules violations (IDE1006
) will be considered Warning
, because it is defined for the specific rule:
# 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
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.
Generate .editoconfig file from settings
and save it in the folder your solution file is in (.sln
).
⚠️ 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
.
<PropertyGroup>
<AnalysisMode>Recommended</AnalysisMode>
</PropertyGroup>
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
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.
# 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
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.
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.
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.
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
Here are some naming conventions of the C# language:
Symbols |
Convention |
Example |
---|---|---|
class/record/struct |
PascalCase |
PhysicalAddress |
interface |
"I"+PascalCase |
IWorkerQueue |
public members |
PascalCase |
StartEventProcessing |
private/internal fields |
"_"+camelCase |
_workerQueue |
static fields |
"s_"+camelCase |
s_workerQueue |
local variables *️ |
camelCase |
isValid |
parameters |
camelCase |
name |
async methods |
PascalCase+"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.
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
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
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.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Not applicable for test names", Scope = "module")]
There are third-party analyzers that can have additional rules that can be useful. These are some:
I post extra content in my personal blog. Click here to see.
Also published here.