Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat: Introduce CombinedDataSource attribute for flexible data-driven…
… testing
  • Loading branch information
thomhurst committed Nov 1, 2025
commit 12b873413a4b5accb51308b8a7b3c6322e05a626
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier<TUnit.Analyzers.MixedParametersDataSourceAnalyzer>;
using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier<TUnit.Analyzers.CombinedDataSourceAnalyzer>;

namespace TUnit.Analyzers.Tests;

public class MixedParametersDataSourceAnalyzerTests
public class CombinedDataSourceAnalyzerTests
{
[Test]
public async Task Method_With_MixedParametersDataSource_And_ParameterDataSources_No_Error()
public async Task Method_With_CombinedDataSource_And_ParameterDataSources_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
Expand All @@ -14,7 +14,7 @@ await Verifier

public class MyClass
{
[MixedParametersDataSource]
[CombinedDataSource]
[Test]
public void MyTest(
[Arguments(1, 2, 3)] int value,
Expand All @@ -28,7 +28,7 @@ public void MyTest(
}

[Test]
public async Task Method_With_ParameterDataSources_Missing_MixedParametersDataSource_Error()
public async Task Method_With_ParameterDataSources_Missing_CombinedDataSource_Error()
{
await Verifier
.VerifyAnalyzerAsync(
Expand All @@ -46,13 +46,13 @@ public class MyClass
}
}
""",
Verifier.Diagnostic(Rules.MixedParametersDataSourceAttributeRequired)
Verifier.Diagnostic(Rules.CombinedDataSourceAttributeRequired)
.WithLocation(0)
);
}

[Test]
public async Task Method_With_MixedParametersDataSource_Missing_ParameterDataSource_Error()
public async Task Method_With_CombinedDataSource_Missing_ParameterDataSource_Error()
{
await Verifier
.VerifyAnalyzerAsync(
Expand All @@ -61,7 +61,7 @@ await Verifier

public class MyClass
{
[MixedParametersDataSource]
[CombinedDataSource]
[Test]
public void MyTest(
[Arguments(1, 2, 3)] int value,
Expand All @@ -71,14 +71,14 @@ public void MyTest(
}
}
""",
Verifier.Diagnostic(Rules.MixedParametersDataSourceMissingParameterDataSource)
Verifier.Diagnostic(Rules.CombinedDataSourceMissingParameterDataSource)
.WithLocation(0)
.WithArguments("text")
);
}

[Test]
public async Task Method_With_MixedParametersDataSource_And_MatrixDataSource_Warning()
public async Task Method_With_CombinedDataSource_And_MatrixDataSource_Warning()
{
await Verifier
.VerifyAnalyzerAsync(
Expand All @@ -87,7 +87,7 @@ await Verifier

public class MyClass
{
[MixedParametersDataSource]
[CombinedDataSource]
[MatrixDataSource]
[Test]
public void {|#0:MyTest|}(
Expand All @@ -98,20 +98,20 @@ public class MyClass
}
}
""",
Verifier.Diagnostic(Rules.MixedParametersDataSourceConflictWithMatrix)
Verifier.Diagnostic(Rules.CombinedDataSourceConflictWithMatrix)
.WithLocation(0)
);
}

[Test]
public async Task Class_With_MixedParametersDataSource_And_ParameterDataSources_No_Error()
public async Task Class_With_CombinedDataSource_And_ParameterDataSources_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

[MixedParametersDataSource]
[CombinedDataSource]
public class MyClass
{
public MyClass(
Expand All @@ -131,7 +131,7 @@ public void MyTest()
}

[Test]
public async Task Class_With_ParameterDataSources_Missing_MixedParametersDataSource_Error()
public async Task Class_With_ParameterDataSources_Missing_CombinedDataSource_Error()
{
await Verifier
.VerifyAnalyzerAsync(
Expand All @@ -153,7 +153,7 @@ public void MyTest()
}
}
""",
Verifier.Diagnostic(Rules.MixedParametersDataSourceAttributeRequired)
Verifier.Diagnostic(Rules.CombinedDataSourceAttributeRequired)
.WithLocation(0)
);
}
Expand All @@ -169,7 +169,7 @@ await Verifier

public class MyClass
{
[MixedParametersDataSource]
[CombinedDataSource]
[Test]
public void MyTest(
[Arguments(1, 2, 3)] int value,
Expand All @@ -195,7 +195,7 @@ public class MyClass
{
public static IEnumerable<int> GetNumbers() => [1, 2, 3];

[MixedParametersDataSource]
[CombinedDataSource]
[Test]
public void MyTest(
[MethodDataSource(nameof(GetNumbers))] int number,
Expand Down Expand Up @@ -229,7 +229,7 @@ protected override IEnumerable<Func<int>> GenerateDataSources(DataGeneratorMetad

public class MyClass
{
[MixedParametersDataSource]
[CombinedDataSource]
[Test]
public void MyTest(
[ClassDataSource<TestData>] int number,
Expand All @@ -252,7 +252,7 @@ await Verifier

public class MyClass
{
[MixedParametersDataSource]
[CombinedDataSource]
[Test]
public void MyTest(
[Arguments(1, 2, 3)] int value,
Expand All @@ -263,17 +263,17 @@ public void MyTest(
}
}
""",
Verifier.Diagnostic(Rules.MixedParametersDataSourceMissingParameterDataSource)
Verifier.Diagnostic(Rules.CombinedDataSourceMissingParameterDataSource)
.WithLocation(0)
.WithArguments("text"),
Verifier.Diagnostic(Rules.MixedParametersDataSourceMissingParameterDataSource)
Verifier.Diagnostic(Rules.CombinedDataSourceMissingParameterDataSource)
.WithLocation(1)
.WithArguments("flag")
);
}

[Test]
public async Task Method_Without_MixedParametersDataSource_No_ParameterDataSources_No_Error()
public async Task Method_Without_CombinedDataSource_No_ParameterDataSources_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
Expand All @@ -293,14 +293,14 @@ public void MyTest(int value, string text, bool flag)
}

[Test]
public async Task Class_With_MixedParametersDataSource_Missing_ParameterDataSource_Error()
public async Task Class_With_CombinedDataSource_Missing_ParameterDataSource_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

[MixedParametersDataSource]
[CombinedDataSource]
public class MyClass
{
public MyClass(
Expand All @@ -316,7 +316,7 @@ public void MyTest()
}
}
""",
Verifier.Diagnostic(Rules.MixedParametersDataSourceMissingParameterDataSource)
Verifier.Diagnostic(Rules.CombinedDataSourceMissingParameterDataSource)
.WithLocation(0)
.WithArguments("text")
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
namespace TUnit.Analyzers;

/// <summary>
/// Analyzer for MixedParametersDataSource validation
/// Analyzer for CombinedDataSource validation
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MixedParametersDataSourceAnalyzer : ConcurrentDiagnosticAnalyzer
public class CombinedDataSourceAnalyzer : ConcurrentDiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create(
Rules.MixedParametersDataSourceAttributeRequired,
Rules.MixedParametersDataSourceMissingParameterDataSource,
Rules.MixedParametersDataSourceConflictWithMatrix
Rules.CombinedDataSourceAttributeRequired,
Rules.CombinedDataSourceMissingParameterDataSource,
Rules.CombinedDataSourceConflictWithMatrix
);

protected override void InitializeInternal(AnalysisContext context)
Expand All @@ -36,7 +36,7 @@ private void AnalyzeClass(SymbolAnalysisContext context)
return;
}

CheckMixedParametersDataSourceErrors(context, namedTypeSymbol.GetAttributes(),
CheckCombinedDataSourceErrors(context, namedTypeSymbol.GetAttributes(),
namedTypeSymbol.InstanceConstructors.FirstOrDefault()?.Parameters ?? ImmutableArray<IParameterSymbol>.Empty);
}

Expand All @@ -52,31 +52,31 @@ private void AnalyzeMethod(SymbolAnalysisContext context)
return;
}

CheckMixedParametersDataSourceErrors(context, methodSymbol.GetAttributes(), methodSymbol.Parameters);
CheckCombinedDataSourceErrors(context, methodSymbol.GetAttributes(), methodSymbol.Parameters);
}

private void CheckMixedParametersDataSourceErrors(SymbolAnalysisContext context,
private void CheckCombinedDataSourceErrors(SymbolAnalysisContext context,
ImmutableArray<AttributeData> attributes,
ImmutableArray<IParameterSymbol> parameters)
{
var hasMixedParametersDataSource = attributes.Any(x =>
x.IsMixedParametersDataSourceAttribute(context.Compilation));
var hasCombinedDataSource = attributes.Any(x =>
x.IsCombinedDataSourceAttribute(context.Compilation));

var parametersWithDataSources = parameters
.Where(p => p.HasDataSourceAttribute(context.Compilation))
.ToList();

// Rule 1: If parameters have data source attributes, MixedParametersDataSource must be present
if (parametersWithDataSources.Any() && !hasMixedParametersDataSource)
// Rule 1: If parameters have data source attributes, CombinedDataSource must be present
if (parametersWithDataSources.Any() && !hasCombinedDataSource)
{
context.ReportDiagnostic(
Diagnostic.Create(Rules.MixedParametersDataSourceAttributeRequired,
Diagnostic.Create(Rules.CombinedDataSourceAttributeRequired,
context.Symbol.Locations.FirstOrDefault())
);
}

// Rule 2: If MixedParametersDataSource is present, all parameters must have data sources
if (hasMixedParametersDataSource)
// Rule 2: If CombinedDataSource is present, all parameters must have data sources
if (hasCombinedDataSource)
{
// Filter out CancellationToken parameters as they're handled by the engine
var nonCancellationTokenParams = parameters
Expand All @@ -89,19 +89,19 @@ private void CheckMixedParametersDataSourceErrors(SymbolAnalysisContext context,
if (!parameter.HasDataSourceAttribute(context.Compilation))
{
context.ReportDiagnostic(
Diagnostic.Create(Rules.MixedParametersDataSourceMissingParameterDataSource,
Diagnostic.Create(Rules.CombinedDataSourceMissingParameterDataSource,
parameter.Locations.FirstOrDefault() ?? context.Symbol.Locations.FirstOrDefault(),
parameter.Name)
);
}
}

// Rule 3: Warn if mixing MixedParametersDataSource with MatrixDataSource
// Rule 3: Warn if mixing CombinedDataSource with MatrixDataSource
var hasMatrixDataSource = attributes.Any(x => x.IsMatrixDataSourceAttribute(context.Compilation));
if (hasMatrixDataSource)
{
context.ReportDiagnostic(
Diagnostic.Create(Rules.MixedParametersDataSourceConflictWithMatrix,
Diagnostic.Create(Rules.CombinedDataSourceConflictWithMatrix,
context.Symbol.Locations.FirstOrDefault())
);
}
Expand Down
4 changes: 2 additions & 2 deletions TUnit.Analyzers/Extensions/AttributeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ public static bool IsMatrixDataSourceAttribute(this AttributeData attributeData,
.WithoutGlobalPrefix));
}

public static bool IsMixedParametersDataSourceAttribute(this AttributeData attributeData, Compilation compilation)
public static bool IsCombinedDataSourceAttribute(this AttributeData attributeData, Compilation compilation)
{
return SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass,
compilation.GetTypeByMetadataName(WellKnown.AttributeFullyQualifiedClasses.MixedParametersDataSourceAttribute
compilation.GetTypeByMetadataName(WellKnown.AttributeFullyQualifiedClasses.CombinedDataSourceAttribute
.WithoutGlobalPrefix));
}

Expand Down
2 changes: 1 addition & 1 deletion TUnit.Analyzers/Helpers/WellKnown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public static class AttributeFullyQualifiedClasses
public static readonly FullyQualifiedTypeName Explicit = GetTypeName("ExplicitAttribute");
public static readonly FullyQualifiedTypeName Matrix = GetTypeName("MatrixAttribute");
public static readonly FullyQualifiedTypeName MatrixDataSourceAttribute = GetTypeName("MatrixDataSourceAttribute");
public static readonly FullyQualifiedTypeName MixedParametersDataSourceAttribute = GetTypeName("MixedParametersDataSourceAttribute");
public static readonly FullyQualifiedTypeName CombinedDataSourceAttribute = GetTypeName("CombinedDataSourceAttribute");

public static readonly FullyQualifiedTypeName BeforeAttribute = GetTypeName("BeforeAttribute");
public static readonly FullyQualifiedTypeName AfterAttribute = GetTypeName("AfterAttribute");
Expand Down
14 changes: 7 additions & 7 deletions TUnit.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -463,28 +463,28 @@
<value>Data source may produce no tests</value>
</data>
<data name="TUnit0070Description" xml:space="preserve">
<value>When parameters have data source attributes, the method or class must be marked with [MixedParametersDataSource] to combine the parameter data sources.</value>
<value>When parameters have data source attributes, the method or class must be marked with [CombinedDataSource] to combine the parameter data sources.</value>
</data>
<data name="TUnit0070MessageFormat" xml:space="preserve">
<value>[MixedParametersDataSource] is required when parameters have data source attributes.</value>
<value>[CombinedDataSource] is required when parameters have data source attributes.</value>
</data>
<data name="TUnit0070Title" xml:space="preserve">
<value>[MixedParametersDataSource] attribute required</value>
<value>[CombinedDataSource] attribute required</value>
</data>
<data name="TUnit0071Description" xml:space="preserve">
<value>When [MixedParametersDataSource] is used, all parameters (except CancellationToken) must have data source attributes to provide test data.</value>
<value>When [CombinedDataSource] is used, all parameters (except CancellationToken) must have data source attributes to provide test data.</value>
</data>
<data name="TUnit0071MessageFormat" xml:space="preserve">
<value>Parameter '{0}' is missing a data source attribute. All parameters must have data sources when using [MixedParametersDataSource].</value>
<value>Parameter '{0}' is missing a data source attribute. All parameters must have data sources when using [CombinedDataSource].</value>
</data>
<data name="TUnit0071Title" xml:space="preserve">
<value>Parameter missing data source attribute</value>
</data>
<data name="TUnit0072Description" xml:space="preserve">
<value>Using [MixedParametersDataSource] together with [MatrixDataSource] is not recommended as they serve different purposes and may cause confusion.</value>
<value>Using [CombinedDataSource] together with [MatrixDataSource] is not recommended as they serve different purposes and may cause confusion.</value>
</data>
<data name="TUnit0072MessageFormat" xml:space="preserve">
<value>[MixedParametersDataSource] should not be used with [MatrixDataSource]. Use one or the other.</value>
<value>[CombinedDataSource] should not be used with [MatrixDataSource]. Use one or the other.</value>
</data>
<data name="TUnit0072Title" xml:space="preserve">
<value>Conflicting data source attributes</value>
Expand Down
6 changes: 3 additions & 3 deletions TUnit.Analyzers/Rules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ public static class Rules
public static readonly DiagnosticDescriptor OverwriteConsole =
CreateDescriptor("TUnit0055", UsageCategory, DiagnosticSeverity.Warning);

public static readonly DiagnosticDescriptor MixedParametersDataSourceAttributeRequired =
public static readonly DiagnosticDescriptor CombinedDataSourceAttributeRequired =
CreateDescriptor("TUnit0070", UsageCategory, DiagnosticSeverity.Error);

public static readonly DiagnosticDescriptor MixedParametersDataSourceMissingParameterDataSource =
public static readonly DiagnosticDescriptor CombinedDataSourceMissingParameterDataSource =
CreateDescriptor("TUnit0071", UsageCategory, DiagnosticSeverity.Error);

public static readonly DiagnosticDescriptor MixedParametersDataSourceConflictWithMatrix =
public static readonly DiagnosticDescriptor CombinedDataSourceConflictWithMatrix =
CreateDescriptor("TUnit0072", UsageCategory, DiagnosticSeverity.Warning);

public static readonly DiagnosticDescriptor InstanceMethodSource =
Expand Down
Loading
Loading