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
72 changes: 72 additions & 0 deletions TUnit.Analyzers.Tests/DataDrivenTestArgumentsAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,76 @@ public void Create_Unit_Test(string? something)
.WithLocation(0)
);
}

[Test]
public async Task TypedDataSource_Is_Flagged_When_Does_Not_Match_Parameter_Types()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TUnit.Core;

public class MyClass
{
[Test]
[{|#0:CustomData|}]
public void MyTest(string asdf, int notExistingParameter)
{
}
}

[AttributeUsage(AttributeTargets.Method)]
public class CustomData : TypedDataSourceAttribute<string>
{
public override async IAsyncEnumerable<Func<Task<string>>> GetTypedDataRowsAsync(
DataGeneratorMetadata dataGeneratorMetadata)
{
await Task.Yield();
yield return () => Task.FromResult("one");
}
}
""",

Verifier.Diagnostic(Rules.WrongArgumentTypeTestData)
.WithLocation(0)
.WithArguments("string", "string, int")
);
}

[Test]
public async Task TypedDataSource_Is_Not_Flagged_When_Matches_Parameter_Type()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TUnit.Core;

public class MyClass
{
[Test]
[CustomData]
public void MyTest(string value)
{
}
}

[AttributeUsage(AttributeTargets.Method)]
public class CustomData : TypedDataSourceAttribute<string>
{
public override async IAsyncEnumerable<Func<Task<string>>> GetTypedDataRowsAsync(
DataGeneratorMetadata dataGeneratorMetadata)
{
await Task.Yield();
yield return () => Task.FromResult("one");
}
}
"""
);
}
}
49 changes: 10 additions & 39 deletions TUnit.Analyzers/TestDataAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ private void Analyze(SymbolAnalysisContext context,
}
}

// Also check if we have IDataSourceAttribute interface
// Also check if we have IDataSourceAttribute interface (use AllInterfaces to catch indirect implementations)
if (dataSourceInterface != null &&
currentType.Interfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, dataSourceInterface)))
currentType.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, dataSourceInterface)))
{
return true;
}
Expand All @@ -189,6 +189,7 @@ private void Analyze(SymbolAnalysisContext context,
context.Compilation.GetTypeByMetadataName(WellKnown.AttributeFullyQualifiedClasses.Arguments.WithoutGlobalPrefix)))
{
CheckArguments(context, attribute, parameters, propertySymbol);
continue;
}

if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass,
Expand All @@ -200,6 +201,7 @@ private void Analyze(SymbolAnalysisContext context,
: parameters.Select(p => p.Type).ToImmutableArray().WithoutCancellationTokenParameter();

CheckMethodDataSource(context, attribute, testClassType, typesToValidate, propertySymbol);
continue;
}

if (attribute.AttributeClass?.IsGenericType is true
Expand All @@ -211,50 +213,19 @@ private void Analyze(SymbolAnalysisContext context,
? ImmutableArray.Create(propertySymbol.Type)
: parameters.Select(p => p.Type).ToImmutableArray().WithoutCancellationTokenParameter();
CheckMethodDataSource(context, attribute, testClassType, typesToValidate, propertySymbol);
continue;
}

// Check for ClassDataSourceAttribute<T> by fully qualified name
if (attribute.AttributeClass?.ToDisplayString()?.StartsWith("TUnit.Core.ClassDataSourceAttribute<") == true)
// For all other IDataSourceAttribute implementations, check type arguments
if (attribute.AttributeClass != null &&
dataSourceInterface != null &&
attribute.AttributeClass.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, dataSourceInterface)))
{
var typesToValidate = propertySymbol != null
? ImmutableArray.Create(propertySymbol.Type)
: types;
CheckDataGenerator(context, attribute, typesToValidate);
}

// Check for any custom data source generators that inherit from known base classes
// (excluding ClassDataSourceAttribute which is handled above)
if (attribute.AttributeClass != null &&
!attribute.AttributeClass.ToDisplayString()?.StartsWith("TUnit.Core.ClassDataSourceAttribute<") == true)
{
var isDataSourceGenerator = false;
var selfAndBaseTypes = attribute.AttributeClass.GetSelfAndBaseTypes();

foreach (var type in selfAndBaseTypes)
{
if (type.IsGenericType && type.TypeArguments.Length > 0)
{
var originalDef = type.OriginalDefinition;
var metadataName = originalDef?.ToDisplayString();

if (metadataName?.Contains("DataSourceGeneratorAttribute") == true ||
metadataName?.Contains("AsyncDataSourceGeneratorAttribute") == true)
{
isDataSourceGenerator = true;
break;
}
}
}

if (isDataSourceGenerator)
{
var typesToValidate = propertySymbol != null
? ImmutableArray.Create(propertySymbol.Type)
: types;
CheckDataGenerator(context, attribute, typesToValidate);
}
}

}
}

Expand Down Expand Up @@ -902,7 +873,7 @@ private void CheckDataGenerator(SymbolAnalysisContext context,
// First, try the same approach as the source generator: look for ITypedDataSourceAttribute<T> interface
var typedInterface = attribute.AttributeClass?.AllInterfaces
.FirstOrDefault(i => i.IsGenericType &&
i.ConstructedFrom.GloballyQualified() == WellKnown.AttributeFullyQualifiedClasses.ITypedDataSourceAttribute.WithGlobalPrefix + "`1");
i.ConstructedFrom.GloballyQualifiedNonGeneric() == WellKnown.AttributeFullyQualifiedClasses.ITypedDataSourceAttribute.WithGlobalPrefix);

if (typedInterface != null)
{
Expand Down
Loading