Skip to content

Commit 06b84a0

Browse files
authored
Analyzer fix for handlers declared on different partial classes (#6372)
* Analyzer fix for handlers declared on different partial classes * Update src/NServiceBus.Core.Analyzer.Tests.Common/Sagas/SagaAnalyzerTests.cs
1 parent de92a71 commit 06b84a0

File tree

2 files changed

+123
-7
lines changed

2 files changed

+123
-7
lines changed

src/NServiceBus.Core.Analyzer.Tests.Common/Sagas/SagaAnalyzerTests.cs

+109
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,115 @@ public Task Handle(StartSaga message, IMessageHandlerContext context)
10281028
return Assert(source);
10291029
}
10301030
}
1031+
1032+
// https://github.com/Particular/NServiceBus/issues/6370
1033+
[Test]
1034+
public Task SagaHandlersInPartialClasses()
1035+
{
1036+
var source =
1037+
@"
1038+
using System.Threading.Tasks;
1039+
using NServiceBus;
1040+
1041+
public partial class SagaImplementation : Saga<SagaData>, IAmStartedByMessages<SagaStartMessage>
1042+
{
1043+
protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SagaData> mapper)
1044+
{
1045+
mapper.MapSaga(s => s.OrderId)
1046+
.ToMessage<SagaStartMessage>(b => b.OrderId)
1047+
.ToMessage<SagaStep1>(b => b.OrderId)
1048+
.ToMessage<SagaStep2>(s => s.OrderId)
1049+
.ToMessage<SagaStep3>(b => b.OrderId);
1050+
}
1051+
1052+
public async Task Handle(SagaStartMessage message, IMessageHandlerContext context)
1053+
{
1054+
var options = new SendOptions();
1055+
options.RouteToThisEndpoint();
1056+
await context.Send(new SagaStep1 {OrderId = Data.OrderId}, options);
1057+
await context.Send(new SagaStep2 {OrderId = Data.OrderId}, options);
1058+
await context.Send(new SagaStep3 {OrderId = Data.OrderId}, options);
1059+
}
1060+
1061+
private void CompleteSaga()
1062+
{
1063+
if(Data.Step1Complete && Data.Step2Complete && Data.Step3Complete)
1064+
{
1065+
MarkAsComplete();
1066+
}
1067+
}
1068+
}
1069+
-----
1070+
using System.Threading.Tasks;
1071+
using NServiceBus;
1072+
1073+
public partial class SagaImplementation : IHandleMessages<SagaStep1>
1074+
{
1075+
public Task Handle(SagaStep1 message, IMessageHandlerContext context)
1076+
{
1077+
Data.Step1Complete = true;
1078+
CompleteSaga();
1079+
return Task.CompletedTask;
1080+
}
1081+
}
1082+
-----
1083+
using System.Threading.Tasks;
1084+
using NServiceBus;
1085+
1086+
public partial class SagaImplementation : IHandleMessages<SagaStep2>
1087+
{
1088+
public Task Handle(SagaStep2 message, IMessageHandlerContext context)
1089+
{
1090+
Data.Step2Complete = true;
1091+
CompleteSaga();
1092+
return Task.CompletedTask;
1093+
}
1094+
}
1095+
-----
1096+
using System.Threading.Tasks;
1097+
using NServiceBus;
1098+
1099+
public partial class SagaImplementation : IHandleMessages<SagaStep3>
1100+
{
1101+
public Task Handle(SagaStep3 message, IMessageHandlerContext context)
1102+
{
1103+
Data.Step3Complete = true;
1104+
CompleteSaga();
1105+
return Task.CompletedTask;
1106+
}
1107+
}
1108+
-----
1109+
using NServiceBus;
1110+
1111+
public class SagaData : ContainSagaData
1112+
{
1113+
public string OrderId { get; set; } = null!;
1114+
1115+
public bool Step1Complete { get; set; }
1116+
public bool Step2Complete { get; set; }
1117+
public bool Step3Complete { get; set; }
1118+
}
1119+
1120+
public class SagaStartMessage : ICommand
1121+
{
1122+
public string OrderId { get; set; } = null!;
1123+
}
1124+
1125+
public class SagaStep1 : SagaStartMessage
1126+
{
1127+
}
1128+
1129+
public class SagaStep2 : SagaStartMessage
1130+
{
1131+
}
1132+
1133+
public class SagaStep3 : SagaStartMessage
1134+
{
1135+
}
1136+
";
1137+
1138+
return Assert(source);
1139+
}
10311140
}
10321141

10331142
public class SagaAnalyzerTestsCSharp9 : SagaAnalyzerTestsCSharp8

src/NServiceBus.Core.Analyzer/Sagas/SagaAnalyzer.cs

+14-7
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ static void AnalyzeSagaClass(SyntaxNodeAnalysisContext context, ClassDeclaration
102102
}
103103
}
104104

105-
if (!TryGetSagaDetails(context, knownTypes, sagaType, out var saga))
105+
// Manages access to semantic models for other files, since those are expensive to create
106+
var semanticModels = new SemanticModelCache(context.Compilation, context.Node.SyntaxTree, context.SemanticModel);
107+
108+
if (!TryGetSagaDetails(context, semanticModels, knownTypes, sagaType, out var saga))
106109
{
107110
return;
108111
}
@@ -235,7 +238,11 @@ static void AnalyzeSagaClass(SyntaxNodeAnalysisContext context, ClassDeclaration
235238

236239
// Figure out which message types have a ConfigureHowToFindSaga mapping...
237240
var mappedMessageTypes = saga.MessageMappings
238-
.Select(m => context.SemanticModel.GetTypeInfo(m.MessageTypeSyntax).Type)
241+
.Select(m =>
242+
{
243+
var semanticModel = semanticModels.GetFor(m.MessageTypeSyntax);
244+
return semanticModel.GetTypeInfo(m.MessageTypeSyntax).Type;
245+
})
239246
.ToImmutableHashSet(SymbolEqualityComparer.Default); // Message types shouldn't need nullability annotations
240247

241248
// ...then find the IAmStartedBy message types that don't already have a mapping defined
@@ -325,7 +332,10 @@ static void AnalyzeSagaDataClass(SyntaxNodeAnalysisContext context, ClassDeclara
325332
return;
326333
}
327334

328-
if (!TryGetSagaDetails(context, knownTypes, sagaType, out var saga))
335+
// Manages access to semantic models for other files, since those are expensive to create
336+
var semanticModels = new SemanticModelCache(context.Compilation, context.Node.SyntaxTree, context.SemanticModel);
337+
338+
if (!TryGetSagaDetails(context, semanticModels, knownTypes, sagaType, out var saga))
329339
{
330340
return;
331341
}
@@ -487,7 +497,7 @@ static Diagnostic CreateMappingRewritingDiagnostic(
487497
return diagnostic;
488498
}
489499

490-
static bool TryGetSagaDetails(SyntaxNodeAnalysisContext context, KnownTypes knownTypes, INamedTypeSymbol sagaType, out SagaDetails saga)
500+
static bool TryGetSagaDetails(SyntaxNodeAnalysisContext context, SemanticModelCache semanticModels, KnownTypes knownTypes, INamedTypeSymbol sagaType, out SagaDetails saga)
491501
{
492502
saga = null;
493503

@@ -515,9 +525,6 @@ static bool TryGetSagaDetails(SyntaxNodeAnalysisContext context, KnownTypes know
515525
return false;
516526
}
517527

518-
// Manages access to semantic models for other files, since those are expensive to create
519-
var semanticModels = new SemanticModelCache(context.Compilation, context.Node.SyntaxTree, context.SemanticModel);
520-
521528
// From all the base lists on all partials, find the methods that are one of our IAmStarted/IHandle methods, then get
522529
// the TypeSymbol so we have both the syntax and type available
523530
var handlerDeclarations = classDeclarations

0 commit comments

Comments
 (0)