I've encountered a tricky situation using the SyntaxRewriter in Roslyn. I'd like to rewrite certain kinds of statements, including local variable declarations. The solution requires me to transform the statements in question into multiple statements, as in the following trivial example:
void method()
{
int i;
}
becomes
void method()
{
int i;
Console.WriteLine("I declared a variable.");
}
I've seen other examples where blocks are used to accomplish something similar, but of course in the case of a variable declaration, the scope of the declaration will be affected. I came up with the following solution, but I'm balking at it. It seems over-complicated, and requires a break in the visitor pattern:
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.\");");
}
}
What am I missing? Editing statements and removing them (via empty statement) seem more straight forward than rewriting to multiples.
My take on the answer:
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.\");");
}
}
}
I think there is a simple way to make your Rewriter
more visitor-like: use another visitor to process the nodes in the list:
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.\");")
};
}
}
Note that this is not safe and will throw an InvalidCastException
if you return a collection that contains object that is not TNode
from the Visitor
.
I don't know of a radically better way to handle this. One other approach that is slightly more "visitor like" is to use VisitLocalDeclaration
to annotate nodes that you want to replace with something like: return (base.Visit(node).WithAdditionalAnnoations(myAnnotation);
. Then in VisitList, you can just find the child nodes that your annotation and do the rewrite at that point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With