Remove Extraneous Semicolons in C# Using Roslyn - (replace w empty trivia)

.net, c#, roslyn

Solution

Here is a little program that removes the optional semicolon after all class-, struct-, interface and enum-declarations within a solution. The program loops through documents within the solution, and uses a `SyntaxWriter` for rewriting the syntaxtree. If any changes were made, the original code-files are overwritten with the new syntax.

using System;
using System.IO;
using System.Linq;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;

namespace TrailingSemicolon
{
  class Program
  {
    static void Main(string[] args)
    {
      string solutionfile = @"c:\temp\mysolution.sln";
      var workspace = Workspace.LoadSolution(solutionfile);
      var solution = workspace.CurrentSolution;

      var rewriter = new TrailingSemicolonRewriter();

      foreach (var project in solution.Projects)
      {
        foreach (var document in project.Documents)
        {
          SyntaxTree tree = (SyntaxTree)document.GetSyntaxTree();

          var newSource = rewriter.Visit(tree.GetRoot());

          if (newSource != tree.GetRoot())
          {
            File.WriteAllText(tree.FilePath, newSource.GetText().ToString());
          }
        }
      }
    }

    class TrailingSemicolonRewriter : SyntaxRewriter
    {
      public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
      {
        return RemoveSemicolon(node, node.SemicolonToken, t => node.WithSemicolonToken(t));
      }

      public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
      {
        return RemoveSemicolon(node, node.SemicolonToken, t => node.WithSemicolonToken(t));
      }

      public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
      {
        return RemoveSemicolon(node, node.SemicolonToken, t => node.WithSemicolonToken(t));
      }

      public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node)
      {
        return RemoveSemicolon(node, node.SemicolonToken, t => node.WithSemicolonToken(t));
      }

      private SyntaxNode RemoveSemicolon(SyntaxNode node,
                                         SyntaxToken semicolonToken,
                                         Func<SyntaxToken, SyntaxNode> withSemicolonToken)
      {
        if (semicolonToken.Kind != SyntaxKind.None)
        {
          var leadingTrivia = semicolonToken.LeadingTrivia;
          var trailingTrivia = semicolonToken.TrailingTrivia;

          SyntaxToken newToken = Syntax.Token(
            leadingTrivia,
            SyntaxKind.None,
            trailingTrivia);

          bool addNewline = semicolonToken.HasTrailingTrivia
            && trailingTrivia.Count() == 1
            && trailingTrivia.First().Kind == SyntaxKind.EndOfLineTrivia;

          var newNode = withSemicolonToken(newToken);

          if (addNewline)
            return newNode.WithTrailingTrivia(Syntax.Whitespace(Environment.NewLine));
          else
            return newNode;
        }
        return node;
      }
    }
  }
}

Hopefully it is something along the lines of what you were looking for.

Problem

I've figured out how to open a solution and then iterate through the Projects and then Documents. I'm stuck with how to look for C# Classes, Enums, Structs, and Interfaces that may have an extraneous semicolon at the end of the declaration (C++ style). I'd like to remove those and save the .cs files back to disk. There are approximately 25 solutions written at my current company that I would run this against. Note: The reason we are doing this is to move forward with a better set of coding standards. (And I'd like to learn how to use Roslyn to do these 'simple' adjustments) Example (UPDATED): ``` class Program { static void Main(string[] args) { string solutionFile = @"S:\source\dotnet\SimpleApp\SimpleApp.sln"; IWorkspace workspace = Workspace.LoadSolution(solutionFile); var proj = workspace.CurrentSolution.Projects.First(); var doc = proj.Documents.First(); var root = (CompilationUnitSyntax)doc.GetSyntaxRoot(); var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>(); foreach (var decl in classes) { ProcessClass(decl); } Console.ReadKey(); } private static SyntaxNode ProcessClass(ClassDeclarationSyntax node) { ClassDeclarationSyntax newNode; if (node.HasTrailingTrivia) { foreach (var t in node.GetTrailingTrivia()) { var es = new SyntaxTrivia(); es.Kind = SyntaxKind.EmptyStatement; // kind is readonly - what is the right way to create // the right SyntaxTrivia? if (t.Kind == SyntaxKind.EndOfLineTrivia) { node.ReplaceTrivia(t, es); } } return // unsure how to do transform and return it } } ``` Example Code I Want to Transform ``` using System; public class Person { public string FirstName { get; set; } public string LastName { get; set; } }; // note: the semicolon at the end of the Person class ```

Original source