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
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
public sealed class MethodAssertionGenerator : IIncrementalGenerator
{
private static readonly DiagnosticDescriptor MethodMustBeStaticRule = new DiagnosticDescriptor(
id: "TUNITGEN001",

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

Włącz śledzenie wydań analizatora dla projektu analizatora zawierającego regułę „TUNITGEN001” (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

Activer le suivi de version d'analyseur pour le projet d'analyseur contenant la règle 'TUNITGEN001' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Check warning on line 22 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

title: "Method must be static",
messageFormat: "Method '{0}' decorated with [GenerateAssertion] must be static",
category: "TUnit.Assertions.SourceGenerator",
Expand All @@ -28,7 +28,7 @@
description: "Methods decorated with [GenerateAssertion] must be static to be used in generated assertions.");

private static readonly DiagnosticDescriptor MethodMustHaveParametersRule = new DiagnosticDescriptor(
id: "TUNITGEN002",

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

Włącz śledzenie wydań analizatora dla projektu analizatora zawierającego regułę „TUNITGEN002” (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

Activer le suivi de version d'analyseur pour le projet d'analyseur contenant la règle 'TUNITGEN002' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Check warning on line 31 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

title: "Method must have at least one parameter",
messageFormat: "Method '{0}' decorated with [GenerateAssertion] must have at least one parameter (the value to assert)",
category: "TUnit.Assertions.SourceGenerator",
Expand All @@ -37,9 +37,9 @@
description: "Methods decorated with [GenerateAssertion] must have at least one parameter representing the value being asserted.");

private static readonly DiagnosticDescriptor UnsupportedReturnTypeRule = new DiagnosticDescriptor(
id: "TUNITGEN003",

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

Włącz śledzenie wydań analizatora dla projektu analizatora zawierającego regułę „TUNITGEN003” (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

Activer le suivi de version d'analyseur pour le projet d'analyseur contenant la règle 'TUNITGEN003' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Check warning on line 40 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

title: "Unsupported return type",
messageFormat: "Method '{0}' decorated with [GenerateAssertion] has unsupported return type '{1}'. Supported types are: bool, AssertionResult, Task<bool>, Task<AssertionResult>",

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

Komunikat dotyczący diagnostyki nie powinien zawierać znaku nowego wiersza ani odstępów na początku i końcu oraz powinien być pojedynczym zdaniem bez kropki na końcu lub wieloma zdaniami z kropkami na końcu

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Die Diagnosemeldung darf keine Zeilenvorschubzeichen und keine führenden oder nachfolgenden Leerzeichen enthalten und muss entweder einen einzelnen Satz ohne Satzendepunkt oder mehrere Sätze mit Satzendepunkt umfassen.

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

Le message du diagnostic ne doit comporter aucun caractère de retour de ligne et aucun espace blanc de début ou de fin, et doit tenir en une seule phrase sans point final ou en plusieurs phrases avec un point final

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period

Check warning on line 42 in TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

The diagnostic message should not contain any line return character nor any leading or trailing whitespaces and should either be a single sentence without a trailing period or a multi-sentences with a trailing period
category: "TUnit.Assertions.SourceGenerator",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
Expand Down Expand Up @@ -459,9 +459,10 @@

if (data.IsExtensionMethod)
{
// Extension method syntax: value.MethodName<T1, T2>(params)
// Extension method syntax: value!.MethodName<T1, T2>(params)
// Use null-forgiving operator since we've already checked for null above
var paramList = string.Join(", ", data.AdditionalParameters.Select(p => $"_{p.Name}"));
return $"value.{methodName}{typeArguments}({paramList})";
return $"value!.{methodName}{typeArguments}({paramList})";
}
else
{
Expand Down Expand Up @@ -502,14 +503,18 @@
// Additional parameters
foreach (var param in data.AdditionalParameters)
{
sb.Append($", {param.Type.ToDisplayString()} {param.Name}");
var paramsModifier = param.IsParams ? "params " : "";
sb.Append($", {paramsModifier}{param.Type.ToDisplayString()} {param.Name}");
}

// CallerArgumentExpression parameters
// CallerArgumentExpression parameters (skip for params since params must be last)
for (int i = 0; i < data.AdditionalParameters.Length; i++)
{
var param = data.AdditionalParameters[i];
sb.Append($", [CallerArgumentExpression(nameof({param.Name}))] string? {param.Name}Expression = null");
if (!param.IsParams)
{
sb.Append($", [CallerArgumentExpression(nameof({param.Name}))] string? {param.Name}Expression = null");
}
}

sb.AppendLine(")");
Expand All @@ -528,7 +533,9 @@
// Build expression string
if (data.AdditionalParameters.Length > 0)
{
var exprList = string.Join(", ", data.AdditionalParameters.Select(p => $"{{{p.Name}Expression}}"));
// For params parameters, use parameter name directly (no Expression suffix since we didn't generate it)
var exprList = string.Join(", ", data.AdditionalParameters.Select(p =>
p.IsParams ? $"{{{p.Name}}}" : $"{{{p.Name}Expression}}"));
sb.AppendLine($" source.Context.ExpressionBuilder.Append($\".{methodName}({exprList})\");");
}
else
Expand Down
42 changes: 42 additions & 0 deletions TUnit.Assertions/Assertions/GenericAssertions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using TUnit.Assertions.Attributes;

namespace TUnit.Assertions.Assertions;

internal static class GenericAssertions
{
[GenerateAssertion]
public static bool IsIn<T>(this T value, IEnumerable<T> collection)
{
return collection.Contains(value);
}

[GenerateAssertion]
public static bool IsIn<T>(this T value, IEnumerable<T> collection, IEqualityComparer<T> equalityComparer)
{
return collection.Contains(value, equalityComparer);
}

[GenerateAssertion]
public static bool IsIn<T>(this T value, params T[] collection)
{
return collection.Contains(value);
}

[GenerateAssertion]
public static bool IsNotIn<T>(this T value, IEnumerable<T> collection)
{
return !collection.Contains(value);
}

[GenerateAssertion]
public static bool IsNotIn<T>(this T value, IEnumerable<T> collection, IEqualityComparer<T> equalityComparer)
{
return !collection.Contains(value, equalityComparer);
}

[GenerateAssertion]
public static bool IsNotIn<T>(this T value, params T[] collection)
{
return !collection.Contains(value);
}
}
101 changes: 0 additions & 101 deletions TUnit.Assertions/Conditions/MembershipAssertions.cs

This file was deleted.

64 changes: 20 additions & 44 deletions TUnit.Assertions/Extensions/AssertionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -753,16 +753,22 @@ public static NotStructuralEquivalencyAssertion<TValue> IsNotEquivalentTo<TValue
}

/// <summary>
/// Asserts that the value satisfies the specified predicate.
/// Example: await Assert.That(x).Satisfies(v => v > 0 && v < 100);
/// Asserts that a mapped Task value satisfies custom assertions on the unwrapped result.
/// Maps the source value using a selector that returns a Task, then runs assertions on the awaited result.
/// Example: await Assert.That(model).Satisfies(m => m.AsyncValue, assert => assert.IsEqualTo("Hello"));
/// </summary>
public static SatisfiesAssertion<TValue> Satisfies<TValue>(
public static AsyncMappedSatisfiesAssertion<TValue, TMapped> Satisfies<TValue, TMapped>(
this IAssertionSource<TValue> source,
Func<TValue?, bool> predicate,
[CallerArgumentExpression(nameof(predicate))] string? expression = null)
Func<TValue?, Task<TMapped?>> selector,
Func<ValueAssertion<TMapped>, Assertion<TMapped>> assertions,
[CallerArgumentExpression(nameof(selector))] string? selectorExpression = null)
{
source.Context.ExpressionBuilder.Append($".Satisfies({expression})");
return new SatisfiesAssertion<TValue>(source.Context, predicate, expression ?? "predicate");
source.Context.ExpressionBuilder.Append($".Satisfies({selectorExpression}, ...)");
return new AsyncMappedSatisfiesAssertion<TValue, TMapped>(
source.Context,
selector!,
assertions,
selectorExpression ?? "selector");
}

/// <summary>
Expand All @@ -785,46 +791,16 @@ public static MappedSatisfiesAssertion<TValue, TMapped> Satisfies<TValue, TMappe
}

/// <summary>
/// Asserts that an async-mapped value satisfies custom assertions.
/// Maps the source value using an async selector, then runs assertions on the mapped value.
/// Example: await Assert.That(model).Satisfies(m => m.GetNameAsync(), assert => assert.IsEqualTo("John"));
/// </summary>
public static AsyncMappedSatisfiesAssertion<TValue, TMapped> Satisfies<TValue, TMapped>(
this IAssertionSource<TValue> source,
Func<TValue?, Task<TMapped>> selector,
Func<ValueAssertion<TMapped>, Assertion<TMapped>?> assertions,
[CallerArgumentExpression(nameof(selector))] string? selectorExpression = null)
{
source.Context.ExpressionBuilder.Append($".Satisfies({selectorExpression}, ...)");
return new AsyncMappedSatisfiesAssertion<TValue, TMapped>(
source.Context,
selector,
assertions,
selectorExpression ?? "selector");
}

/// <summary>
/// Asserts that the value is in the specified collection (params array convenience method).
/// Example: await Assert.That(5).IsIn(1, 3, 5, 7, 9);
/// </summary>
public static IsInAssertion<TValue> IsIn<TValue>(
this IAssertionSource<TValue> source,
params TValue[] collection)
{
source.Context.ExpressionBuilder.Append($".IsIn({string.Join(", ", collection)})");
return new IsInAssertion<TValue>(source.Context, collection);
}

/// <summary>
/// Asserts that the value is NOT in the specified collection (params array convenience method).
/// Example: await Assert.That(4).IsNotIn(1, 3, 5, 7, 9);
/// Asserts that the value satisfies the specified predicate.
/// Example: await Assert.That(x).Satisfies(v => v > 0 && v < 100);
/// </summary>
public static IsNotInAssertion<TValue> IsNotIn<TValue>(
public static SatisfiesAssertion<TValue> Satisfies<TValue>(
this IAssertionSource<TValue> source,
params TValue[] collection)
Func<TValue?, bool> predicate,
[CallerArgumentExpression(nameof(predicate))] string? expression = null)
{
source.Context.ExpressionBuilder.Append($".IsNotIn({string.Join(", ", collection)})");
return new IsNotInAssertion<TValue>(source.Context, collection);
source.Context.ExpressionBuilder.Append($".Satisfies({expression})");
return new SatisfiesAssertion<TValue>(source.Context, predicate, expression ?? "predicate");
}

/// <summary>
Expand Down
Loading
Loading