Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
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);
}
}
Owner

xdaDaveShaw commented Jun 21, 2015

Part of my Roslyn Based Attribute Remover blog post.

Owner

xdaDaveShaw commented Jul 31, 2015

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

Owner

xdaDaveShaw commented Jul 31, 2015

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

Owner

xdaDaveShaw commented Aug 1, 2015

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