Fun with Roslyn Rewrite

Though I had never used Roslyn before, I always thought it was a very interesting tool to learn. I had no practical use for it until recently when I decided I’d write my own Mutation Test framework. I’m still moving in baby steps when it comes to both the framework and Roslyn, however, I was very surprised to see how well abstracted and easy to use it is.

I considered the ability to create mutations in the code to be the logical first step in my experiment. For example, a simple mutation would be to turn a Greater Than binary expression into a Less Than instead. Consider the following implementation:

[csharp light=”true”]
namespace FunWithRoslyn
{
public class SalaryCalculator
{
private const decimal HourlyRateSafeFromTax = 20;
public decimal CalculateGrossAmount(decimal hourlyRate, decimal workedHours)
{
var grossAmount = hourlyRate * workedHours;

return IsTaxable(hourlyRate) ? ApplyTax(grossAmount) : grossAmount;
}

public bool IsTaxable(decimal hourlyRate)
{
return hourlyRate > HourlyRateSafeFromTax;
}

public decimal ApplyTax(decimal grossAmount)
{
return grossAmount * 0.85m;
}
}
}
[/csharp]

To change the GreaterThan symbol with a LessThan using Roslyn, it’s as simple as:

[csharp light=”true”]
public class GreaterThanToLessThanSwapper : CSharpSyntaxRewriter
{
public override SyntaxNode VisitBinaryExpression(BinaryExpressionSyntax node)
{
if (node.Kind() == SyntaxKind.GreaterThanExpression)
{
return SyntaxFactory.BinaryExpression(SyntaxKind.LessThanExpression, node.Left, node.Right);
}

return node;
}
}
[/csharp]

All it’s doing is implementing the abstract class CSharpSyntaxRewriter and overriding the VisitBinaryExpression to swap the operator. When you call the Visit method on the Rewriter (which is not overridden) it’ll recursively call every other visit method. Like in this case, when navigating through the nodes, it’ll call the swapper implementation whenever a BinaryExpression is found. The CSharpSyntaxRewriter class exposes Visitor methods for everything you can possibly imagine, like: “VisitForStatement”, “VisitCaseSwitchLabel” and so on.

In case you’re wondering why they’re called Visit, it comes from the Visitor Pattern.

[csharp light=”true”]
class Program
{
static void Main(string[] args)
{
var code = new StreamReader("SalaryCalculator.cs").ReadToEnd();
var tree = CSharpSyntaxTree.ParseText(code);
var originalCode = tree.GetRoot();
var swapper = new GreaterThanToLessThanSwapper();
var updatedCode = swapper.Visit(originalCode);

Console.WriteLine(updatedCode);
}
}
[/csharp]

  1. I obtain the text of the file I want to mutate.
  2. I instantiate a CSharpSyntaxTreeParser and get it to parse the text of the class. By doing so I have access to the entire tree of syntax elements in great detail.
  3. I access the root to get the full implementation.
  4. I create the mutator.
  5. I invoke the mutator behavior by calling the Visit method passing in the original implementation.
  6. Displays the updated implementation.

It never occurred to me that the journey to use Roslyn would be so much fun and I have much to learn yet; if you’re in the same situation as me, take a look at:

https://joshvarty.wordpress.com/learn-roslyn-now/ it’s a great source of information.

Leave a Reply

Your email address will not be published. Required fields are marked *