Search code examples
c#propertiesroslynclass-members

How do I add a JsonIgnore attribute to all properties with a specific return type in a class using Roslyn?


In the following code only one property is updated at a time and previously updated properties are ignored. I know that syntax nodes are immutable so I'm missing something. What I end up with is that only the last replaced property is updated. What am I missing?

var newMemberDeclarations = new SyntaxList<MemberDeclarationSyntax>();
newMemberDeclarations.AddRange(@class.Members);

foreach (var prop in props)
{
    var oldProperty = (PropertyDeclarationSyntax)prop;
    // e.g. Customer or ICollection<Customer>
    if (oldProperty.Type.Kind() == SyntaxKind.IdentifierName ||
        oldProperty.Type.Kind() == SyntaxKind.GenericName)
    {
        var memberAttributes = new SyntaxList<AttributeListSyntax>().AddRange(oldProperty.AttributeLists).Add(jsonIgnoreAttribute);
        var newProperty = SyntaxFactory.PropertyDeclaration(
                            memberAttributes,
                            oldProperty.Modifiers,
                            oldProperty.Type,
                            oldProperty.ExplicitInterfaceSpecifier,
                            oldProperty.Identifier,
                            oldProperty.AccessorList
                            );
        var replaced = @class.Members.Replace(oldProperty, newProperty);
        // This ignores previously updated property 
        // and only adds the attribute on the last replaced property
        newMemberDeclarations.AddRange(replaced);                        
     }
}
var attributes = @class.AttributeLists.AddRange(attributeLists);
root = root.ReplaceNode(@class, @class.WithAttributeLists(attributes))
           .AddUsings(usingSerialization, usingCollections, usingJson)
           .WithMembers(newMemberDeclarations)
           .NormalizeWhitespace();

Solution

  •             foreach (var classDeclaration in classDeclarations)
                {
                    var @class = classDeclaration as ClassDeclarationSyntax;
    
                    List<AttributeListSyntax> attributeLists = new List<AttributeListSyntax>();
                    attributeLists.Add(serializableAttribute);
    
                    var guidTypeSyntax = SyntaxFactory.ParseTypeName(typeof(Guid).FullName) as QualifiedNameSyntax;
                    var dateTimeTypeSyntax = SyntaxFactory.ParseTypeName(typeof(DateTime).FullName) as QualifiedNameSyntax;
                    
                    var customTypes = @class.DescendantNodes().Where(x => x is PropertyDeclarationSyntax)
                        .Select(x => (PropertyDeclarationSyntax)x)
                        .Where(x => x.Type.Kind() == SyntaxKind.IdentifierName || x.Type.Kind() == SyntaxKind.GenericName)
                        ;
                    
                    customTypes.ToList().ForEach(t => // .Type.GetType().FullName
                    {
                        bool isGuid = ((IdentifierNameSyntax)t.Type).Identifier.Value == guidTypeSyntax.Right.Identifier.Value;
                        bool isDateTime = ((IdentifierNameSyntax)t.Type).Identifier.Value == dateTimeTypeSyntax.Right.Identifier.Value;
                        
                        if (t.Type is GenericNameSyntax)
                        {
                            var args = ((GenericNameSyntax)t.Type).TypeArgumentList.Arguments;
                            foreach(var arg in args)
                            {
                                if(arg is IdentifierNameSyntax)
                                {
                                    if (!(isGuid || isDateTime))
                                    {
                                        var type = ((IdentifierNameSyntax)arg).Identifier.Value.ToString();
                                        var attribute = CreateKnownTypeAttribute(type);
                                        var knownTypeEntityHashSetAttribute = CreateKnownTypeAttribute($"HashSet<{type}>");
                                        attributeLists.Add(knownTypeEntityHashSetAttribute);
                                        attributeLists.Add(attribute);
                                    }
                                }
                            }
                        } 
                        else if(t.Type is IdentifierNameSyntax)
                        {
                            if (!(isGuid || isDateTime))
                            {
                                var identifier = ((IdentifierNameSyntax)t.Type).Identifier.Value.ToString();
                                var attribute = CreateKnownTypeAttribute(identifier);
                                var knownTypeEntityHashSetAttribute = CreateKnownTypeAttribute($"HashSet<{identifier}>");
                                attributeLists.Add(knownTypeEntityHashSetAttribute);
                                attributeLists.Add(attribute);
                            }
                        }
                    });
    
                    var members = new SyntaxList<MemberDeclarationSyntax>(@class.Members);
                    
                    customTypes.ToList().ForEach(oldProperty =>
                    {
                        var memberAttributes = new SyntaxList<AttributeListSyntax>().AddRange(oldProperty.AttributeLists).Add(jsonIgnoreAttribute);
                        var newProperty = SyntaxFactory.PropertyDeclaration(
                            memberAttributes,
                            oldProperty.Modifiers,
                            oldProperty.Type,
                            oldProperty.ExplicitInterfaceSpecifier,
                            oldProperty.Identifier,
                            oldProperty.AccessorList
                            ) as MemberDeclarationSyntax;
    
                        if (oldProperty.Type is IdentifierNameSyntax)
                        {
                            bool isGuid = ((IdentifierNameSyntax)oldProperty.Type).Identifier.Value == guidTypeSyntax.Right.Identifier.Value;
                            bool isDateTime = ((IdentifierNameSyntax)oldProperty.Type).Identifier.Value == dateTimeTypeSyntax.Right.Identifier.Value;
                            bool isInt = oldProperty.Type.Kind() == SyntaxKind.IntKeyword;
                            bool isFloat = oldProperty.Type.Kind() == SyntaxKind.FloatKeyword;
    
                            if (!(isGuid || isDateTime))
                            {
                                var identifier = oldProperty.Identifier.Value;
                                var indexOf = members.IndexOf(m => m is PropertyDeclarationSyntax ? ((PropertyDeclarationSyntax)m).Identifier.Value == identifier : false);
                                members = members.RemoveAt(indexOf).Add(newProperty);
                            }
                        }
                        else if(oldProperty.Type is GenericNameSyntax)
                        {
                            var identifier = oldProperty.Identifier.Value;
                            var indexOf = members.IndexOf(m => m is PropertyDeclarationSyntax 
                               ? ((PropertyDeclarationSyntax)m).Identifier.Value == identifier : false);
                            members = members.RemoveAt(indexOf).Add(newProperty);
                        }
                    });
    
                    var attributes = @class.AttributeLists.AddRange(attributeLists);
                    var c = SyntaxFactory.ClassDeclaration(@class.Identifier)
                        .WithBaseList(@class.BaseList)
                        .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword), 
                                      SyntaxFactory.Token(SyntaxKind.PartialKeyword))
                        .WithAttributeLists(attributes)
                        .AddMembers(members.ToArray());
    
                    root = root.ReplaceNode(@class, c)
                        .AddUsings(usingSerialization, usingCollections, usingSerialisation)
                        .NormalizeWhitespace();
                }