diff --git a/TUnit.Analyzers.Tests/AttributeClassPropertyRequiredTests.cs b/TUnit.Analyzers.Tests/AttributeClassPropertyRequiredTests.cs new file mode 100644 index 0000000000..e967d2c936 --- /dev/null +++ b/TUnit.Analyzers.Tests/AttributeClassPropertyRequiredTests.cs @@ -0,0 +1,129 @@ +using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier; + +namespace TUnit.Analyzers.Tests; + +public class AttributeClassPropertyRequiredTests +{ + [Test] + public async Task Attribute_Class_Property_Without_Required_Should_Not_Flag_Error() + { + await Verifier + .VerifyAnalyzerAsync( + """ + using TUnit.Core; + using System; + + public class MyCustomAttribute : System.Attribute + { + [ClassDataSource] + public string? TestProperty { get; init; } // Should NOT trigger TUnit0043 + } + """ + ); + } + + [Test] + public async Task Attribute_Class_Property_With_Required_Should_Not_Flag_Error() + { + await Verifier + .VerifyAnalyzerAsync( + """ + using TUnit.Core; + using System; + + public class MyCustomAttribute : System.Attribute + { + [ClassDataSource] + public required string TestProperty { get; init; } // Should also NOT trigger error + } + """ + ); + } + + [Test] + public async Task Regular_Test_Class_Property_Without_Required_Should_Flag_Error() + { + await Verifier + .VerifyAnalyzerAsync( + """ + using TUnit.Core; + + public class MyTestClass + { + [ClassDataSource] + public string? {|#0:TestProperty|} { get; init; } // SHOULD trigger TUnit0043 + + [Test] + public void TestMethod() + { + } + } + """, + + Verifier.Diagnostic(Rules.PropertyRequiredNotSet) + .WithLocation(0) + ); + } + + [Test] + public async Task Regular_Test_Class_Property_With_Required_Should_Not_Flag_Error() + { + await Verifier + .VerifyAnalyzerAsync( + """ + using TUnit.Core; + + public class MyTestClass + { + [ClassDataSource] + public required string TestProperty { get; init; } // Should NOT trigger error + + [Test] + public void TestMethod() + { + } + } + """ + ); + } + + [Test] + public async Task Indirect_Attribute_Inheritance_Should_Not_Flag_Error() + { + await Verifier + .VerifyAnalyzerAsync( + """ + using TUnit.Core; + using System; + + public class BaseCustomAttribute : System.Attribute + { + } + + public class MyCustomAttribute : BaseCustomAttribute + { + [ClassDataSource] + public string? TestProperty { get; init; } // Should NOT trigger TUnit0043 + } + """ + ); + } + + [Test] + public async Task Multiple_Data_Source_Attributes_On_Attribute_Class_Property() + { + await Verifier + .VerifyAnalyzerAsync( + """ + using TUnit.Core; + using System; + + public class MyCustomAttribute : System.Attribute + { + [Arguments("test")] + public string? TestProperty { get; init; } // Should NOT trigger TUnit0043 even with Arguments + } + """ + ); + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/TestDataAnalyzer.cs b/TUnit.Analyzers/TestDataAnalyzer.cs index 2895d4a686..15e2239e3e 100644 --- a/TUnit.Analyzers/TestDataAnalyzer.cs +++ b/TUnit.Analyzers/TestDataAnalyzer.cs @@ -265,7 +265,15 @@ private void CheckPropertyAccessor(SymbolAnalysisContext context, IPropertySymbo if (propertySymbol is { IsStatic: false, IsRequired: false }) { - context.ReportDiagnostic(Diagnostic.Create(Rules.PropertyRequiredNotSet, propertySymbol.Locations.FirstOrDefault())); + // Skip required keyword enforcement if the property is in a class that inherits from System.Attribute + if (IsInAttributeClass(propertySymbol.ContainingType)) + { + // Attribute classes don't need to enforce the required keyword for injected properties + } + else + { + context.ReportDiagnostic(Diagnostic.Create(Rules.PropertyRequiredNotSet, propertySymbol.Locations.FirstOrDefault())); + } } if (propertySymbol is { IsStatic: true, SetMethod: null }) @@ -274,6 +282,17 @@ private void CheckPropertyAccessor(SymbolAnalysisContext context, IPropertySymbo } } + private static bool IsInAttributeClass(INamedTypeSymbol? typeSymbol) + { + if (typeSymbol is null) + { + return false; + } + + // Check if the type or any of its base types is System.Attribute + return typeSymbol.IsOrInherits("global::System.Attribute"); + } + private ImmutableArray GetTypes(ImmutableArray parameters, IPropertySymbol? propertySymbol) { IEnumerable types = parameters.Select(x => x.Type).Concat(new[] { propertySymbol?.Type }).Where(t => t != null);