使用Roslyn删除C#中无关的分号-(替换为空的琐事)

使用Roslyn删除C#中无关的分号-(替换为空的琐事),c#,.net,roslyn,C#,.net,Roslyn,我已经知道了如何打开一个解决方案,然后遍历项目和文档。我一直在寻找在声明末尾可能有一个无关分号(C++风格)的C#类、枚举、结构和接口。我想删除这些文件并将.cs文件保存回磁盘。在我目前的公司里,大约有25个解决方案是我要运行的。注:我们这样做的原因是为了更好地制定编码标准。(我想学习如何使用Roslyn进行这些“简单”调整) 示例(更新): class Program { static void Main(string[] args) { string solu

我已经知道了如何打开一个解决方案,然后遍历项目和文档。我一直在寻找在声明末尾可能有一个无关分号(C++风格)的C#类、枚举、结构和接口。我想删除这些文件并将.cs文件保存回磁盘。在我目前的公司里,大约有25个解决方案是我要运行的。注:我们这样做的原因是为了更好地制定编码标准。(我想学习如何使用Roslyn进行这些“简单”调整)

示例(更新)

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
        }
    }

这些信息必须存储在ClassDeclaration节点中,因为根据C#规范,分号是其产品结束时的可选标记:

类别声明: attributesopt类修饰符选择partialopt类标识符类型参数listopt 类baseopt类型参数约束clausesopt类主体;选择

更新

根据Roslyn的文档,您实际上无法更改语法树,因为它们是不可变的结构。这可能就是
kind
是只读的原因。但是,您可以使用为每个可更改的树属性定义的
With*
方法,并使用
ReplaceNode
创建一个新树。Roslyn文档中有一个很好的例子:

var root = (CompilationUnitSyntax)tree.GetRoot();
var oldUsing = root.Usings[1];
var newUsing = oldUsing.WithName(name); //changes the name property of a Using statement
root = root.ReplaceNode(oldUsing, newUsing);
要将新树再次转换为代码(也称为漂亮打印),可以使用编译单元节点中的
GetText()
方法(在我们的示例中,是
root
变量)

还可以扩展SyntaxRewriter类以执行代码转换。Roslyn官方网站上有一个这样做的广泛例子;看一看。以下命令将转换后的树写回原始文件:

SyntaxNode newSource = rewriter.Visit(sourceTree.GetRoot());
if (newSource != sourceTree.GetRoot())
{
    File.WriteAllText(sourceTree.FilePath, newSource.GetFullText());
}

其中rewriter是SyntaxRewriter的一个实例,这里有一个小程序,它在解决方案中的所有类、结构、接口和枚举声明之后删除可选分号。程序在解决方案中循环遍历文档,并使用
SyntaxWriter
重写syntaxtree。如果进行了任何更改,原始代码文件将被新语法覆盖

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;
      }
    }
  }
}
使用系统;
使用System.IO;
使用System.Linq;
使用Roslyn.Compilers.CSharp;
使用Roslyn.Services;
命名空间跟踪分号
{
班级计划
{
静态void Main(字符串[]参数)
{
字符串solutionfile=@“c:\temp\mysolution.sln”;
var workspace=workspace.LoadSolution(solutionfile);
var solution=workspace.CurrentSolution;
var rewriter=新的跟踪分号rewriter();
foreach(solution.Projects中的var项目)
{
foreach(project.Documents中的var文档)
{
SyntaxTree=(SyntaxTree)document.GetSyntaxTree();
var newSource=rewriter.Visit(tree.GetRoot());
if(newSource!=tree.GetRoot())
{
File.writealText(tree.FilePath,newSource.GetText().ToString());
}
}
}
}
类跟踪分号重写器:SyntaxRewriter
{
公共重写SyntaxNode VisitClassClassDeclaration(ClassDeclarationSyntax节点)
{
返回RemoveSemicolon(node,node.SemicolonToken,t=>node.WithSemicolonToken(t));
}
公共重写SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax节点)
{
返回RemoveSemicolon(node,node.SemicolonToken,t=>node.WithSemicolonToken(t));
}
公共重写SyntaxNode访问StructDeclaration(StructDeclarationSyntax节点)
{
返回RemoveSemicolon(node,node.SemicolonToken,t=>node.WithSemicolonToken(t));
}
公共重写SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax节点)
{
返回RemoveSemicolon(node,node.SemicolonToken,t=>node.WithSemicolonToken(t));
}
专用SyntaxNode移除Semicolon(SyntaxNode节点,
SyntaxToken分号Token,
Func(带符号)
{
if(分号token.Kind!=SyntaxKind.None)
{
var leadingTrivia=分号token.leadingTrivia;
var trailingTrivia=分号token.trailingTrivia;
SyntaxToken newToken=Syntax.Token(
领导里维亚,
SyntaxKind.没有,
追踪琐事);
bool addNewline=分号token.HasTrailingTrivia
&&trailingTrivia.Count()=1
&&trailingTrivia.First().Kind==SyntaxKind.EndOfLineTrivia;
var newNode=withSemicolonToken(newToken);
if(addNewline)
返回newNode.WithTrailingTrivia(Syntax.Whitespace(Environment.NewLine));
其他的
返回newNode;
}
返回节点;
}
}
}
}

希望它与您所寻找的内容大致相同。

我想我可以使用TypeOf()找到一个节点,但我不知道如何从那里说“下一个标记”。我在家里有代码库,今晚我将用我目前掌握的更新这个问题。谢谢我基本上需要“下一个标记,如果分号转换为删除分号”的帮助,然后保存回磁盘。分号将不是下一个标记,而是类的最后一个标记。您可以调用GetLastToken并检查其种类,或者有一个命名属性将是默认的(SyntaxToken)或有效的标记。我想这个属性应该是SelicolonToken。你能用一个简单的例子来编辑你的问题吗?这样我就100%确定你想要去掉什么。谢谢@RobLang刚刚在我的问题末尾添加了一个类示例我刚刚更新了我的答案,包括一些关于如何转换(并漂亮地打印)语法树的示例。希望能有帮助。
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;
      }
    }
  }
}