Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions TUnit.Analyzers.Tests/AttributeClassPropertyRequiredTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier<TUnit.Analyzers.TestDataAnalyzer>;

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<string>]
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<string>]
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<string>]
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<string>]
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<string>]
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
}
"""
);
}
}
21 changes: 20 additions & 1 deletion TUnit.Analyzers/TestDataAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand All @@ -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<ITypeSymbol> GetTypes(ImmutableArray<IParameterSymbol> parameters, IPropertySymbol? propertySymbol)
{
IEnumerable<ITypeSymbol?> types = parameters.Select(x => x.Type).Concat(new[] { propertySymbol?.Type }).Where(t => t != null);
Expand Down
Loading