Skip to content
Open
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
14 changes: 14 additions & 0 deletions src/Mediator.SourceGenerator/Implementation/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ static Diagnostics()
isEnabledByDefault: true
);

NotificationPassedToSend = new DiagnosticDescriptor(
GetNextId(),
$"{nameof(MediatorGenerator)} notification passed to Send",
$"{nameof(MediatorGenerator)} found a notification passed to Send; use Publish instead.",
nameof(MediatorGenerator),
DiagnosticSeverity.Warning,
isEnabledByDefault: true
);

static string GetNextId()
{
var count = _counter++;
Expand Down Expand Up @@ -222,6 +231,11 @@ internal static Diagnostic ReportMessageWithoutHandler(
INamedTypeSymbol messageType
) => context.Report(MessageWithoutHandler, messageType);

public static readonly DiagnosticDescriptor NotificationPassedToSend;

internal static Diagnostic CreateNotificationPassedToSend(Location location) =>
Diagnostic.Create(NotificationPassedToSend, location);

public static readonly DiagnosticDescriptor ConflictingConfiguration;

internal static Diagnostic ReportConflictingConfiguration(
Expand Down
111 changes: 111 additions & 0 deletions src/Mediator.SourceGenerator/IncrementalMediatorGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Operations;

namespace Mediator.SourceGenerator;

Expand All @@ -15,6 +16,23 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
predicate: static (s, _) => SyntaxReceiver.ShouldVisit(s, out var _),
transform: static (ctx, _) => (InvocationExpressionSyntax)ctx.Node
);
var notificationSendDiagnostics = context
.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) =>
node
is InvocationExpressionSyntax
{
Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: "Send" },
ArgumentList.Arguments.Count: > 0
},
transform: static (context, cancellationToken) => AnalyzeNotificationSend(context, cancellationToken)
)
.Where(static diagnostic => diagnostic is not null);

context.RegisterSourceOutput(
notificationSendDiagnostics,
static (context, diagnostic) => context.ReportDiagnostic(diagnostic!)
);

IncrementalValueProvider<(
Compilation Compilation,
Expand Down Expand Up @@ -51,6 +69,99 @@ ImmutableArray<InvocationExpressionSyntax> AddMediatorCalls
);
}

private static Diagnostic? AnalyzeNotificationSend(
GeneratorSyntaxContext context,
CancellationToken cancellationToken
)
{
var invocation = (InvocationExpressionSyntax)context.Node;
var semanticModel = context.SemanticModel;
if (semanticModel.GetOperation(invocation, cancellationToken) is not IInvocationOperation operation)
return null;

var method = operation.TargetMethod;

var compilation = semanticModel.Compilation;
var senderType = compilation.GetTypeByMetadataName("Mediator.ISender");
var notificationType = compilation.GetTypeByMetadataName("Mediator.INotification");
if (senderType is null || notificationType is null)
return null;

if (!IsMediatorSendMethod(method, senderType))
return null;

var messageArgument = operation.Arguments.FirstOrDefault(argument => argument.Parameter?.Ordinal == 0);
var argumentValue = messageArgument?.Value;
while (argumentValue is IConversionOperation { IsImplicit: true } conversion)
argumentValue = conversion.Operand;

var argumentType = argumentValue?.Type;
if (argumentType is null || argumentType.TypeKind == TypeKind.Error)
return null;

if (!IsNotificationType(argumentType, notificationType))
return null;

return Diagnostics.CreateNotificationPassedToSend(argumentValue!.Syntax.GetLocation());
}

private static bool IsNotificationType(ITypeSymbol type, INamedTypeSymbol notificationType) =>
IsNotificationType(type, notificationType, new HashSet<ITypeSymbol>(SymbolEqualityComparer.Default));

private static bool IsNotificationType(
ITypeSymbol type,
INamedTypeSymbol notificationType,
HashSet<ITypeSymbol> visitedTypes
)
{
if (!visitedTypes.Add(type))
return false;

if (SymbolEqualityComparer.Default.Equals(type, notificationType))
return true;

if (
type is INamedTypeSymbol namedType
&& namedType.AllInterfaces.Contains(notificationType, SymbolEqualityComparer.Default)
)
{
return true;
}

return type is ITypeParameterSymbol typeParameter
&& typeParameter.ConstraintTypes.Any(constraintType =>
IsNotificationType(constraintType, notificationType, visitedTypes)
);
}

private static bool IsMediatorSendMethod(IMethodSymbol method, INamedTypeSymbol senderType)
{
var containingType = method.ContainingType;
if (
!SymbolEqualityComparer.Default.Equals(containingType, senderType)
&& !containingType.AllInterfaces.Contains(senderType, SymbolEqualityComparer.Default)
)
{
return false;
}

foreach (var senderMethod in senderType.GetMembers("Send").OfType<IMethodSymbol>())
{
if (SymbolEqualityComparer.Default.Equals(method.OriginalDefinition, senderMethod.OriginalDefinition))
return true;

if (
containingType.FindImplementationForInterfaceMember(senderMethod) is IMethodSymbol implementation
&& SymbolEqualityComparer.Default.Equals(method.OriginalDefinition, implementation.OriginalDefinition)
)
{
return true;
}
}

return false;
}

private (ImmutableEquatableArray<Diagnostic> Diagnostics, CompilationModel Model) Parse(
Compilation compilation,
IReadOnlyList<InvocationExpressionSyntax> addMediatorCalls,
Expand Down
Loading