Roslyn Compiler Fun: Implementing a Property-Expression Analyzer and Refactoring!

The upcoming .net compiler platform “roslyn” is a really exciting piece of technology that enables us c# developers to create our own custom code analysis and refactoring libraries. In this post I will show you how I have used the roslyn apis to create a custom refactoring that transforms common property-expression helper code to the new c# 6 nameof expression.

Most c# developers try to avoid magic strings whenever possible because of missing refactoring support and missing static verification by the compiler. However, historically c# made it very difficult to avoid magic strings if you needed to provide a string which names some program element. This if often needed if you want to identify the property which has changed when using the INotifyPropertyChanged interface.

Lets have a look at the following common code, which many developers around the world have written in some similar form:

1
2
3
4
5
6
7
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Forename") // Magic string
{
// Perform some logic
}
}

With the introduction of lambda expressions in C#3, many developers started working around this limitation by implementing a helper class, which uses linq expression trees to get the names of these properties with compiler check.

So instead of the above code, we often write the following code instead:

1
2
3
4
5
6
7
8
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName ==
PropertyUtil.GetName(x => x.Forename))
{
// Perform some logic
}
}

This makes the code more verbose and adds some runtime overhead, but solves the problem of missing compiler verification and gives us safe refactoring support.
Fortunately, with C#6 we can now use the new nameof() expression feature. Nameof() allows you to name a symbol in source code, so you get a compiler checked and refactoring enabled string.

So once you use C#6 you could browse through all of your files and find those nasty PropertyUtil calls and replace them by the new nameof() keyword.

1
2
3
4
5
6
7
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName ==
nameof(SampleViewModel.Forename)
{
}
}

Maybe this is a fun task at the beginning, but after fixing the first 10 occurencies you usually want to automate this, especially if you have to “fix” a whole solution with thousands of occurencies.
Fortunately, we can use the power of the new roslyn compiler to automate this task.

Since I have always wanted to dig into the roslyn apis, this was a really nice use case to write my first roslyn analyzer and refactoring.

Interested? This is a screenshot which shows my new nameof refactoring in action:

Prerequisites

  • Install the current Visual Studio 2015 CTP6
  • Install the latest Visual Studio SDK + Roslyn Syntax Analyzer + Roslyn Template
  • Install the Roslyn Syntax Analyzer VSIX + Roslyn SDK Templates VSIX extensions

Once you have installed all packages and created a new code analyzer project, you can start to implement your analyzer and refactoring.

Step #1 : Exploring the c# syntax tree

In the first step I have used the roslyn syntax tree analyzer to generate an overview of how the PropertyUtil invocation is represented in the roslyn ast.
The PropertyUtil.GetName(x => x.Name) invocation is represented as the following syntax graph:

With this information, it is now possible to create a new DiagnosticAnalyzer:

Step #2: Implementing the DiagnosticAnalyzer

Since we are interested in syntax nodes of type “InvocationExpressionSyntax”, I have used the method RegisterSyntaxNodeAction to register a callback to analyze the invocation.

1
2
3
4
5
6
7
8
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class PropertyExpressionAnalyzer : DiagnosticAnalyzer
{
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression);
}
}

In the following AnalyzeInvocation method it is now easy to check if the analyzing expression-syntax matches the PropertyUtil expression call.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
{
var invocationExpressionSyntax = (context.Node as InvocationExpressionSyntax);
var memberAccess = invocationExpressionSyntax?.Expression as MemberAccessExpressionSyntax;
if (memberAccess != null)
{
var name = (memberAccess.Expression as IdentifierNameSyntax)?.Identifier.ValueText;
if (name == "PropertyUtil")
{
var childNodes = (context.Node as InvocationExpressionSyntax).ArgumentList.Arguments[0];
var lambdaExprSyntax = childNodes.Expression as SimpleLambdaExpressionSyntax;
var memberAccessExprSyntax = lambdaExprSyntax.Body as MemberAccessExpressionSyntax;
if (memberAccessExprSyntax == null)
return;
string propertyName = memberAccessExprSyntax.Name.Identifier.ValueText;

var genericNameSyntax = memberAccess.Name as GenericNameSyntax;
string genericNameParamter = (genericNameSyntax.TypeArgumentList.Arguments[0] as IdentifierNameSyntax).Identifier.ValueText;

var diagnostic = Diagnostic.Create(Rule, memberAccess.GetLocation(), context.Node.ToString(), genericNameParamter, propertyName);
context.ReportDiagnostic(diagnostic);
}
}
}

Step #3: Implementing the CodeFixProvider

Now we are able to implement the CodeFix provider. This class replaces the original syntax tree with the new simplified syntax tree.

The first step is to register the new code fix via the RegisterCodeFixesAsync method. The CodeFixContext argument gives us the source span which represents the boundings of the analyzed code.
I have then used the RegisterCodeFix method to register the new Use nameof(…) callback UseNameOfAsync(..).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;

var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First();

var expr = root.FindToken(diagnosticSpan.Start)
.Parent.AncestorsAndSelf()
.OfType()
.First();

context.RegisterCodeFix(
CodeAction.Create("Use nameof(...)",
cancellationToken => UseNameOfAsync(context.Document, expr, cancellationToken)),
diagnostic);
}

The callback gets invoked immediately when the refactoring preview of visual studio shows up and enables VS to both show you a preview of the refactoring and to apply the refactoring if needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private Task UseNameOfAsync(Document document, InvocationExpressionSyntax invocationExpression, CancellationToken cancellationToken)
{
var memberAccess = invocationExpression.Expression as MemberAccessExpressionSyntax;
var genericName = memberAccess.Name as GenericNameSyntax;
string genericNameParamter = (genericName.TypeArgumentList.Arguments[0] as IdentifierNameSyntax).Identifier.ValueText;

var childNodes = invocationExpression.ArgumentList.Arguments[0];
var lambdaExp = childNodes.Expression as SimpleLambdaExpressionSyntax;
var memberAccessBody = lambdaExp.Body as MemberAccessExpressionSyntax;
string propertyName = memberAccessBody.Name.Identifier.ValueText;

var nameOfSyntax = SyntaxFactory.ParseExpression(string.Format("nameof({0}.{1})", genericNameParamter, propertyName))
.WithLeadingTrivia(invocationExpression.GetLeadingTrivia())
.WithTrailingTrivia(invocationExpression.GetTrailingTrivia());

var root = document.GetSyntaxRootAsync().Result;
var newRoot = root.ReplaceNode(invocationExpression, nameOfSyntax);
return Task.FromResult(document.WithSyntaxRoot(newRoot));
}

Step #4: Start Visual Studio 2015 and enjoy the new refactoring:

Thats it! The above code (+ some boilerplate removed for simplicity of this blog) is everything I needed to code to implement my first roslyn refactoring:

I have published the full source code of this analyzer on this github repository: https://github.com/davidroth/propertyexpressionanalyzer

If you have some similar form of expresison helper and also want to upgrade your solution to use the new nameof() feature, you can download and modify the code so that it matches your helper classes.
Most probably you just need to rename the 2 string variables which represent the name of the utility class (ex. “PropertyUtil”) and the method (ex: “GetName”).