• Blog
  • About
  • Contact
linkedin
twitter

Blog Post

26
OCT
2011

Roslyn – Formatting Code

tag : Code Generation, Roslyn
by : Christoph De Baene
comment : 0

Roslyn CTP is the implementation of a ‘compiler-as-a-service’ for C# and VB.Net. Generally compilers are black boxes, the Roslyn project changes that by opening up APIs that you can use for code related tasks in your tools and applications.

In this post we are investigating source code formatting. Inside my projects I use as much as possible code generation and whether it is coming from T4 templates or some other mechanism, there is usually the need to format your code in a consistent way.

Inside Roslyn we can use the SyntaxTree class that resides in the Roslyn.Compilers.CSharp namespace to parse a text file (that contains C# of VB code). And after we can use the Format extension method that resides in the CompilationUnitSyntax class to reformat your code.

C#
1
2
3
4
5
6
var code = File.ReadAllText("Sample.cs");
 
var tree = SyntaxTree.ParseCompilationUnit(code);
var root = (CompilationUnitSyntax)tree.Root;
 
var formattedCode = root.Format().GetFullText();

Take for example the code fragment below (Sample.cs) which is totally unformatted.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
namespace Domain
{
using System;
using System
.Collections
.Generic;
using System.ComponentModel.DataAnnotations;
 
public class BankAccount
:Entity,
IValidatableObject
{
public BankAccountNumber BankAccountNumber {
get;
set; }
 
public string Iban
{
get
{
return string
.Format("ES{0} {1} {2} {0}{3}",
this.BankAccountNumber.CheckDigits,
this.BankAccountNumber
.NationalBankCode,
this.BankAccountNumber
.OfficeNumber,
this.BankAccountNumber
.AccountNumber);
}
set {
 
}
}
 
public decimal Balance { get;
private set; }
 
public virtual ICollection
BankAccountActivity
{
get
{
if (_bankAccountActivity
== null)
_bankAccountActivity =
new HashSet();
 
return _bankAccountActivity;
}
set
{
_bankAccountActivity =
new HashSet(value);
}
}
}
}

When passing the code fragment through the SyntaxTree and calling the Format extension method you get the following result which is formatted correctly.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
namespace Domain
{
   using System;
   using System.Collections.Generic;
   using System.ComponentModel.DataAnnotations;
 
   public class BankAccount : Entity, IValidatableObject
   {
      public BankAccountNumber BankAccountNumber
      {  
         get;
         set;
      }
 
      public string Iban
      {
         get
         {
            return string.Format("ES{0} {1} {2} {0}{3}", this.BankAccountNumber.CheckDigits, this.BankAccountNumber.NationalBankCode, this.BankAccountNumber.OfficeNumber, this.BankAccountNumber.AccountNumber);
         }
         set
         {
         }
      }
 
      public decimal Balance
      {
         get;
         private set;
      }
 
      public virtual ICollection BankAccountActivity
      {
         get
         {
            if (_bankAccountActivity == null)
               _bankAccountActivity = new HashSet();
            return _bankAccountActivity;
         }
         set
         {
            _bankAccountActivity = new HashSet(value);
         }
     }  
   }
}

Note that when you are using for example lambda expressions or delegates that the formatter will add unnecessary newlines and whitespaces. Take for example the following code fragment after calling the Format method.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void SomeMethod()
{
   this.Click += (s, e) =>
   {
      MessageBox.Show(((MouseEventArgs)e).Location.ToString());
   }
   ;
   treeView.AfterExpand += new TreeViewEventHandler(delegate (object o, TreeViewEventArgs t)
   {
      t.Node.ImageIndex = (int)FolderIconEnum.open;
      t.Node.SelectedImageIndex = (int)FolderIconEnum.open;
   }
);
}

Thankfully we have everything in control through the APIs and we can rewrite the expression (like the Format extension method is doing).

For that we need to create a class inheriting from SyntaxRewriter that resides in the Roslyn.Compilers.CSharp namespace and implements the Visitor pattern.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class CodeBeautifier: SyntaxRewriter
{
   protected override SyntaxToken VisitToken(SyntaxToken token)
   {
      switch (token.Kind)
      {
         case SyntaxKind.SemicolonToken:
 
         if (token.GetPreviousToken().Kind == SyntaxKind.CloseBraceToken ||
            token.GetPreviousToken().Kind == SyntaxKind.CloseParenToken)
         {
            return token
            .WithLeadingTrivia()
            .WithTrailingTrivia(Syntax.ElasticCarriageReturnLineFeed);
         }
 
         break;
 
      case SyntaxKind.CloseBraceToken:
 
         if (token.GetNextToken().Kind == SyntaxKind.CloseParenToken ||
            token.GetNextToken().Kind == SyntaxKind.SemicolonToken)
         {
             return token
             .WithTrailingTrivia();
         }
 
      break;
 
      case SyntaxKind.CloseParenToken:
 
         if (token.GetPreviousToken().Kind == SyntaxKind.CloseBraceToken)
         {
            return token
            .WithLeadingTrivia();
         }
 
      break;
   }
 
    return token;
}
}

Note that I am visiting the syntax tokens, these are the terminals of the language grammar (representing the smallest syntactic fragments) and investigating the current kind of token with the next or previous kind of token. Syntax Trivia represents the parts such as whitespace, comments and preprocessor directives. Inside the VisitToken method I am replacing the syntax trivia parts.

To use the CodeBeautifier class you need to simply create an instance of it and using the Visit method to pass your node.

C#
1
2
3
4
5
6
var code = File.ReadAllText("Sample.cs");
 
var tree = SyntaxTree.ParseCompilationUnit(code);
var root = (CompilationUnitSyntax)tree.Root;
 
var formattedCode = new CodeBeautifier().Visit(root.Format()).GetFullText();

After the syntax rewriting you will see that the code now looks like below

C#
1
2
3
4
5
6
7
8
9
10
11
12
public void SomeMethod()
{
this.Click += (s, e) =>
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
};
treeView.AfterExpand += new TreeViewEventHandler(delegate (object o, TreeViewEventArgs t)
{
t.Node.ImageIndex = (int)FolderIconEnum.open;
t.Node.SelectedImageIndex = (int)FolderIconEnum.open;
});
}

About the Author

Social Share

    Leave a Reply Cancel reply

    You must be logged in to post a comment.

    Tag cloud

    .NET SDK Ajax Architecture ASP.NET ASP.NET Web API Code Generation Components Events Forms Hardware Modeling Neo4j Patterns & Practices Personal Roslyn Services Silverlight SQL Server Testing Tools Unity Utils & Tools Virtualization Visual studio Windows Live WPF

    Archive

    • April 2012
    • October 2011
    • August 2011
    • April 2011
    • March 2011
    • July 2009
    • March 2009
    • February 2009
    • January 2009
    • December 2008
    • November 2008
    • October 2008
    • August 2008
    • March 2008
    • December 2007
    • November 2007
    • October 2007
    • March 2007
    • February 2007
    • January 2007
    • December 2006
    • November 2006
    • October 2006
    • September 2006
    • August 2006
    • May 2006
    • April 2006
    • March 2006
    • February 2006
    • January 2006
    • November 2005
    • October 2005
    • September 2005
    • August 2005
    • July 2005
    • June 2005
    • May 2005
    • April 2005
    • March 2005
    • February 2005
    • September 2004
    • July 2004
    • June 2004
    • May 2004
    • April 2004
    • March 2004
      Copyright 2013, Christoph De Baene