Lately I have been experimenting with Roslyn Code Analyzers and Code Fixes with Einar W. Høst, and we have found it to be a very nice addition to unit tests and static code analysis with NDepend. What I particularly like is the immediate developer feedback and possibility to add code suggestions for other developers.
In short, Code Analyzers let’s you add errors, warnings or squigglies directly in Visual Studio, with tooltips. You can enhance those with Code Fix providers, which will enable code refactoring using the standard shortcuts (ctrl+b for quick actions).
Real life usage scenario
Our current use case is a HAL NuGet package we are working on. We provide some base classes for enabling HAL in our API contracts, making it easy for the package consumers to follow the HAL specification. However, it is still fully possible to break the HAL standard, or to break deserialization. We could always detect that using static analysis in our CI pipeline, or hope that we would catch such mistakes by code reviews. However, adding our rules as Code Analyzers with Code fixes provides instant and friendly developer feedback.
Let’s look at one specific use case, we have a class
HalResource where TLinks : HalLinks. So the developers can inherit from that in their model, and then make another class implementing HalLinks. Having statically typed HalLinks provides us with two benefits:
- We can guarantee the types of links presented to the consumer
- We can generate documentation for these links using Swagger
One of the premises for this to work is that all public properties on classes deriving from
HalLinks must be of type
IEnumerable<HalLink>. Enforcing this can be difficult, but this is something which we can quite easily do with Roslyn Code Analyzers and Code Fixes.
Let’s take a look at an example where I add a property of type
string instead of
HalLink. Notice the squigglies under string.
When I place my marker on the squigglies, I get further help:
And when I press ctrl+. for quick actions I get a preview of how the code will look after I apply the defined code fix.
A really nice thing to remember is that we can distribute our analyzers and fixes within the NuGet package so it is automatically enabled for any developer using our package.
How to create your own
The easiest way to get started is to create a new project from and go from there.
Analyzer with Code Fix template is not there, try installing the .NET Compiler Platform SDK from Visual Studio Marketplace – currently found here.
This will create three new projects, an analyzer project, a test project for your analyzers and a VSIX project. We don’t really use the VSIX project except when we wish to try our code analyzer and fix behaves in Visual Studio. For rapid development, the unit testes tends to be sufficient.
In the Analyzer project you’ll find a sample DiagnosticAnalyzer and a CodeFixProvider. The fastest way to get started is to start playing with the DiagnosticsAnalyzer which is responsible for finding code. Unless you wish to create a code action to assist with refactoring, this is all you need to care about.
Writing a diagnostics analyzer is a fairly straight forward task, the main steps required are:
- Inherit from
- Create a
DiagnosticsDescriptor(called Rule in the template)
RegisterSymbolActionon the context
- Implement the Symbol action, calling
ReportDiagnosticswith a Diagnostic create from the
The Symbol action is where you will need to write your logic for evaluating the source code. Here’s a sample on how you can locate properties without private setters
Writing a codefix provider is not much different, except we’ll be manipulating the code tree instead of just analyzing it.
- Inherit from
FixableDiagnosticIds, the id’s returned should match the Id in the DiagnosticsDescriptor from your analyzer.
GetFixAllProvider()which enables fixing multiple instance of this error at once. More details here
RegisterCodeFixesAsync(CodeFixContext context)and call
- Implement your fix.
Here’s an example on how to change a property setter to private. Notice that the Roslyn api’s for working with code are immutable (yay).
Executing and testing your provider
When you create an Analyzer + Fix project, you get a Unittest and VSIX project as well. The Vsix can be used to fire up a new solution with your Analyzer project loaded. This is however somewhat tedious and I much prefer the unit test approach. The unit tests are quite simple to write as the Test project mostly contains what you need.
Writing a new unit test can be done by
- Creating a new class, intherit from
GetCSharpDiagnosticAnalyzer()and return your own DiagnosticAnalyzer implementation
GetCSharpCodeFixProvider()and return your own CodeFixProvider implementation
- For Analyzer a test can be written as such
- Testing a fix follows the same pattern and can be done using
VerifyCSharpFix(string code, string code)which will compare input with output.
We usually distribute our code fixes and analyzers with other libraries, and by adding them to the right place in the nuget package they are automatically picked up by Visual Studio 2017. The C# analyzers should be located in
analyzers/dotnet/cs. If all your users use Visual Studio 2017, you can remove the tools folder and powershell scripts created by the Analyzer with Code Fix project template.
We often scope our tests to a specific namespace or to classes implementing an interface or inheriting from a base class. Running the tests will compile the code on the fly in an empty solution, hence it has no knowledge of our interface or class and it will result in a compilation error and thus not apply our analyzer. To remedy this we have slightly modified the generated test helpers
DiagnosticsVerifier.cs with a new method to override, and
DiagnosticsVerifierHelper.cs where we include our references as a method parameter to the
GetSortedDiagnosticsFromDocuments and then add them to the compilation as shown below.
One will then need to update all calls to the
GetReferences() as the last parameter. This enables us to include dll’s we wish to reference in the compilation of the code in the unit tests by overriding
GetReferences() in the test class and adding a reference to the dll’s we need to compile the test code for the Analyzer project.
For more documentation on Roslyn and Code Analyzers/Fixes I can recommend checking out https://github.com/dotnet/roslyn/tree/master/docs
When and where?
Roslyn Code fixes and Analyzers are powerful tools when you have more than a handfull developers or external consumers. And it fits really good with APIs provided as NuGet packages or to protect the domain model.
The first few providers will take some time to write, but as you get more comfortable working with the semantic model and syntax tree, writing new ones are quite simple. We also see that we quickly build extension methods and libraries for common checks and actions.