@@ -23,6 +23,8 @@ public class TestingOnlyAnalyzerTests
2323
2424 public static string [ ] GetTestingOnlyAttributes { get ; } = { "TestingOnly" , "TestingOnlyAttribute" } ;
2525
26+ public static string [ ] GetTestingAndPrivateOnlyAttributes { get ; } = { "TestingAndPrivateOnly" , "TestingAndPrivateOnlyAttribute" } ;
27+
2628 public static string [ ] NonTestingOnlyAccesses { get ; } =
2729 {
2830 "var x = _nonPublicField;" ,
@@ -65,6 +67,26 @@ public class TestingOnlyAnalyzerTests
6567 """ ,
6668 } ;
6769
70+ public static string [ ] TestingAndPrivateOnlySameTypeAccesses { get ; } =
71+ {
72+ // Access members from the same class - should NOT be flagged
73+ "var x = _publicField;" ,
74+ "var x = PublicProperty;" ,
75+ "PublicProperty = string.Empty;" ,
76+ "var x = PublicMethod();" ,
77+ "var x = new TestClass();" ,
78+ } ;
79+
80+ public static string [ ] TestingAndPrivateOnlyDifferentTypeAccesses { get ; } =
81+ {
82+ // Access members from a different class - should be flagged
83+ "var x = {|#0:OtherClass._testingAndPrivateField|};" ,
84+ "var x = {|#0:OtherClass.TestingAndPrivateProperty|};" ,
85+ "{|#0:OtherClass.TestingAndPrivateProperty|} = string.Empty;" ,
86+ "var x = {|#0:OtherClass.TestingAndPrivateMethod()|};" ,
87+ "var x = {|#0:new OtherClass()|};" ,
88+ } ;
89+
6890 public static IEnumerable < object [ ] > NonPublicCombination { get ; } =
6991 from attrs in GetTestingOnlyAttributes
7092 from includeNamespace in new [ ] { true , false }
@@ -86,6 +108,20 @@ from api in ShouldThrowButDoesnt
86108 from conditional in new [ ] { true , false }
87109 select new object [ ] { attrs , includeNamespace , api , conditional } ;
88110
111+ public static IEnumerable < object [ ] > TestingAndPrivateOnlySameTypeCombination { get ; } =
112+ from attrs in GetTestingAndPrivateOnlyAttributes
113+ from includeNamespace in new [ ] { true , false }
114+ from api in TestingAndPrivateOnlySameTypeAccesses
115+ from conditional in new [ ] { true , false }
116+ select new object [ ] { attrs , includeNamespace , api , conditional } ;
117+
118+ public static IEnumerable < object [ ] > TestingAndPrivateOnlyDifferentTypeCombination { get ; } =
119+ from attrs in GetTestingAndPrivateOnlyAttributes
120+ from includeNamespace in new [ ] { true , false }
121+ from api in TestingAndPrivateOnlyDifferentTypeAccesses
122+ from conditional in new [ ] { true , false }
123+ select new object [ ] { attrs , includeNamespace , api , conditional } ;
124+
89125 [ Fact ]
90126 public async Task EmptySourceShouldNotHaveDiagnostics ( )
91127 {
@@ -133,6 +169,27 @@ public async Task NotSupported(string publicAttribute, bool includeNamespace, st
133169 await ideallyShouldThrow . Should ( ) . ThrowAsync < InvalidOperationException > ( ) ;
134170 }
135171
172+ [ Theory ]
173+ [ MemberData ( nameof ( TestingAndPrivateOnlySameTypeCombination ) ) ]
174+ public async Task ShouldNotFlagTestingAndPrivateOnlyWhenCalledFromSameType ( string publicAttribute , bool includeNamespace , string testFragment , bool attributeIsConditional )
175+ {
176+ var code = GetSampleCode ( publicAttribute , includeNamespace , testFragment , attributeIsConditional ) ;
177+
178+ // No diagnostics expected - calls from within the same type should be allowed
179+ await Verifier . VerifyAnalyzerAsync ( code ) ;
180+ }
181+
182+ [ Theory ]
183+ [ MemberData ( nameof ( TestingAndPrivateOnlyDifferentTypeCombination ) ) ]
184+ public async Task ShouldFlagTestingAndPrivateOnlyWhenCalledFromDifferentType ( string publicAttribute , bool includeNamespace , string testFragment , bool attributeIsConditional )
185+ {
186+ var code = GetSampleCode ( publicAttribute , includeNamespace , testFragment , attributeIsConditional ) ;
187+
188+ var expected = new DiagnosticResult ( DiagnosticId , DiagnosticSeverity . Error )
189+ . WithLocation ( 0 ) ;
190+ await Verifier . VerifyAnalyzerAsync ( code , expected ) ;
191+ }
192+
136193 private static string GetSampleCode ( string publicAttribute , bool includeNamespace , string testFragment , bool attributeIsConditional )
137194 {
138195 var attributePrefix = includeNamespace ? "ConsoleApplication1." : string . Empty ;
@@ -147,7 +204,7 @@ private static string GetSampleCode(string publicAttribute, bool includeNamespac
147204 using System.Diagnostics;
148205
149206 namespace ConsoleApplication1;
150-
207+
151208 {{ GetAttributeDefinition ( publicAttribute , attributeIsConditional ) }}
152209
153210 class TestClass
@@ -161,7 +218,7 @@ internal TestClass(string arg1) { }
161218
162219 [{{ attributePrefix }} {{ publicAttribute }} ]
163220 public TestClass() { }
164-
221+
165222 public string NonPublicProperty { get; set; }
166223
167224 [{{ attributePrefix }} {{ publicAttribute }} ]
@@ -211,6 +268,22 @@ public PublicClass() { }
211268 public static string PublicMethod() => "test";
212269 }
213270
271+ // Class for testing different-type access with TestingAndPrivateOnly
272+ class OtherClass
273+ {
274+ [{{ attributePrefix }} {{ publicAttribute }} ]
275+ public static string _testingAndPrivateField;
276+
277+ [{{ attributePrefix }} {{ publicAttribute }} ]
278+ public OtherClass() { }
279+
280+ [{{ attributePrefix }} {{ publicAttribute }} ]
281+ public static string TestingAndPrivateProperty { get; set; }
282+
283+ [{{ attributePrefix }} {{ publicAttribute }} ]
284+ public static string TestingAndPrivateMethod() => "test";
285+ }
286+
214287 interface IPublic
215288 {
216289 string PublicMethod();
0 commit comments