Skip to content
Merged
511 changes: 423 additions & 88 deletions src/TUnit.Assertions.Should.SourceGenerator/ShouldExtensionGenerator.cs

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using TUnit.Assertions.SourceGenerator.Models;

namespace TUnit.Assertions.SourceGenerator.Generators;
Expand All @@ -18,9 +19,10 @@
public sealed class MethodAssertionGenerator : IIncrementalGenerator
{
public static string BuildAssertion = "MethodAssertionData";
public const string BuildAssertionGroup = "MethodAssertionGroup";

private static readonly DiagnosticDescriptor MethodMustBeStaticRule = new DiagnosticDescriptor(
id: "TUNITGEN001",

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

View workflow job for this annotation

GitHub Actions / WASI smoke test

Check warning on line 25 in src/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 25 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Browser smoke test

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

View workflow job for this annotation

GitHub Actions / integration-tests

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

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Check warning on line 25 in src/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 25 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Android smoke test

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

View workflow job for this annotation

GitHub Actions / MacCatalyst smoke test

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

View workflow job for this annotation

GitHub Actions / tvOS smoke test

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

View workflow job for this annotation

GitHub Actions / iOS smoke test

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

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 25 in src/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 @@ -29,7 +31,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 34 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / WASI smoke test

Check warning on line 34 in src/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 34 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Browser smoke test

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

View workflow job for this annotation

GitHub Actions / integration-tests

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

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Check warning on line 34 in src/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 34 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Android smoke test

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

View workflow job for this annotation

GitHub Actions / MacCatalyst smoke test

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

View workflow job for this annotation

GitHub Actions / tvOS smoke test

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

View workflow job for this annotation

GitHub Actions / iOS smoke test

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

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 34 in src/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 @@ -38,18 +40,18 @@
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 43 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / WASI smoke test

Check warning on line 43 in src/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 43 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Browser smoke test

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

View workflow job for this annotation

GitHub Actions / integration-tests

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

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Check warning on line 43 in src/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 43 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Android smoke test

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

View workflow job for this annotation

GitHub Actions / MacCatalyst smoke test

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

View workflow job for this annotation

GitHub Actions / tvOS smoke test

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

View workflow job for this annotation

GitHub Actions / iOS smoke test

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

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Check warning on line 43 in src/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, AssertionResult<T>, Task<bool>, Task<AssertionResult>, Task<AssertionResult<T>>",

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

View workflow job for this annotation

GitHub Actions / WASI smoke test

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 45 in src/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 45 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Browser smoke test

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 45 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / integration-tests

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 45 in src/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 45 in src/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 45 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Android smoke test

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 45 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / MacCatalyst smoke test

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 45 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / tvOS smoke test

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 45 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / iOS smoke test

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 45 in src/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 45 in src/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 45 in src/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,
description: "Methods decorated with [GenerateAssertion] must return bool, AssertionResult, Task<bool>, or Task<AssertionResult>.");

private static readonly DiagnosticDescriptor RefStructRequiresInliningRule = new DiagnosticDescriptor(
id: "TUNITGEN004",

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

View workflow job for this annotation

GitHub Actions / WASI smoke test

Check warning on line 52 in src/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 'TUNITGEN004' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

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

View workflow job for this annotation

GitHub Actions / Browser smoke test

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

View workflow job for this annotation

GitHub Actions / integration-tests

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

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Check warning on line 52 in src/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łę „TUNITGEN004” (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

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

View workflow job for this annotation

GitHub Actions / Android smoke test

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

View workflow job for this annotation

GitHub Actions / MacCatalyst smoke test

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

View workflow job for this annotation

GitHub Actions / tvOS smoke test

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

View workflow job for this annotation

GitHub Actions / iOS smoke test

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

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

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

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

title: "Ref struct parameter requires method body inlining",
messageFormat: "Method '{0}' has ref struct parameter '{1}' of type '{2}'. Use InlineMethodBody = true and ensure the method has a single-expression or single-return-statement body",

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

View workflow job for this annotation

GitHub Actions / WASI smoke test

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 54 in src/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 54 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Browser smoke test

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 54 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / integration-tests

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 54 in src/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 54 in src/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 54 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / Android smoke test

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 54 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / MacCatalyst smoke test

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 54 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / tvOS smoke test

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 54 in src/TUnit.Assertions.SourceGenerator/Generators/MethodAssertionGenerator.cs

View workflow job for this annotation

GitHub Actions / iOS smoke test

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 54 in src/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 54 in src/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 54 in src/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 All @@ -74,20 +76,53 @@
.Where(x => x.Diagnostic != null)
.Select((x, _) => x.Diagnostic!);

// Report diagnostics
context.RegisterSourceOutput(diagnostics, static (context, diagnostic) =>
// Report diagnostics. Each one is located in the current compilation's syntax tree so that
// #pragma and per-file .editorconfig severities still apply; an external-file location
// has no SourceTree to read them from. Pairing each diagnostic (not the collected array)
// with the compilation means this output only re-runs while there are diagnostics.
context.RegisterSourceOutput(diagnostics.Combine(context.CompilationProvider), static (context, data) =>
{
context.ReportDiagnostic(diagnostic);
context.ReportDiagnostic(data.Left.ToDiagnostic(data.Right));
});

// Group methods by containing type so an edit to one type only re-emits that type's file.
// The grouping itself is cheap; each group compares by value, so unchanged groups stay cached.
var methodGroups = methods.Collect()
.SelectMany(static (methods, _) => GroupByContainingType(methods))
.WithTrackingName(BuildAssertionGroup);

// Generate assertion classes and extension methods
context.RegisterSourceOutput(methods.Collect(), static (context, methods) =>
context.RegisterSourceOutput(methodGroups, static (context, group) =>
{
GenerateAssertions(context, methods);
GenerateAssertions(context, group);
});
}

private static (AssertionMethodData? Data, Diagnostic? Diagnostic) GetAssertionMethodData(
private static ImmutableArray<ContainingTypeGroup> GroupByContainingType(ImmutableArray<AssertionMethodData> methods)
{
if (methods.IsEmpty)
{
return ImmutableArray<ContainingTypeGroup>.Empty;
}

var builder = ImmutableArray.CreateBuilder<ContainingTypeGroup>();
foreach (var methodGroup in methods.GroupBy(m => m.Method.ContainingType?.FullContainingType))
{
// FullContainingType is the fully qualified name, so every method in the group comes
// from the same type and carries identical containing-type data; any one can stand in.
var containingType = methodGroup.First().Method.ContainingType;
if (containingType == null)
{
continue;
}

builder.Add(new ContainingTypeGroup(containingType, methodGroup.ToImmutableEquatableArray()));
}

return builder.ToImmutable();
}

private static (AssertionMethodData? Data, DiagnosticInfo? Diagnostic) GetAssertionMethodData(
GeneratorAttributeSyntaxContext context,
CancellationToken cancellationToken)
{
Expand All @@ -101,7 +136,7 @@
// Validate method is static
if (!methodSymbol.IsStatic)
{
var diagnostic = Diagnostic.Create(
var diagnostic = DiagnosticInfo.Create(
MethodMustBeStaticRule,
location,
methodSymbol.Name);
Expand All @@ -111,7 +146,7 @@
// Validate method has at least one parameter
if (methodSymbol.Parameters.Length == 0)
{
var diagnostic = Diagnostic.Create(
var diagnostic = DiagnosticInfo.Create(
MethodMustHaveParametersRule,
location,
methodSymbol.Name);
Expand All @@ -122,7 +157,7 @@
var returnTypeInfo = AnalyzeReturnType(methodSymbol.ReturnType);
if (returnTypeInfo == null)
{
var diagnostic = Diagnostic.Create(
var diagnostic = DiagnosticInfo.Create(
UnsupportedReturnTypeRule,
location,
methodSymbol.Name,
Expand Down Expand Up @@ -301,7 +336,7 @@
{
if (param.IsRefStruct && string.IsNullOrEmpty(methodBody))
{
var diagnostic = Diagnostic.Create(
var diagnostic = DiagnosticInfo.Create(
RefStructRequiresInliningRule,
location,
methodSymbol.Name,
Expand Down Expand Up @@ -546,86 +581,74 @@

private static void GenerateAssertions(
SourceProductionContext context,
ImmutableArray<AssertionMethodData> methods)
ContainingTypeGroup group)
{
if (methods.IsEmpty)
{
return;
}
var containingType = group.ContainingType;
var methodGroup = group.Methods;

// Group by containing class to generate one file per class
foreach (var methodGroup in methods.GroupBy(m => m.Method.ContainingType?.FullContainingType))
{
var containingType = methodGroup.First().Method.ContainingType;
if (containingType == null)
{
continue;
}
var sourceBuilder = new StringBuilder();
// Always generate extension methods in TUnit.Assertions.Extensions namespace
// so they're available via implicit usings in consuming projects
var namespaceName = "TUnit.Assertions.Extensions";

var sourceBuilder = new StringBuilder();
// Always generate extension methods in TUnit.Assertions.Extensions namespace
// so they're available via implicit usings in consuming projects
var namespaceName = "TUnit.Assertions.Extensions";
// Get the original namespace where the helper methods are defined
var originalNamespace = containingType.ContainingNamespace;

// Get the original namespace where the helper methods are defined
var originalNamespace = containingType.ContainingNamespace;
// File header
sourceBuilder.AppendLine("// <auto-generated/>");
sourceBuilder.AppendLine("#pragma warning disable");
sourceBuilder.AppendLine("#nullable enable");
sourceBuilder.AppendLine();
sourceBuilder.AppendLine("using System;");
sourceBuilder.AppendLine("using System.Runtime.CompilerServices;");
sourceBuilder.AppendLine("using System.Threading.Tasks;");
sourceBuilder.AppendLine("using TUnit.Assertions.Core;");

// File header
sourceBuilder.AppendLine("// <auto-generated/>");
sourceBuilder.AppendLine("#pragma warning disable");
sourceBuilder.AppendLine("#nullable enable");
sourceBuilder.AppendLine();
sourceBuilder.AppendLine("using System;");
sourceBuilder.AppendLine("using System.Runtime.CompilerServices;");
sourceBuilder.AppendLine("using System.Threading.Tasks;");
sourceBuilder.AppendLine("using TUnit.Assertions.Core;");
// Add using for the original namespace to access helper methods
if (!string.IsNullOrEmpty(originalNamespace) && originalNamespace != namespaceName)
{
sourceBuilder.AppendLine($"using {originalNamespace};");
}

// Add using for the original namespace to access helper methods
if (!string.IsNullOrEmpty(originalNamespace) && originalNamespace != namespaceName)
{
sourceBuilder.AppendLine($"using {originalNamespace};");
}
sourceBuilder.AppendLine();

if (!string.IsNullOrEmpty(namespaceName))
{
sourceBuilder.AppendLine($"namespace {namespaceName};");
sourceBuilder.AppendLine();
}

if (!string.IsNullOrEmpty(namespaceName))
{
sourceBuilder.AppendLine($"namespace {namespaceName};");
sourceBuilder.AppendLine();
}

// Generate assertion classes
foreach (var methodData in methodGroup)
{
GenerateAssertionClass(sourceBuilder, methodData);
sourceBuilder.AppendLine();
}
// Generate assertion classes
foreach (var methodData in methodGroup)
{
GenerateAssertionClass(sourceBuilder, methodData);
sourceBuilder.AppendLine();
}

// Generate extension methods class
// For file-scoped types, we can't use partial classes, so create a standalone public class
// For non-file-scoped types, we use partial classes to combine with the source definition
var isFileScopedType = methodGroup.Any(m => m.IsFileScoped);
// Generate extension methods class
// For file-scoped types, we can't use partial classes, so create a standalone public class
// For non-file-scoped types, we use partial classes to combine with the source definition
var isFileScopedType = methodGroup.Any(m => m.IsFileScoped);

var extensionClassName = isFileScopedType
? $"{containingType.Name}Extensions" // Standalone class for file-scoped types
: containingType.Name; // Partial class for public types
var extensionClassName = isFileScopedType
? $"{containingType.Name}Extensions" // Standalone class for file-scoped types
: containingType.Name; // Partial class for public types

var partialModifier = isFileScopedType ? "" : "partial ";
sourceBuilder.AppendLine($"public static {partialModifier}class {extensionClassName}");
sourceBuilder.AppendLine("{");
var partialModifier = isFileScopedType ? "" : "partial ";
sourceBuilder.AppendLine($"public static {partialModifier}class {extensionClassName}");
sourceBuilder.AppendLine("{");

foreach (var methodData in methodGroup)
{
GenerateExtensionMethod(sourceBuilder, methodData);
sourceBuilder.AppendLine();
}
foreach (var methodData in methodGroup)
{
GenerateExtensionMethod(sourceBuilder, methodData);
sourceBuilder.AppendLine();
}

sourceBuilder.AppendLine("}");
sourceBuilder.AppendLine("}");

// Add source to compilation
var fileName = $"{containingType.Name}.GeneratedAssertions.g.cs";
context.AddSource(fileName, sourceBuilder.ToString());
}
// Add source to compilation
var fileName = $"{containingType.Name}.GeneratedAssertions.g.cs";
context.AddSource(fileName, sourceBuilder.ToString());
}

private static void GenerateAssertionClass(StringBuilder sb, AssertionMethodData data)
Expand Down Expand Up @@ -929,7 +952,7 @@
// Add null-forgiving operator for reference types if not already present
// This is safe because we've already checked for null above
var isNullable = data.TargetType.IsNullable;
if (isNullable && !string.IsNullOrEmpty(inlinedBody) && !inlinedBody.StartsWith("value!"))

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

View workflow job for this annotation

GitHub Actions / WASI smoke test

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

Déréférencement d'une éventuelle référence null.

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

View workflow job for this annotation

GitHub Actions / Browser smoke test

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / integration-tests

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / modularpipeline (de-DE)

Dereferenzierung eines möglichen Nullverweises.

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

View workflow job for this annotation

GitHub Actions / modularpipeline (pl-PL)

Wyłuskanie odwołania, które może mieć wartość null.

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

View workflow job for this annotation

GitHub Actions / Android smoke test

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / MacCatalyst smoke test

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / tvOS smoke test

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / iOS smoke test

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / modularpipeline (ubuntu-latest)

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / modularpipeline (windows-latest)

Dereference of a possibly null reference.

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

View workflow job for this annotation

GitHub Actions / modularpipeline (macos-latest)

Dereference of a possibly null reference.
{
// Replace null-conditional operators with null-forgiving + regular operators
// value?.Member becomes value!.Member (safe because we already null-checked)
Expand Down Expand Up @@ -1351,6 +1374,96 @@
ImmutableEquatableArray<string> DiagnosticAttributesForExtensionMethod
);

/// <summary>
/// All [GenerateAssertion] methods of one containing type; emitted as a single file.
/// </summary>
private sealed record ContainingTypeGroup(
ContainingTypeData ContainingType,
ImmutableEquatableArray<AssertionMethodData> Methods
);

/// <summary>
/// Equatable stand-in for a <see cref="Diagnostic"/>. A <see cref="Diagnostic"/> holds a
/// <see cref="Location"/> that references its <see cref="SyntaxTree"/>, which would keep old
/// compilations alive in the incremental cache; this captures only primitive location data.
/// </summary>
private sealed record DiagnosticInfo(
DiagnosticDescriptor Descriptor,
string? FilePath,
string? SourceChecksum,
TextSpan TextSpan,
LinePositionSpan LineSpan,
ImmutableEquatableArray<string> MessageArgs)
{
public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location location, params string[] messageArgs)
{
var lineSpan = location.GetLineSpan();
return new DiagnosticInfo(
descriptor,
lineSpan.Path,
location.SourceTree is { } tree ? GetChecksum(tree) : null,
location.SourceSpan,
lineSpan.Span,
messageArgs.ToImmutableEquatableArray());
}

private static string GetChecksum(SyntaxTree tree)
=> Convert.ToBase64String(tree.GetText().GetChecksum().ToArray());

public Diagnostic ToDiagnostic(Compilation compilation)
{
if (FilePath is null)
{
return Diagnostic.Create(Descriptor, Location.None, MessageArgs.Cast<object?>().ToArray());
}

// Reattach to the tree with the same path. Paths need not be unique (trees parsed without
// a path all share an empty one), so when several match, the text checksum picks the tree.
SyntaxTree? match = null;
var ambiguous = false;
foreach (var tree in compilation.SyntaxTrees)
{
if (!string.Equals(tree.FilePath, FilePath, StringComparison.Ordinal))
{
continue;
}

if (match is null)
{
match = tree;
}
else
{
ambiguous = true;
break;
}
}

if (ambiguous)
{
match = null;
if (SourceChecksum is not null)
{
foreach (var tree in compilation.SyntaxTrees)
{
if (string.Equals(tree.FilePath, FilePath, StringComparison.Ordinal)
&& string.Equals(GetChecksum(tree), SourceChecksum, StringComparison.Ordinal))
{
match = tree;
break;
}
}
}
}

var location = match is not null && TextSpan.End <= match.Length
? Location.Create(match, TextSpan)
: Location.Create(FilePath, TextSpan, LineSpan);

return Diagnostic.Create(Descriptor, location, MessageArgs.Cast<object?>().ToArray());
}
}

private record ContainingTypeData(
string Name,
string FullContainingType,
Expand Down
Loading
Loading