如何使用Roslyn以编程方式从代码中删除区域?

问题描述:

我使用Roslyn从文本解析C#代码。一些代码具有围绕多个类的区域。例如:如何使用Roslyn以编程方式从代码中删除区域?

#region Classes 
public class MyClass 
{ 
} 

public class MyClass2 
{ 
    #region Methods 
    #endregion 
} 
#endregion 

我想移除周围的类(“类”在上面的例子)的区域,但离开内部区域完好像一个命名在上面的例子中“方法”。我怎么能这样做呢?

+0

请向我们展示您的进度。 – mjwills

+1

只需使用syntaxrewriter删除不需要的区域... – Sievajet

由于Sievajet建议,您可以使用CSharpSyntaxRewriter删除连接到特定节点(在您的情况下:ClassDeclarationSyntaxRegion

这里是代码,让你开始:

public class RegionRemoval : CSharpSyntaxRewriter 
    { 
     public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) 
     { 

      if(node.HasLeadingTrivia) 
      { 
       var enumerator = node.GetLeadingTrivia().GetEnumerator(); 

       while(enumerator.MoveNext()) 
       { 
        var syntaxTrivia = enumerator.Current; 
        if(syntaxTrivia.Kind().Equals(SyntaxKind.RegionDirectiveTrivia)) 
        { 
         node = node.ReplaceTrivia(syntaxTrivia, SyntaxFactory.Whitespace("\n")); 
        } 
       } 

      } 
      return node; 
     } 
    } 

    class RoslynTry 
    { 
     public static void RegionRemover() 
     { 
      //A syntax tree with an unnecessary semicolon on its own line 
      var tree = CSharpSyntaxTree.ParseText(@" 
        #region Classes 
     public class MyClass 
     { 
     } 

     public class MyClass2 
     { 
      #region Methods 
      #endregion 
     } 
     #endregion 
     "); 

      var rewriter = new RegionRemoval(); 
      var result = rewriter.Visit(tree.GetRoot()); 
      Console.WriteLine(result.ToFullString()); 
     } 
    } 

你的输出应该是这样的:

 public class MyClass 
     { 
     } 

     public class MyClass2 
     { 
      #region Methods 
      #endregion 
     } 
     #endregion 

附: :这不是完整的解决方案。我同意mjwills,你应该在发布问题之前展示一些进展。

P.S. :代码的灵感来源于JoshVartyEmptyStatementRemoval

区域比较特殊,因为它们不遵循通常的树状结构。例如,您可以创建如下构造:

public class TestClass{ 
    public void TestMethod(){ 
     #region TestRegion 
    } 
} 
#endregion 

这仍然有效。考虑到这一点,分析区域时还有一个额外的问题:它们是琐事中的节点。因此,要获取相关节点,您可以使用SyntaxRewriter(并传递构造函数“true”以启用trivia分析),或使用node.DescendantNodes(descendIntoTrivia:true)查找后代节点。

由于区域的开始和结束可能位于文件中的任何位置,因此应始终从语法树的根部开始分析以确保能够找到区域的结尾/开始。

为了找到该区域,您可以重写VisitRegionDirectiveTrivia以及VisitEndRegionDirectiveTrivia。由于RegionTrivia的开始和结束彼此不了解,所以您需要自己匹配。在下面的例子中,我简单地计算了我已经通过了多少个区域,并且记录了当走出一个区域时应该删除的#endregion位置的列表。

为了确定相关区域,我提供了两种方法:您可以使用区域的名称,也可以确定附加的节点是否是ClassDeclaration。

这两种方法都没有考虑类声明之前的属性声明等情况。如果你想处理这个问题,你需要看看兄弟节点,并检查它们中的任何一个是否在该区域的范围内开始。

private class RegionSyntaxRewriter : CSharpSyntaxRewriter 
{ 
    int currentPosition = 0; 
    private List<int> EndRegionsForDeletion = new List<int>(); 
    private string deletedRegion; 
    private bool useRegionNameForAnalysis = false; 

    public RegionSyntaxRewriter(string deletedRegion) : base(true) 
    { 
     this.deletedRegion = deletedRegion; 
    } 

    public override SyntaxNode VisitRegionDirectiveTrivia(
      RegionDirectiveTriviaSyntax node) 
    { 
     currentPosition++; 
     var regionText = node.ToFullString().Substring(8).Trim(); 
     if (!useRegionNameForAnalysis && 
      node.ParentTrivia.Token.Parent is ClassDeclarationSyntax) 
     { 
      EndRegionsForDeletion.Add(currentPosition); 
      return SyntaxFactory.SkippedTokensTrivia(); 
     } 
     if (useRegionNameForAnalysis && 
      regionText == deletedRegion) 
     { 
      EndRegionsForDeletion.Add(currentPosition); 
      return SyntaxFactory.SkippedTokensTrivia(); 
     } 

     return base.VisitRegionDirectiveTrivia(node); 
    } 

    public override SyntaxNode VisitEndRegionDirectiveTrivia(
      EndRegionDirectiveTriviaSyntax node) 
    { 
     var oldPosition = currentPosition; 
     currentPosition--; 
     if (EndRegionsForDeletion.Contains(oldPosition)) 
     { 
      EndRegionsForDeletion.Remove(currentPosition); 
      return SyntaxFactory.SkippedTokensTrivia(); 
     } 

     return base.VisitEndRegionDirectiveTrivia(node); 
    } 
}