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

时间:2017-12-18 11:14:08

标签: c# roslyn

我正在使用Roslyn从文本中解析C#代码。一些代码具有围绕多个类的区域。例如:

#region Classes
public class MyClass
{
}

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

我想删除类周围的区域(上例中的“类”),但保留内部区域,就像上面示例中名为“Methods”的区域一样。我怎么能这样做呢?

2 个答案:

答案 0 :(得分:3)

区域相当奇特,因为它们不遵循通常的树形结构。例如,您可以创建一个这样的构造:

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

这仍然有效。考虑到这一点,在分析区域时还有一个问题:它们是琐事中的节点。因此,要获取相关节点,您可以使用SyntaxRewriter(并传递构造函数" true"启用琐事分析)或使用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);
    }
}

答案 1 :(得分:2)

根据Sievajet建议,您可以使用CSharpSyntaxRewriter删除附加到特定节点的Region(在您的情况下为ClassDeclarationSyntax)。

以下是帮助您入门的代码:

 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

P.S。 :这不是完整的解决方案。我同意mjwills,您应该在发布问题之前先显示一些进展。

P.S。 :代码的灵感来自JoshVartyEmptyStatementRemoval