Сохраняйте структурированные мелочи при удалении узла

Есть ли простой способ удалить SyntaxNode (то есть метод) из дерева, но сохранив при этом структурированные мелочи?

В следующем коде я хочу удалить MethodA:

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

  }
  #endregion 
} 

Я использую CSharpSyntaxRewriter для перезаписи SyntaxTree. В методе VisitMethodDeclaration я просто возвращаю null для MethodA. Проблема с этим подходом заключается в том, что StructuredTrivia тега #region также удаляется. Это результат результата:

public class Sample 
{ 
  #endregion 
}

В моем CSharpSyntaxRewriter:

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

EDIT: как упоминалось в одном из ответов ниже, я мог бы использовать SyntaxNode.RemoveNodes с параметром SyntaxRemoveOptions.KeepDirectives. У этого решения есть два больших недостатка:

  1. Мне нужно знать, какие типы SyntaxNode могут содержать этот дочерний тип. Например, метод может быть объявлен в структуре, классе, интерфейсе и т. д. Это означает, что мне нужно выполнять фильтрацию в нескольких местах.
  2. Я теряю возможность строить синтаксическое дерево снизу вверх. Это создает проблемы при сравнении объектов SyntaxTree. Все последующие вызовы методов посетителя уже видят новые узлы, созданные в методе «RemoveNodes». С помощью метода SyntaxNode.RemoveNodes можно указать SyntaxRemoveOptions.KeepDirectives, но возможно ли это также с помощью CSharpSyntaxRewriter?

EDIT2: вот код того, что я пытаюсь сделать: https://dotnetfiddle.net/1Cg6UZ


person TWT    schedule 05.08.2016    source источник
comment
Относительно Но когда я это делаю: что именно вы делаете? Можете ли вы добавить (минимально полный) пример кода, показывающий, как вы пытаетесь удалить SyntaxNode из этого ввода?   -  person stakx - no longer contributing    schedule 05.08.2016
comment
Я использую CSharpSyntaxRewriter для перезаписи SyntaxTree. В методе VisitMethodDeclaration я просто возвращаю null для MethodA. общественное переопределение SyntaxNode VisitMethodDeclaration (MethodDeclarationSyntax node) { if (...) return null; иначе вернуть узел; }   -  person TWT    schedule 05.08.2016
comment
Какие аргументы вы передаете RemoveNode()?   -  person Jeroen Vannevel    schedule 05.08.2016
comment
Я использую не RemoveNodes, а CSharpSyntaxRewriter. Смотрите мой обновленный вопрос.   -  person TWT    schedule 08.08.2016


Ответы (2)


При удалении узла вы фактически удаляете вместе с ним мелочи. Чтобы сохранить мелочи, вам нужно изменить ClassDeclarationSyntax вместо MethodDeclaration.

При посещении ClassDeclarationSyntax вы можете изменить класс, удалив соответствующие узлы и используя SyntaxRemoveOptions.KeepTrailingTrivia | SyntaxRemoveOptions.KeepLeadingTrivia вы сохраняете комментарии и операторы региона до и после фактического определения метода.

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, поэтому его повторное использование для других частей вашего кода может быть затруднено.

person SJP    schedule 05.08.2016
comment
Если вы хотите сначала посетить дочерние узлы, вы, конечно, также можете сначала выполнить base.VisitClassDeclaration(node) и удалить узел метода только после этого. Как я могу определить узел, который я хочу удалить в этом случае? После вызова base.VisitClassDeclaration возвращается новое дерево. Мой CSharpSyntaxRewriter содержит HashSet со всеми SyntaxNodes из исходного дерева, которые необходимо сохранить. - person TWT; 10.08.2016
comment
Я расширил исходный вопрос примером кода того, что я пытаюсь сделать. - person TWT; 10.08.2016
comment
Просто поясню: в вашем примере вы хотите сохранить пространство имен, объявление первого класса (с регионом), вызов второго метода, но удалить второй класс со всем (методы и регион) содержащимся? - person SJP; 11.08.2016
comment
Это правильно. Но это только пример. По сути, переписчик должен удалить все символы (пространства имен, классы, поля, свойства, методы и т. д.), которых нет в хэш-наборе symbolsToKeep, и сохранить все директивы нетронутыми. Список symbolsToKeep строится в отдельном классе SyntaxVisitor на основе некоторых условий (не включенных в пример). - person TWT; 11.08.2016
comment
Не совсем уверен, решит ли это вашу проблему. Он удаляет указанные записи, сохраняя мелочи. Однако, если вы хотите удалить вызовы, вам потребуется расширить решение: dotnetfiddle.net/MHuTFm. - person SJP; 11.08.2016
comment
Большое спасибо! Это то, что я хочу. - person TWT; 11.08.2016

Я думаю, вы ищете флаг 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);
    }
}
person johnny 5    schedule 30.05.2017