Refactoring Roslyn like a boss

Roslyn is a platform for the analysis of the C# language. We can use it (among other

Refactoring like a boss with Roslyn
Roslyn is a platform for the analysis of the C# language. We can use it (among other things) to write code refactoring tools. Here is an example of how use Roslyn to rewrite implicit local variables statements (using the var keyword) to make declarations explicit.

Obtained result

Using code shown in this article, we convert the following sample code:

using System;
using System.Collections.Generic;
class Program {
  static void Main(string[] args) {
    var x = 5;
    var s = "Test string";
    var l = new List<string>();
    var scores = new byte[8][]; // Test comment
    var names = new string[3] {"Mike", "Danny", "John"};
  }
}

Into:

using System;
using System.Collections.Generic;
class Program {
  static void Main(string[] args) {
    int x = 5;
    string s = "Test string";
    List<string> l = new List<string>();
    byte[][] scores = new byte[8][]; // Test comment
    string[] names = new string[3]{"Mike", "Danny", "John"};
  }
}

project.json

If we use Microsoft’s open source implementation platform, it is important to add the necessary assemblies to the project.json:

"frameworks": {
    "dnx451": {
      "frameworkAssemblies": {
        "System.Runtime": "4.0.10.0",
        "System.Threading.Tasks": "4.0.0.0",
        "System.Text.Encoding": "4.0.0.0",
        "System.Reflection": "4.0.0.0"
      }
    }
  }

Syntax Factory

The Syntax Factory class is used to programmatically create code. In this example, we need to create a statement of the LocalDeclarationStatement type. For example, to create the code “int x;”:

SyntaxFactory
  .LocalDeclarationStatement(
    SyntaxFactory
      .VariableDeclaration(
        SyntaxFactory.PredefinedType(
          SyntaxFactory.Token(SyntaxKind.IntKeyword)))
        .WithVariables(
          SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
            SyntaxFactory.VariableDeclarator(
              SyntaxFactory.Identifier("x"))
          ))
  .NormalizeWhitespace()

What we do is:

  • Indicate the type as a predefined type int (IntKeyword).
  • Indicate the identifier, in this case “x” (Identifier(“x”)).
  • Format the code (NormalizeWhitespace()).

SyntaxTree and CSharpSyntaxRewriter visitor

The syntax tree is a parsed representation obtained from a C# code. Roslyn provides us with a visitor to visit the syntax tree nodes and nodes that we want to overwrite. In our case, we will visit all the nodes of LocalDeclarationStatementSyntax type and change the var for the explicit type.

To obtain the SyntaxTree of a C#, we use CSharpSyntaxTree.ParseText:

var tree = CSharpSyntaxTree.ParseText(@"
using System;
using System.Collections.Generic;
class Program {
  static void Main(string[] args) {
    var x = 5;
    var s = ""Test string"";
    var l = new List<string>();
    var scores = new byte[8][]; // Test comment
    var names = new string[3] {""Mike"", ""Danny"", ""John""};
  }
}");

Then, we create the visitor that visits all nodes of LocalDeclarationStatementSyntax type:

public class VarRewriter : CSharpSyntaxRewriter
    {
        public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
        {
            return null; // Deletes the node
        }
    }

If we are returning null, we delete the node. Then we will complete the visitor to solve the problem posed.

Semantic Model

To obtain the type of data corresponding to the variable declaration, we must analyze the code to a semantic level (not only syntactic), therefore we will need to obtain the SemanticModel corresponding to a SyntaxTree:

// Get the assembly file, the compilation and the semantic model
var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("RoslynVarRewrite",
  syntaxTrees: new[] { tree },
  references: new[] { Mscorlib });
var model = compilation.GetSemanticModel(tree);

Then, to obtain the type of a variable declaration, we do the following:

model.GetSymbolInfo(node.Declaration.Type).Symbol

Where node is a node of the LocalDeclarationStatementSyntax type.

Var Rewriter

Finally, putting all together, we can complete the visitor that rewrites the implicit statements into explicit:

public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
  var symbolInfo = model.GetSymbolInfo(node.Declaration.Type);
  var typeSymbol = symbolInfo.Symbol;
  var type = typeSymbol.ToDisplayString(
    SymbolDisplayFormat.MinimallyQualifiedFormat);

  var declaration = SyntaxFactory
      .LocalDeclarationStatement(
          SyntaxFactory
              .VariableDeclaration(SyntaxFactory.IdentifierName(
                SyntaxFactory.Identifier(type)))
                  .WithVariables(node.Declaration.Variables)
                  .NormalizeWhitespace()
          )
          .WithTriviaFrom(node);
  return declaration;
}

Here is what we do:

  • Get the type (var type) of the implicit statement (without namespaces), for example: List<string> instead of Collection.Generic.List<string>.
  • Create a LocalDeclarationStatement with the corresponding type and keep the rest equal to the node visited (WithVariables(node.Declaration.Variables)).
  • Keep trivia (format, blanks) of the same node (WithTriviaFrom(node)).
  • Return the declaration that will be replaced by the visited node.

Full Code

You can see the entire project in github’s repository. Just in case, here it is:

using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace RoslynVarRewrite
{
  public class VarRewriter : CSharpSyntaxRewriter
  {
    private readonly SemanticModel model;

    public VarRewriter(SemanticModel model)
    {
      this.model = model;
    }

    public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
    {
      var symbolInfo = model.GetSymbolInfo(node.Declaration.Type);
      var typeSymbol = symbolInfo.Symbol;
      var type = typeSymbol.ToDisplayString(
        SymbolDisplayFormat.MinimallyQualifiedFormat);

      var declaration = SyntaxFactory
          .LocalDeclarationStatement(
              SyntaxFactory
                  .VariableDeclaration(SyntaxFactory.IdentifierName(
                    SyntaxFactory.Identifier(type)))
                      .WithVariables(node.Declaration.Variables)
                      .NormalizeWhitespace()
              )
              .WithTriviaFrom(node);
      return declaration;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      var tree = CSharpSyntaxTree.ParseText(@"
using System;
using System.Collections.Generic;
class Program {
  static void Main(string[] args) {
    var x = 5;
    var s = ""Test string"";
    var l = new List<string>();
    var scores = new byte[8][]; // Test comment
    var names = new string[3] {""Diego"", ""Dani"", ""Seba""};
  }
}");

      // Get the assembly file, the compilation and the semantic model
      var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
      var compilation = CSharpCompilation.Create("RoslynVarRewrite",
        syntaxTrees: new[] { tree },
        references: new[] { Mscorlib });
      var model = compilation.GetSemanticModel(tree);

      var varRewriter = new VarRewriter(model);
      var result = varRewriter.Visit(tree.GetRoot());
      Console.WriteLine(result.ToFullString());
    }
  }
}

 

See All Posts