SyntaxRewriter и несколько операторов - PullRequest
6 голосов
/ 19 марта 2012

Я столкнулся с непростой ситуацией при использовании SyntaxRewriter в Roslyn. Я хотел бы переписать некоторые виды операторов, в том числе объявления локальных переменных. Решение требует от меня преобразования рассматриваемых утверждений в несколько утверждений, как в следующем тривиальном примере:

void method()
{
    int i;
}

становится

void method()
{
    int i;
    Console.WriteLine("I declared a variable.");
}

Я видел другие примеры, где блоки используются для достижения чего-то похожего, но, конечно, в случае объявления переменной, область объявления будет затронута. Я придумал следующее решение, но я отказываюсь от него. Это кажется слишком сложным и требует перерыва в шаблоне посетителей:

class Rewriter: SyntaxRewriter
{
    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list)
    {
        if (typeof(TNode) == typeof(StatementSyntax))
            return Syntax.List<TNode>(list.SelectMany(st => RewriteStatementInList(st as StatementSyntax).Cast<TNode>()));
        else
            return base.VisitList<TNode>(list);
    }

    private IEnumerable<SyntaxNode> RewriteStatementInList(StatementSyntax node)
    {
        if (node is LocalDeclarationStatementSyntax)
            return PerformRewrite((LocalDeclarationStatementSyntax)node);
        //else if other cases (non-visitor) 

        return Visit(node).AsSingleEnumerableOf();
    }

    private IEnumerable<SyntaxNode> PerformRewrite(LocalDeclarationStatementSyntax orig)
    {
        yield return orig;
        yield return Syntax.ParseStatement("Console.WriteLine(\"I declared a variable.\");");
    }
}

Чего мне не хватает? Редактирование операторов и их удаление (через пустые операторы) кажутся более простыми, чем переписывание в кратные.

Мой взгляд на ответ:

class Rewriter : SyntaxRewriter
{
    readonly ListVisitor _visitor = new ListVisitor();

    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list)
    {
        var result = Syntax.List(list.SelectMany(_visitor.Visit).Cast<TNode>());
        return base.VisitList(result);
    }

    private class ListVisitor : SyntaxVisitor<IEnumerable<SyntaxNode>>
    {
        protected override IEnumerable<SyntaxNode> DefaultVisit(SyntaxNode node)
        {
            yield return node;
        }

        protected override IEnumerable<SyntaxNode> VisitLocalDeclarationStatement(
             LocalDeclarationStatementSyntax node)
        {
            yield return node;
            yield return Syntax.ParseStatement("Console.WriteLine(\"I declared a variable.\");");
        }
    }
}

Ответы [ 3 ]

3 голосов
/ 20 марта 2012

Я думаю, что есть простой способ сделать ваш Rewriter более похожим на посетителя: используйте другого посетителя для обработки узлов в списке:

class Rewriter: SyntaxRewriter
{
    readonly Visitor m_visitor = new Visitor();

    public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list)
    {
        var result = Syntax.List(list.SelectMany(m_visitor.Visit).Cast<TNode>());
        return base.VisitList(result);
    }
}

class Visitor : SyntaxVisitor<IEnumerable<SyntaxNode>>
{
    protected override IEnumerable<SyntaxNode> DefaultVisit(SyntaxNode node)
    {
        return new[] { node };
    }

    protected override IEnumerable<SyntaxNode> VisitLocalDeclarationStatement(
         LocalDeclarationStatementSyntax node)
    {
        return new SyntaxNode[]
               {
                   node,
                   Syntax.ParseStatement(
                       "Console.WriteLine(\"I declared a variable.\");")
               };
    }
}

Обратите внимание, что это небезопасно и приведет кInvalidCastException, если вы возвращаете коллекцию, содержащую объект, который не является TNode из Visitor.

2 голосов
/ 20 марта 2012

Я не знаю радикально лучшего способа справиться с этим.Еще один подход, который немного более «похож на посетителя», заключается в использовании VisitLocalDeclaration для аннотирования узлов, которые вы хотите заменить, чем-то вроде: return (base.Visit(node).WithAdditionalAnnoations(myAnnotation);.Затем в VisitList вы можете просто найти дочерние узлы, к которым относится ваша аннотация, и выполнить перезапись в этой точке.

0 голосов
/ 28 июня 2016

Я просматривал исходный код Roslyn, чтобы увидеть, как команда Roslyn сама делает это. Вот пример: http://source.roslyn.codeplex.com/Microsoft.CodeAnalysis.CSharp.Features/R/bcd389b836bf7b4c.html

Короче говоря, я думаю, что это выглядит примерно так. (этот переписчик просто удаляет StatementExpressions, но вы можете видеть, что он построен из метода итератора, поэтому также легко добавлять методы).

class TreeRewriter : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitBlock(BlockSyntax node)
        => node.WithStatements(VisitList(SyntaxFactory.List(ReplaceStatements(node.Statements))));

    public override SyntaxNode VisitSwitchSection(SwitchSectionSyntax node)
        => node.WithStatements(VisitList(SyntaxFactory.List(ReplaceStatements(node.Statements))));

    IEnumerable<StatementSyntax> ReplaceStatements(IEnumerable<StatementSyntax> statements)
    {
        foreach (var statement in statements)
        {
            if (statement is ExpressionStatementSyntax) continue;
            yield return statement;
        }
    }
}

Вот как я управляю этим кодом:

var rewriter = new TreeRewriter();
var syntaxTree = await document.GetSyntaxTreeAsync();
var root = await syntaxTree.GetRootAsync();
var newRoot = rewriter.Visit(root);
var newDocument = document.WithSyntaxRoot(newRoot);
...