From 8ecf274960d3c2b1de27522d729ce246afad441e Mon Sep 17 00:00:00 2001 From: Matthew Steeples Date: Sat, 9 May 2026 14:18:49 +0100 Subject: [PATCH] Exclude private and protected fields from source generators --- .../MemberCollector.cs | 8 ++ .../SourceGeneratorEdgeCaseTests.cs | 93 +++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/src/FastCloner.SourceGenerator/MemberCollector.cs b/src/FastCloner.SourceGenerator/MemberCollector.cs index fbc06e6..e74fcb9 100644 --- a/src/FastCloner.SourceGenerator/MemberCollector.cs +++ b/src/FastCloner.SourceGenerator/MemberCollector.cs @@ -36,6 +36,14 @@ public static List GetMembers( if (member.IsStatic || member.IsImplicitlyDeclared) continue; + bool hasAccessibleVisibility = + member.DeclaredAccessibility == Accessibility.Public || + member.DeclaredAccessibility == Accessibility.Internal || + member.DeclaredAccessibility == Accessibility.ProtectedOrInternal; + + if (!hasAccessibleVisibility) + continue; + // Skip if we've already seen this member name (base class members override derived) if (!seenNames.Add(member.Name)) continue; diff --git a/src/FastCloner.Tests/SourceGeneratorEdgeCaseTests.cs b/src/FastCloner.Tests/SourceGeneratorEdgeCaseTests.cs index 2519dcf..8af3a5d 100644 --- a/src/FastCloner.Tests/SourceGeneratorEdgeCaseTests.cs +++ b/src/FastCloner.Tests/SourceGeneratorEdgeCaseTests.cs @@ -256,6 +256,99 @@ public async Task PrivateSetter_Properties_Should_Be_Skipped() await Assert.That(clone.PrivateSetterProperty).IsEqualTo(0); } + [FastClonerClonable] + public class ClassWithPrivateField + { + public int PublicProperty { get; set; } + private int privateField; + + public int PrivateFieldValue => privateField; + + public void SetPrivateField(int value) => privateField = value; + } + + [Test] + [SourceGeneratorCompatible] + public async Task Private_Fields_Should_Be_Skipped() + { + // Arrange + ClassWithPrivateField original = new ClassWithPrivateField + { + PublicProperty = 100 + }; + original.SetPrivateField(200); + + // Act + ClassWithPrivateField clone = original.FastDeepClone(); + + // Assert + await Assert.That(clone).IsNotNull(); + await Assert.That(clone!.PublicProperty).IsEqualTo(100); + await Assert.That(clone.PrivateFieldValue).IsEqualTo(0); + } + + [FastClonerClonable] + public class ClassWithProtectedField + { + public int PublicProperty { get; set; } + protected int protectedField; + + public int ProtectedFieldValue => protectedField; + + public void SetProtectedField(int value) => protectedField = value; + } + + [Test] + [SourceGeneratorCompatible] + public async Task Protected_Fields_Should_Be_Skipped() + { + // Arrange + ClassWithProtectedField original = new ClassWithProtectedField + { + PublicProperty = 100 + }; + original.SetProtectedField(300); + + // Act + ClassWithProtectedField clone = original.FastDeepClone(); + + // Assert + await Assert.That(clone).IsNotNull(); + await Assert.That(clone!.PublicProperty).IsEqualTo(100); + await Assert.That(clone.ProtectedFieldValue).IsEqualTo(0); + } + + [FastClonerClonable] + public class ClassWithInternalField + { + public int PublicProperty { get; set; } + internal int internalField; + + public int InternalFieldValue => internalField; + + public void SetInternalField(int value) => internalField = value; + } + + [Test] + [SourceGeneratorCompatible] + public async Task Internal_Fields_Should_Be_Cloned() + { + // Arrange + ClassWithInternalField original = new ClassWithInternalField + { + PublicProperty = 100 + }; + original.SetInternalField(400); + + // Act + ClassWithInternalField clone = original.FastDeepClone(); + + // Assert + await Assert.That(clone).IsNotNull(); + await Assert.That(clone!.PublicProperty).IsEqualTo(100); + await Assert.That(clone.InternalFieldValue).IsEqualTo(400); + } + #endregion #region Issue 5: Delegate and Behavioral Types (Lazy, Func, Task)