Writing a simple analyzer using Roslyn – part 2

Writing a simple analyzer using Roslyn – part 2

In the first part of our article we discussed analyzers – why we need them and how to created them. We continue the article by showing you how to write a Fixer.

Writing a Fixer

Before proceeding to a more correct implementation of a type definition of our command, let's create a simple fixer. Its main task will be to replace the default constructor call with the constructor that takes an Action object. As it is difficult to come up with a sensible behavior, we're just going to pass an empty lambda expression there.

The first task of a fixer is to tell the diagnostics infrastructure which problems it can solve. This is made by using FixableDiagnosticIds property:

8.jpg



Now we need to register the action of fixing an issue:

9.jpg


Here we can see another feature: in the current diagnostics infrastructure there is no possibility to transfer data between the diagnostics and the fixer. The fixer knows that the diagnostics "complains" about the constructor call, so we need to manually get an instance of ObjectCreationExpression. This leads to a rather rigid connection between the fixer and the analyzer, which can cause problems when the diagnostics are provided for various nodes of the tree.

The main action in this method takes place in the method AddEmptyLambda, which takes the old document, the problem node ObjectCreationExpression, and returns the updated document:

10.jpg


Since the syntax tree in Roslyn is immutable, we need to use a set of WithXXX methods to create new elements with replaced nodes. Another goal of this method is to create constructor arguments - an empty lambda expression, which is done using SyntaxFactory class.

That’s all; our fixer is ready and we can start testing it:

11.jpg


Our project with tests already contains a helper method VerifyCSharpFix, which makes testing a fixer a rather simple task.

Semantic Information and Correct Type Search

The first implementation of diagnostics was detecting the use of RelayCommand structure by comparing the text. This is not the best way, since it may be that the code contains more than one type with the name RelayCommand. Our analyzer must distinguish one type from the other and issue warnings only for the structure of the assembly MvvmUltraLight. And the semantic model will help us here.

The main difference of syntax trees from symbols (obtained through SemanticModel class) is that the latter define "related symbols" which point to specific types. Syntax tree defines whitespaces, comments, etc. by low-level nodes, such as names, and does not know anything about the rules of method overloading or visibility. Semantic model knows who and where used this method, how to find the definition of the called method, and what type was used there. Syntax tree can be created even for “uncompilable” programs, and semantic model will only be available in the event of a successful compilation.

12.jpg


Now, IsRelayCommandType method returns true only when an instance of RelayCommand structure is created, defined in MvvmUltraLight assembly.

Summary
In this post we created a very simple analyzer which covers only a fraction of the required functionality. For example, it does not cover factory methods, and the creation of structures with the help of default(RelayCommand).

My main goal was to show the complexity (or, more precisely, the relative simplicity) of the solution. Download an example of this project from github and try to extend its functionality. Or think about an analyzer which could be useful in your own project and try to implement it. You will be surprised how easy it is!

Sergey Teplyakov
Expert in .Net, С++ and Application Architecture