Как создать новый корень, добавив и удалив узлы, извлеченные из старого корня?

Я создаю Code Fix, который меняет это:

if(obj is MyClass)
{
    var castedObj = obj as MyClass;
}

в это:

var castedObj = obj as MyClass;
if(castedObj != null)
{
}

Это означает, что я должен сделать 3 вещи:

  • Измените условие в инструкции if.
  • Переместите кастинг прямо над оператором if.
  • Удалить оператор в теле.

До сих пор все мои попытки застряли в том, что я получаю не более двух таких вещей.

Я считаю, что эта проблема возникает, потому что у вас в основном есть 2 синтаксических узла на одном уровне. Таким образом, изменение одного из них делает недействительным местоположение другого. Или что-то типа того. Короче говоря: я либо копирую назначение переменной вне оператора if, либо мне удается изменить условие + удалить назначение переменной. Никогда не 3.

Как я могу это решить?

Для хорошей меры, вот мой код, который изменяет условие и удаляет назначение:

var newIfStatement = ifStatement.RemoveNode(
                                   variableDeclaration,
                                   SyntaxRemoveOptions.KeepExteriorTrivia);
newIfStatement = newIfStatement.ReplaceNode(newIfStatement.Condition, newCondition);

var ifParent = ifStatement.Parent;
var newParent = ifParent.ReplaceNode(ifStatement, newIfStatement);
newParent = newParent.InsertNodesBefore(
                           newIfStatement, 
                           new[] { variableDeclaration })
                           .WithAdditionalAnnotations(Formatter.Annotation);

var newRoot = root.ReplaceNode(ifParent, newParent);

Ответ 1

Вы просмотрели класс DocumentEditor? Это очень полезно при работе с модификацией синтаксиса, особенно когда изменения, которые применяются к дереву, могут вызвать проблемы с недействительностью. Операции почти такие же, как те, которые вы уже определили, просто используйте методы DocumentEditor и посмотрите, поможет ли это. Я не могу проверить, решает ли это ваш проблемный банкомат, но я думаю, что он решил подобную проблему для меня однажды в прошлом. Я проверю его позже, если смогу.

Что-то вроде этого сделает это:

var editor = await DocumentEditor.CreateAsync(document);
editor.RemoveNode(variableDeclaration);
editor.ReplaceNode(ifStatement.Condition, newCondition);
editor.InsertBefore(ifStatement, 
     new[] { variableDeclaration.WithAdditionalAnnotations(Formatter.Annotation) });

var newDocument = editor.GetChangedDocument();

Ответ 2

Мне удалось сделать что-то очень похожее следующим образом. Я извлекаю условие while и перемещаю его до времени и заменяю условие новым node. В то же время я добавляю новое утверждение. В вашем случае вместо добавления инструкции вы удалите требуемое утверждение из тела.

Начните с

Refactor(BlockSyntax oldBody)

ШАГ 1: я сначала посещаю и отмечаю узлы, которые я хочу изменить, и в то же время генерирую новые узлы, но пока не добавляю новые.

ШАГ 2: Отслеживайте отмеченные узлы и заменяйте их новыми.

class WhileConditionRefactoringVisitor : CSharpSyntaxRewriter
{
    private static int CONDITION_COUNTER = 0;
    private static string CONDITION_VAR = "whileCondition_";

    private static string ConditionIdentifier
    {
        get { return CONDITION_VAR + CONDITION_COUNTER++; }
    }

    private readonly List<SyntaxNode> markedNodes = new List<SyntaxNode>();

    private readonly List<Tuple<ExpressionSyntax, IdentifierNameSyntax, StatementSyntax, WhileStatementSyntax>> replacementNodes =
        new List<Tuple<ExpressionSyntax, IdentifierNameSyntax, StatementSyntax, WhileStatementSyntax>>();


        //STEP 1
        public override SyntaxNode VisitWhileStatement(WhileStatementSyntax node)
    {
        var nodeVisited = (WhileStatementSyntax) base.VisitWhileStatement(node);

        var condition = nodeVisited.Condition;
        if (condition.Kind() == SyntaxKind.IdentifierName)
            return nodeVisited;


        string conditionVarIdentifier = ConditionIdentifier;
        var newConditionVar = SyntaxFactoryExtensions.GenerateLocalVariableDeclaration(conditionVarIdentifier,
            condition, SyntaxKind.BoolKeyword).NormalizeWhitespace().WithTriviaFrom(nodeVisited);
        var newCondition = SyntaxFactory.IdentifierName(conditionVarIdentifier).WithTriviaFrom(condition);

        markedNodes.Add(condition);
        markedNodes.Add(node);
        replacementNodes.Add(new Tuple<ExpressionSyntax, IdentifierNameSyntax, StatementSyntax, WhileStatementSyntax>(condition, newCondition, newConditionVar, node));

        return nodeVisited;
    }

        //STEP 2
         private BlockSyntax ReplaceNodes(BlockSyntax oldBody)
    {
        oldBody = oldBody.TrackNodes(this.markedNodes);
        foreach (var tuple in this.replacementNodes)
        {
            var currentA = oldBody.GetCurrentNode(tuple.Item1);
            if (currentA != null)
            {
                var whileStatement = currentA.Parent;
                oldBody = oldBody.InsertNodesBefore(whileStatement, new List<SyntaxNode>() { tuple.Item3 });
                var currentB = oldBody.GetCurrentNode(tuple.Item1);
                oldBody = oldBody.ReplaceNode(currentB, tuple.Item2);
                var currentWhile = oldBody.GetCurrentNode(tuple.Item4);
                //modify body
                var whileBody = currentWhile.Statement as BlockSyntax;
                //create new statement
                var localCondition = tuple.Item3 as LocalDeclarationStatementSyntax;
                var initializer = localCondition.Declaration.Variables.First();
                var assignment = SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
                    SyntaxFactory.IdentifierName(initializer.Identifier), initializer.Initializer.Value));
                var newStatements = whileBody.Statements.Add(assignment);
                whileBody = whileBody.WithStatements(newStatements);
                //updateWhile
                var newWhile = currentWhile.WithStatement(whileBody);
                oldBody = oldBody.ReplaceNode(currentWhile, newWhile);
            }
        }
        return oldBody;
    }

        public BlockSyntax Refactor(BlockSyntax oldBody)
        {
            markedNodes.Clear();
            replacementNodes.Clear();
            //STEP 1
            oldBody = (BlockSyntax)this.Visit(oldBody);
            //STEP 2
            oldBody = this.ReplaceNodes(oldBody);

            return oldBody;
        }

    }