2012-03-19 37 views
5

我在Roslyn中遇到了使用SyntaxRewriter的棘手情况。我想重写某些类型的语句,包括局部变量声明。该解决方案要求我改变的语句有问题成多个语句,如下面的小例子: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.\");"); 
     } 
    } 
} 

回答

3

,我认为有一个简单的方法,使您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如果您返回一个集合,其中包含的对象不是TNodeVisitor

+0

我正在考虑这样做。唯一不利的一面是IEnumerable只会在调用VisitList的时候才有意义。我认为一个建筑师/作家会是一个令人信服的答案,但我认为我过于沉迷于此:)我会在晚些时候对你们大加赞赏,显然我还没有足够的影响力。 – 2012-03-20 18:38:09

+0

为什么只有在调用'VisitList()'时IEnumerable才有意义?无论何时用一系列节点替换一个节点是有意义的,我想这个节点应该是某个列表的一部分。 – svick 2012-03-20 19:43:21

+0

其实,我没有注意到你只是直接调用'Visitor',只能从'VisitList'调用。我非常喜欢这个解决方案。我将把它放在我的问题中,用'yield return'来生成迭代器并嵌套列表访问者类。 – 2012-03-20 20:21:37

2

我不知道的根本更好的方式来处理这个问题。另一种稍微更像“访客喜欢”的方法是使用VisitLocalDeclaration来标注要替换的节点,如return (base.Visit(node).WithAdditionalAnnoations(myAnnotation);。然后在VisitList中,您可以找到您的注释的子节点,并在那一刻进行重写。

+0

谢谢,凯文。这是我没有看到的。因此,如果我采取这种方法,那么在使用VisitList重写时,仍然需要针对每个节点类型提出任何特殊处理,对吗? – 2012-03-20 01:59:12

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);