Skip to content

Instantly share code, notes, and snippets.

@xdaDaveShaw
Last active April 13, 2021 10:16
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save xdaDaveShaw/87643170e5fa97b7da3b to your computer and use it in GitHub Desktop.
Save xdaDaveShaw/87643170e5fa97b7da3b to your computer and use it in GitHub Desktop.
Roslyn Based Attribute Remover
using System;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis;
void Main()
{
var code = @"namespace P
{
using NSAlias = Test;
class Program
{
public void NoAttributes() { }
[TestCategory(""Atomic"")]
public void JustOneCategoryAttribute() { }
[TestMethod, TestCategory(""Atomic"")]
public void OnOneLine() { }
[TestMethod]
[TestCategory(""Atomic"")]
public void SeparateAttribute() { }
[TestCategory(""Atomic""), TestMethod]
public void WithCategoryBefore() { }
[TestMethod]
[TestCategoryAttribute(""Atomic"")]
public void FullAttributeName() { }
[TestMethod]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryBaseAttribute(""Atomic"")]
public void FullyQualifiedName() { }
//[TestMethod]
//[TestCategory(""Atomic"")]
//public void Commented() { }
[TestMethod]
[TestCategory(""Database"")]
public void ADifferentValue() { }
[TestMethod]
[TestCategory(""Atomic"")]
[TestCategory(""Atomic"")]
public void TwoAttributes() { }
[TestMethod]
[TestCategory(""Atomic""), TestCategory(""Atomic"")]
public void TwoAttributesOneLine() { }
[TestMethod, TestCategory(""Atomic""), TestCategory(""Atomic"")]
public void TwoAttributesOneLineAndOneThatDoesntMatch() { }
[NSAlias.TestCategory(""Atomic"")]
public void QualifiedAlias() { }
[NSAlias::TestCategory(""Atomic"")]
public void AliasQualifiedAlias() { }
[NSAlias::TestCategory(""Atomic"")]
public void NSAlias2() { }
[TestMethod,
TestCategory(""Atomic"")]
public void SomeoneLikesNewLines() { }
}
}";
var tree = CSharpSyntaxTree.ParseText(code);
var rewriter = new AttributeRemoverRewriter(
attributeName: "TestCategory",
attributeValue: "Atomic");
var rewrittenRoot = rewriter.Visit(tree.GetRoot());
rewrittenRoot.GetText().ToString().Dump();
}
public class AttributeRemoverRewriter : CSharpSyntaxRewriter
{
public AttributeRemoverRewriter(
String attributeName,
String attributeValue)
{
_attributeName = attributeName;
_attributeValue = attributeValue;
}
private readonly String _attributeName;
private readonly String _attributeValue;
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
{
var newAttributes = new SyntaxList<AttributeListSyntax>();
foreach (var attributeList in node.AttributeLists)
{
var nodesToRemove =
attributeList
.Attributes
.Where(
attribute =>
AttributeNameMatches(attribute)
&&
HasMatchingAttributeValue(attribute))
.ToArray();
if (nodesToRemove.Length != attributeList.Attributes.Count)
{
//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;
}
private static SimpleNameSyntax GetSimpleNameFromNode(AttributeSyntax node)
{
var identifierNameSyntax = node.Name as IdentifierNameSyntax;
var qualifiedNameSyntax = node.Name as QualifiedNameSyntax;
return
identifierNameSyntax
??
qualifiedNameSyntax?.Right
??
(node.Name as AliasQualifiedNameSyntax).Name;
}
private Boolean AttributeNameMatches(AttributeSyntax attribute)
{
return
GetSimpleNameFromNode(attribute)
.Identifier
.Text
.StartsWith(_attributeName);
}
private Boolean HasMatchingAttributeValue(AttributeSyntax attribute)
{
return
attribute
.ArgumentList
.Arguments
.Select(argument => argument.Expression)
.OfType<LiteralExpressionSyntax>()
.Any(MatchesAttributeValue);
}
private Boolean MatchesAttributeValue(LiteralExpressionSyntax literalExpression)
{
return
String.Equals(literalExpression.Token.ValueText, _attributeValue, StringComparison.OrdinalIgnoreCase);
}
}
@xdaDaveShaw
Copy link
Author

Part of my Roslyn Based Attribute Remover blog post.

@xdaDaveShaw
Copy link
Author

Updated to handle Aliased namespaces on attributes (e.g. NS::Type)

@xdaDaveShaw
Copy link
Author

Added changed value comparison to be case insensitive as it matches the test runner.

@xdaDaveShaw
Copy link
Author

Updated with help from Josh Varty for when completely removing attributes, or when there is a line break in the attribute declaration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment