Removing #region and #endregion with SyntaxRewriter
-
9 ianuarie 2012 13:32
I'm trying to use a custom SyntaxRewriter to get rid of all the #region and #endregion in code. However, I can't figure out the magic return value from my VisitXYZ() methods to not cause an exception to occur. For example, doing this:
protected override SyntaxNode VisitRegionDirective(
RegionDirectiveSyntax node)
{
return null;
}Gives me a NullReferenceException - here's the stack trace:
Roslyn.Compilers.CSharp.Syntax.Trivia(StructuredTriviaSyntax node)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitTrivia(SyntaxTrivia trivia)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitListElement(SyntaxTrivia element)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitList(SyntaxTriviaList list)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitToken(SyntaxToken token)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitList(SyntaxTokenList list)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitClassDeclaration(ClassDeclarationSyntax node)
Roslyn.Compilers.CSharp.ClassDeclarationSyntax.Accept[TResult](SyntaxVisitor`1 visitor)
Roslyn.Compilers.CSharp.SyntaxVisitor`1.Visit(SyntaxNode node)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitList[TNode](SyntaxList`1 list)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitCompilationUnit(CompilationUnitSyntax node)
Roslyn.Compilers.CSharp.CompilationUnitSyntax.Accept[TResult](SyntaxVisitor`1 visitor)Trying to use EmptyStatement():
protected override SyntaxNode VisitRegionDirective(
RegionDirectiveSyntax node)
{
return Syntax.EmptyStatement();
}Doesn't make things better - I now get an InvalidCastException:
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitTrivia(SyntaxTrivia trivia)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitListElement(SyntaxTrivia element)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitList(SyntaxTriviaList list)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitToken(SyntaxToken token)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitList(SyntaxTokenList list)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitClassDeclaration(ClassDeclarationSyntax node)
Roslyn.Compilers.CSharp.ClassDeclarationSyntax.Accept[TResult](SyntaxVisitor`1 visitor)
Roslyn.Compilers.CSharp.SyntaxVisitor`1.Visit(SyntaxNode node)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitList[TNode](SyntaxList`1 list)
Roslyn.Compilers.CSharp.SyntaxRewriter.VisitCompilationUnit(CompilationUnitSyntax node)
Roslyn.Compilers.CSharp.CompilationUnitSyntax.Accept[TResult](SyntaxVisitor`1 visitor)
Roslyn.Compilers.CSharp.SyntaxVisitor`1.Visit(SyntaxNode node)
Any ideas on how I can delete/remove the target nodes from the tree?Thanks,
Jason Bock
Toate mesajele
-
9 ianuarie 2012 15:13
After looking at the decompiled sources, it seem, both VisitRegionDirective and VisitEndRegionDirective have to return StructuredTriviaSyntax. What worked for me was to return an empty SkippedTokensSyntax, that is normally used to help parsing invalid code. But it certainly is a hack and you should probably file a bug about this at Connect.
class RemoveRegionRewriter : SyntaxRewriter { public RemoveRegionRewriter() : base(true) {} protected override SyntaxNode VisitRegionDirective(RegionDirectiveSyntax node) { return Syntax.SkippedTokens(); } protected override SyntaxNode VisitEndRegionDirective(EndRegionDirectiveSyntax node) { return Syntax.SkippedTokens(); } }
Another option is to use the ReplaceTrivia extension method:
var trivia = syntax.DescendentTrivia() .Where( t => t.HasStructure && (t.GetStructure() is RegionDirectiveSyntax || t.GetStructure() is EndRegionDirectiveSyntax)); var result = syntax.ReplaceTrivia(trivia, (t, t2) => Syntax.TriviaList());
In both cases, doing the replacement messes up whitespace.
- Editat de svick 9 ianuarie 2012 15:14
-
9 ianuarie 2012 15:38Thanks for the response. Yeah, that feels a little hackish :). I tried creating a custom StructuredTriviaSyntax but I quickly found out that trying to create a class that inherits from that type is just not possible (due to the accessibility of methods you have to implement).
-
9 ianuarie 2012 16:03I just tried using the SkippedTokens() idea, and yep, it works, but it messes with the spacing/trivia. Would be nice if I could just return null or something "empty" so it just disappears :)
-
9 ianuarie 2012 20:39Proprietar
RegionDirectiveSyntax and EndRegionDirectiveSyntax nodes actually appear under 'structured' trivia. You can read more about structured trivia on this thread.
I think the reason the above code throws when you return null for RegionDirectiveSyntax or EndRegionDirectiveSyntax is because you are basically causing the parent structured trivia to have no 'structure'. In other words, the code is probably throwing because it violates the invariant that one shouldn't be able to generate a tree containing a structured trivia that has no child node (i.e. no structure)...
I think the way to do what you are trying would be to remove the parent strucutred trivia from the tree instead (as shown in the below example).
static void Main()
{
var tree = SyntaxTree.ParseCompilationUnit(@"
using System;
namespace HelloWorld
{
#region Program
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
#endregion
#region Other
#endregion
}");
var rewriter = new RemoveRegionRewriter();
var newRoot = rewriter.Visit(tree.Root);
Console.WriteLine(newRoot);
}
class RemoveRegionRewriter : SyntaxRewriter
{
public RemoveRegionRewriter()
: base(visitIntoStructuredTrivia: true)
{
}
protected override SyntaxTrivia VisitTrivia(SyntaxTrivia trivia)
{
var retVal = default(SyntaxTrivia);
var isRegionOrEndRegionStructuredTrivia = trivia.HasStructure &&
(trivia.Kind == SyntaxKind.RegionDirective ||
trivia.Kind == SyntaxKind.EndRegionDirective);
if (isRegionOrEndRegionStructuredTrivia)
{
// Simply return default(SyntaxTrivia)
}
else
{
retVal = base.VisitTrivia(trivia);
}
return retVal;
}
}Hope this helps!
Shyam Namboodiripad | Software Development Engineer in Test | Roslyn Compilers Team- Propus ca răspuns de billchi_msOwner 16 ianuarie 2012 22:00
-
9 ianuarie 2012 21:06It does work, but as svick mentioned in a previous response, the formatting gets a little messed up. I think I've found a way to handle that as well...
-
9 ianuarie 2012 21:34Proprietar
Also, I think it is still a bug that the API throws a NullReferenceException with message "Object reference not set to an instance of an object" when you return null from VisitRegionDirective / VisitEndRegionDirective :) This makes the behavior confusing. The exception should be more specific (e.g. ArgumentNullException) and the exception message should identify exactly which argument was null. Additionally, it would also be nice if the message could identify why it was an error for the argument to be null...
I can open an internal bug on your behalf for this or if you want to track this, you can open a bug directly on Connect. Thanks for reporting this!!
Shyam Namboodiripad | Software Development Engineer in Test | Roslyn Compilers Team -
10 ianuarie 2012 13:36
Feel free to open a bug on it. I agree, this behaviour should be implemented better (as you described).
Regards,
Jason