SyntaxRewriter and several operators

I faced a difficult situation using SyntaxRewriter in Roslyn. I would like to rewrite certain types of statements, including local variable declarations. The solution requires me to convert the statements in question into several operators, as in the following trivial example:

void method() { int i; } 

becomes

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

I saw other examples where blocks are used to do 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 do not agree with this. This seems overly complex and requires a break in the visitor's template:

 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 deleting them (via an empty operator) seems more straightforward than rewriting for brevity.

I answer 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.\");"); } } } 
+6
source share
3 answers

I think there is an easy way to make your Rewriter look more like a visitor: 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 unsafe and will throw an InvalidCastException if you return a collection containing the TNode object from Visitor .

+3
source

I don't know a radically better way to handle this. Another approach that the visitor likes a bit more is to use VisitLocalDeclaration to comment on the nodes you want to replace, for example: return (base.Visit(node).WithAdditionalAnnoations(myAnnotation); Then in VisitList you can just find the children nodes that your annotation and rewrite at this point.

+2
source

I looked at the source code of Roslyn to see how the Roslyn team does it. Here is an example: http://source.roslyn.codeplex.com/Microsoft.CodeAnalysis.CSharp.Features/R/bcd389b836bf7b4c.html

In a nutshell, I think it looks something like this. (This rewriting process simply removes StatementExpressions, but you can see that it is built from an iterator method, so it's easy to add methods too).

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

This is how I manage this code:

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

Source: https://habr.com/ru/post/911131/


All Articles