I've written a CSharpSyntaxRewriter
that I am using to remove attributes from methods, but I am struggling to keep anything from before the attribute (upto the previous method) when I remove all attributes from a method.
This works perfectly for methods with more than one attribute, but not for just one.
Here's a minimal repro:
void Main()
{
var code = @"namespace P
{
class Program
{
public void NoAttributes() { }
//???
[TestCategory(""Atomic"")]
public void OneAtt1() { }
[TestCategory(""Atomic"")]
public void OneAtt2() { }
[TestMethod, TestCategory(""Atomic"")]
public void TwoAtts() { }
}
}";
var tree = CSharpSyntaxTree.ParseText(code);
var rewriter = new AttributeRemoverRewriter();
var rewrittenRoot = rewriter.Visit(tree.GetRoot());
Console.WriteLine(rewrittenRoot.GetText().ToString());
}
public class AttributeRemoverRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode VisitAttributeList(AttributeListSyntax attributeList)
{
var nodesToRemove =
attributeList
.Attributes
.Where(att => (att.Name as IdentifierNameSyntax).Identifier.Text.StartsWith("TestCategory"))
.ToArray();
if (nodesToRemove.Length == attributeList.Attributes.Count)
{
//Remove the entire attribute
return
attributeList
.RemoveNode(attributeList, SyntaxRemoveOptions.KeepNoTrivia);
}
else
{
//Remove just the matching ones recursively
foreach (var node in nodesToRemove)
return
VisitAttributeList(attributeList.RemoveNode(node, SyntaxRemoveOptions.KeepNoTrivia));
}
return
base.VisitAttributeList(attributeList);
}
}
Full version is here on my gist (before anyone points out any other issues).
Expected output is:
namespace P
{
class Program
{
public void NoAttributes() { }
//???
public void OneAtt1() { }
public void OneAtt2() { }
[TestMethod]
public void TwoAtts() { }
}
}
Actual output is:
namespace P
{
class Program
{
public void NoAttributes() { }
public void OneAtt1() { }
public void OneAtt2() { }
[TestMethod]
public void TwoAtts() { }
}
}
Any ideas on what I need to do to keep the whitespace (or even comments!!)?
I've messed around with every combination of Trivia
I can think of. Changing the SyntaxRemoveOptions
results in NullReferenceException
s inside the Roslyn codebase and using the *Trivia
extension methods results in the attributes not been removed anymore - just whitespace.
Like I said in the comments, it looks like a bug in Roslyn to me. You can report it and use the following workaround if you'd like.
I just tried rewriting at the method level instead of at the attribute level. (You could use a similar approach on properties)
public class AttributeRemoverRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
{
var newAttributes = new SyntaxList<AttributeListSyntax>();
foreach (var attributeList in node.AttributeLists)
{
var nodesToRemove =
attributeList
.Attributes
.Where(att => (att.Name as IdentifierNameSyntax).Identifier.Text.StartsWith("TestCategory"))
.ToArray();
if (nodesToRemove.Length == attributeList.Attributes.Count)
{
//Do not add the attribute to the list. It's being removed completely.
}
else
{
//We want to remove only some of the attributes
var newAttribute = (AttributeListSyntax)VisitAttributeList(attributeList.RemoveNodes(nodesToRemove, SyntaxRemoveOptions.KeepNoTrivia));
newAttributes = newAttributes.Add(newAttribute);
}
}
//Get the leading trivia (the newlines and comments)
var leadTriv = node.GetLeadingTrivia();
node = node.WithAttributeLists(newAttributes);
//Append the leading trivia to the method
node = node.WithLeadingTrivia(leadTriv);
return node;
}
}
Here's the output I get. You could further filter leadTriv
if you'd like to remove comments.
Note that this doesn't cover certain... perverse situations:
[TestCategory(""Atomic"")]
#if Debug
#endif
/*Trivia in unfortunate places*/
[TestCategory("test")]
public void OneAtt2() { }
You'll lose the trivia between the attributes. Trivia is one of the hardest things to get right when building rewriters.
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