9

我在 Roslyn 中使用 SyntaxRewriter 遇到了一个棘手的情况。我想重写某些类型的语句,包括局部变量声明。该解决方案要求我将相关语句转换为多个语句,如下面的简单示例所示:

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.\");");
        }
    }
}
4

3 回答 3

6

我认为有一种简单的方法可以让您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.

于 2012-03-20T10:16:09.700 回答
2

我不知道有更好的方法来处理这个问题。另一种稍微更像“访问者”的方法是使用VisitLocalDeclaration注释来替换要替换为以下内容的节点:return (base.Visit(node).WithAdditionalAnnoations(myAnnotation);. 然后在 VisitList 中,您只需找到您的注释的子节点并在此时进行重写。

于 2012-03-19T20:01:46.810 回答
0

我正在浏览 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);
于 2016-06-28T18:30:20.103 回答