【问题标题】:Keep structured trivia when removing a node删除节点时保持结构化琐事
【发布时间】:2016-08-05 09:24:21
【问题描述】:

有没有一种简单的方法可以从树中删除 SyntaxNode(即方法),但保留结构化的琐事?

在下面的代码中我要删除 MethodA:

public class Sample 
{ 
  #region SomeRegion 
  public void MethodA() 
  { 

  }
  #endregion 
} 

我使用 CSharpSyntaxRewriter 重写 SyntaxTree。在 VisitMethodDeclaration 方法中,我只为 MethodA 返回 null。这种方法的问题是#region 标签的 StructuredTrivia 也被删除了。这是结果结果:

public class Sample 
{ 
  #endregion 
}

在我的 CSharpSyntaxRewriter 中:

public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) 
{ 
   if (...) 
      return null; 
   else 
      return node; 
} 

编辑: 如以下答案之一所述,我可以将 SyntaxNode.RemoveNodes 与 SyntaxRemoveOptions.KeepDirectives 选项一起使用。该解决方案有两大缺点:

  1. 我需要知道哪些 SyntaxNode 类型可以包含这个子类型。例如,可以在结构、类、接口等中声明方法……这意味着我需要在多个位置进行过滤。
  2. 我失去了自下而上构建语法树的能力。这在比较 SyntaxTree 对象时会出现问题。对访问者方法的所有后续调用已经看到在“RemoveNodes”方法中创建的新节点。 使用 SyntaxNode.RemoveNodes 方法可以指定 SyntaxRemoveOptions.KeepDirectives,但使用 CSharpSyntaxRewriter 也可以吗?

EDIT2:这是我正在尝试做的一些代码:https://dotnetfiddle.net/1Cg6UZ

【问题讨论】:

  • 关于“但是当我这样做时”:你到底在做什么?您能否添加一个(至少完整的)代码示例,展示您如何尝试从该输入中删除 SyntaxNode
  • 我使用 CSharpSyntaxRewriter 重写 SyntaxTree。在 VisitMethodDeclaration 方法中,我只为 MethodA 返回 null。 public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) { if (...) return null;否则返回节点; }
  • 你传递给RemoveNode()的参数是什么?
  • 我使用的不是 RemoveNodes,而是 CSharpSyntaxRewriter。请参阅我更新的问题。

标签: c# roslyn roslyn-code-analysis


【解决方案1】:

删除节点时,您实际上是在删除琐事。要保留琐事,您需要修改 ClassDeclarationSyntax 而不是 MethodDeclaration。

访问 ClassDeclarationSyntax 时,您可以通过删除适当的节点来修改类 - 并使用 SyntaxRemoveOptions.KeepTrailingTrivia | SyntaxRemoveOptions.KeepLeadingTrivia 保留实际方法定义前后的 cmets 和 region 语句。

public class ClassDeclarationChanger : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        var methods = node.Members.OfType<MethodDeclarationSyntax>();
        if (methods.Any())
        {
            node = node.RemoveNodes(methods, SyntaxRemoveOptions.KeepTrailingTrivia | 
                    SyntaxRemoveOptions.KeepLeadingTrivia);
        }
        return base.VisitClassDeclaration(node);
    }
}

如果您希望先访问子节点,当然也可以先执行 base.VisitClassDeclaration(node),然后再删除方法节点。

另一种方法是返回另一个声明。但是,您不能简单地返回 EmptyStatement(因为这会导致异常),但您可以插入没有内容的新方法声明:

public class SampleChanger : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        // Generates a node containing only parenthesis 
        // with no identifier, no return type and no parameters
        var newNode = SyntaxFactory.MethodDeclaration(SyntaxFactory.IdentifierName(""), "");
        // Removes the parenthesis from the Parameter List 
        // and replaces them with MissingTokens
        newNode = newNode.ReplaceNode(newNode.ParameterList,
            newNode.ParameterList.WithOpenParenToken(
                SyntaxFactory.MissingToken(SyntaxKind.OpenParenToken)).
            WithCloseParenToken(SyntaxFactory.MissingToken(SyntaxKind.CloseParenToken)));
        // Returns the new method containing no content 
        // but the Leading and Trailing trivia of the previous node
        return newNode.WithLeadingTrivia(node.GetLeadingTrivia()).
            WithTrailingTrivia(node.GetTrailingTrivia());
    }
}

这种方法当然有缺点,它需要针对不同语法类型的特定 SyntaxNode,因此可能难以将其重用于代码的其他部分。

【讨论】:

  • "如果您希望先访问子节点,当然也可以先执行 base.VisitClassDeclaration(node),然后再删除方法节点。"在这种情况下,如何识别要删除的节点?在 base.VisitClassDeclaration 调用之后返回一个新树。我的 CSharpSyntaxRewriter 包含一个 HashSet,其中包含需要保留的原始树中的所有 SyntaxNode。
  • 我用我正在尝试做的代码示例扩展了原始问题。
  • 只是澄清一下-在您的示例中,您希望保留命名空间、第一个类声明(带区域)、第二个方法调用,但删除包含所有内容(方法和区域)的第二个类?
  • 没错。但这只是一个例子。基本上,重写器需要做的是,删除所有不在“symbolsToKeep”哈希集中的符号(命名空间、类、字段、属性、方法……),并保持所有指令不变。 symbolsToKeep 列表基于某些条件(未包含在示例中)构建在单独的 SyntaxVisitor 类中。
  • 不太确定这是否能解决您的问题。它确实删除了指定的条目,同时保留了琐事。但是,如果您希望删除调用,则需要扩展解决方案:dotnetfiddle.net/MHuTFm
【解决方案2】:

我认为您正在寻找标志 KeepExteriorTrivia 如果您检查枚举的源代码,您会看到他们有一个为此预编译的标志

[Flags]
public enum SyntaxRemoveOptions
{
  KeepNoTrivia = 0,
  KeepLeadingTrivia = 1,
  KeepTrailingTrivia = 2,
  KeepExteriorTrivia = KeepTrailingTrivia | KeepLeadingTrivia,
  KeepUnbalancedDirectives = 4,
  KeepDirectives = 8,
  KeepEndOfLine = 16,
  AddElasticMarker = 32,
}

您的函数调用现在看起来像这样:

public class ClassDeclarationChanger : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        var methods = node.Members.OfType<MethodDeclarationSyntax>();
        if (methods.Any())
        {
            node = node.RemoveNodes(methods, SyntaxRemoveOptions.KeepExteriorTrivia);
        }
        return base.VisitClassDeclaration(node);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 2013-03-16
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多