简体   繁体   English

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

[英]How do I remove regions from code programatically using Roslyn?

I am parsing C# code from text using Roslyn. 我正在使用Roslyn从文本中解析C#代码。 Some of the code has regions surrounding multiple classes. 一些代码具有围绕多个类的区域。 Example: 例:

#region Classes
public class MyClass
{
}

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

I'd like to remove the region surrounding the classes ("Classes" in the above example) but leave the inner regions intact like the one named "Methods" in the example above. 我想删除类周围的区域(上例中的“类”),但保留内部区域,就像上面示例中名为“Methods”的区域一样。 How can I go about doing this? 我该怎么做呢?

Regions are rather peculiar, as they do not follow the usual tree structure. 区域相当特殊,因为它们不遵循通常的树形结构。 For instance you can create a construct such as this: 例如,您可以创建一个这样的构造:

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

This will still be valid. 这仍然有效。 With that in mind there is an additional problem when analyzing regions: They are nodes inside of trivia. 考虑到这一点,在分析区域时还有一个问题:它们是琐事中的节点。 So to obtain the relevant nodes you can either use a SyntaxRewriter (and pass the constructor "true" to enable trivia analysis) or find the descendant nodes using node.DescendantNodes(descendIntoTrivia: true). 因此,要获取相关节点,您可以使用SyntaxRewriter(并传递构造函数“true”以启用琐事分析)或使用node.DescendantNodes(descendIntoTrivia:true)查找后代节点。

As the regions start and end could be anywhere in the file you should always start analysis at the root of the syntax tree to ensure that you will find the end / start of a region. 由于区域的开始和结束可能位于文件中的任何位置,因此您应始终在语法树的根目录开始分析,以确保您可以找到区域的结束/开始。

In order to find the region you can override the VisitRegionDirectiveTrivia as well as the VisitEndRegionDirectiveTrivia. 为了找到该区域,您可以覆盖VisitRegionDirectiveTrivia以及VisitEndRegionDirectiveTrivia。 Since the start and end of the RegionTrivia do not know each other you will need to match them yourself. 由于RegionTrivia的开始和结束彼此不了解,您需要自己匹配它们。 In the example below I simple counted how many regions I already passed and and noted a list of #endregion positions which should be deleted when stepping out of a region. 在下面的例子中,我简单地计算了我已经传递了多少个区域,并注意了一个#endregion位置列表,这些位置在走出区域时应该被删除。

To identify the relevant regions I provided two approaches: You can either use the name of the region or identify whether the attached Node is a ClassDeclaration. 为了识别相关区域,我提供了两种方法:您可以使用区域名称或标识附加的节点是否为ClassDeclaration。

Neither approach considers cases such as a property declaration before the class declaration. 这两种方法都没有在类声明之前考虑诸如属性声明之类的情况。 In case you want to handle this as well you need to take a look at the sibling nodes and check whether any of them starts within the confines of the region. 如果你想要处理这个问题,你需要看看兄弟节点,并检查它们是否在该区域的范围内开始。

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

As Sievajet suggested, you can use CSharpSyntaxRewriter to remove Region attached to particular node (in your case : ClassDeclarationSyntax ). 正如Sievajet建议的那样,您可以使用CSharpSyntaxRewriter删除附加到特定节点的Region (在您的情况下: ClassDeclarationSyntax )。

Here is code to get you started : 这是让你入门的代码:

 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());
        }
    }

Your Output should look like : 您的输出应如下所示:

        public class MyClass
        {
        }

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

PS : This is not complete solution. PS:这不是完整的解决方案。 I agree with mjwills and you should show some progress before posting question. 我同意mjwills ,你应该在发布问题之前先显示一些进展。

PS : Code is inspired from JoshVarty 's EmptyStatementRemoval PS:代码的灵感来自于JoshVartyEmptyStatementRemoval

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM