From 73bac5aac8a637a0b5aef84688ebc8317a16184c Mon Sep 17 00:00:00 2001 From: Pascal Senn Date: Sun, 5 Jul 2026 23:53:27 +0200 Subject: [PATCH 1/2] Rework Configuration Pattern and add Source Metadata --- .../Mocha.Abstractions/DeclarationLocation.cs | 21 ++ .../src/Mocha.Abstractions/SourceMetadata.cs | 33 +++ .../DependencyInjectionFileBuilder.cs | 119 +++++--- .../FileBuilders/FileBuilderBase.cs | 41 ++- ...MessagingDependencyInjectionFileBuilder.cs | 267 ++++++++++++----- .../Filters/AddMessageCallSiteFilter.cs | 32 +++ .../DependencyInjectionGenerator.cs | 58 ++-- .../MessagingDependencyInjectionGenerator.cs | 271 ++++++++++++++++-- .../AddMessageDeclarationInspector.cs | 82 ++++++ .../CallSiteMessageTypeInspector.cs | 37 ++- .../Inspectors/HandlerInspector.cs | 6 +- .../Inspectors/MessagingHandlerInspector.cs | 25 +- .../NotificationHandlerInspector.cs | 5 +- .../Inspectors/SagaInspector.cs | 6 +- .../src/Mocha.Analyzers/KnownTypeSymbols.cs | 16 ++ .../src/Mocha.Analyzers/MediatorGenerator.cs | 62 ++-- .../src/Mocha.Analyzers/MessagingGenerator.cs | 165 ++++++++++- .../Models/AddMessageDeclarationInfo.cs | 19 ++ .../Models/CallSiteMessageTypeInfo.cs | 13 +- .../Models/ContextOnlyMessageInfo.cs | 2 + .../Models/DeclaredTypeInfo.cs | 22 ++ .../src/Mocha.Analyzers/Models/HandlerInfo.cs | 8 +- .../JsonContextSerializableTypesInfo.cs | 3 +- .../Models/JsonSerializableTypeInfo.cs | 9 +- .../Mocha.Analyzers/Models/LocationInfo.cs | 65 ++++- .../Models/MessageDeclarationsInfo.cs | 26 ++ .../Mocha.Analyzers/Models/MessageTypeInfo.cs | 4 +- .../Models/MessagingHandlerInfo.cs | 26 +- .../Models/NotificationHandlerInfo.cs | 8 +- .../src/Mocha.Analyzers/Models/SagaInfo.cs | 8 +- .../Models/SourceMetadataOptionsInfo.cs | 35 +++ src/Mocha/src/Mocha.Analyzers/README.md | 23 +- .../src/Mocha.Analyzers/SyntaxConstants.cs | 12 + .../Utils/DeduplicationHelper.cs | 45 +++ .../Utils/GeneratedMetadataWriter.cs | 222 ++++++++++++++ .../src/Mocha.Analyzers/Utils/HashHelper.cs | 3 + .../Mocha.Analyzers/Utils/RoslynExtensions.cs | 114 +++++++- .../buildTransitive/Mocha.Analyzers.props | 4 + .../MediatorHandlerConfiguration.cs | 5 + .../DependencyInjection/IMediatorBuilder.cs | 8 - .../DependencyInjection/MediatorBuilder.cs | 39 +-- .../MediatorHostBuilderHandlerExtensions.cs | 30 +- .../Descriptors/MediatorHandlerDescriptor.cs | 2 +- .../Features/FeatureCollectionExtensions.cs | 17 ++ .../Mocha/Abstractions/IMessageBusBuilder.cs | 40 +-- .../src/Mocha/Builder/ConsumerRegistration.cs | 25 -- .../src/Mocha/Builder/MessageBusBuilder.cs | 233 +++++---------- .../src/Mocha/Builder/MessageBusSetup.cs | 2 +- .../src/Mocha/Builder/SagaRegistration.cs | 25 -- .../MessagingConfigurationContainer.cs | 80 ++++++ ...MessagingConfigurationContextExtensions.cs | 21 ++ .../MessagingConfigurationFeature.cs | 9 + .../MessagingHandlerConfiguration.cs | 20 -- .../MessagingMessageConfiguration.cs | 32 --- .../MessagingSagaConfiguration.cs | 21 -- src/Mocha/src/Mocha/ConsumerFactory.cs | 31 +- .../Configurations/ConsumerConfiguration.cs | 5 + src/Mocha/src/Mocha/Consumers/Consumer.cs | 42 +-- .../Implementations/BatchConsumer.cs | 10 +- .../Implementations/ConsumerAdapter.cs | 20 +- .../Implementations/RequestConsumer.cs | 19 +- .../Consumers/Implementations/SendConsumer.cs | 19 +- .../Implementations/SubscribeConsumer.cs | 20 +- .../IMessageBusHostBuilderExtensions.cs | 108 ++++--- .../MessageTypeConfiguration.cs | 5 + .../src/Mocha/MessageTypes/MessageType.cs | 41 ++- .../Sagas/Definitions/SagaConfiguration.cs | 5 + .../src/Mocha/Sagas/Saga.Initialization.cs | 15 +- src/Mocha/src/Mocha/Sagas/Saga.cs | 10 +- .../src/Mocha/Sagas/SagaEventListener.cs | 9 +- .../Serialization/MessageTypeRegistry.cs | 7 +- .../CommandHandlerGeneratorTests.cs | 113 ++++++++ .../MessagingDiagnosticTests.cs | 104 +++++++ .../MessagingEventHandlerGeneratorTests.cs | 147 ++++++++++ .../MessagingIncrementalGeneratorTests.cs | 59 ++++ .../MessagingPartialClassHandlerTests.cs | 121 ++++++++ .../MessagingTestHelper.cs | 180 ++++++++---- .../PartialClassHandlerTests.cs | 105 +++++++ .../TestAnalyzerConfigOptionsProvider.cs | 37 +++ .../test/Mocha.Analyzers.Tests/TestHelper.cs | 32 ++- ...mandWithResponseHandler_MatchesSnapshot.md | 36 ++- ...MultipleCommandHandlers_MatchesSnapshot.md | 66 +++-- ...ectRelativePath_When_ProjectDirProvided.md | 64 +++++ ...sitoryUrlAndCommit_When_ProvidedByBuild.md | 66 +++++ ...When_MediatorHandlerHasXmlDocumentation.md | 64 +++++ ...etadata_When_EmitSourceMetadataDisabled.md | 52 ++++ ...rate_VoidCommandHandler_MatchesSnapshot.md | 34 ++- ...002_CommandWithTwoHandlers_ReportsError.md | 68 +++-- ...VoidCommandWithTwoHandlers_ReportsError.md | 64 +++-- ...ning_ClosedConcreteHandler_NoDiagnostic.md | 34 ++- ...Warning_CommandWithHandler_NoDiagnostic.md | 34 ++- ...ing_SingleHandlerInterface_NoDiagnostic.md | 34 ++- ...rate_ModuleWithOnlyName_MatchesSnapshot.md | 34 ++- ...pace_DeterministicOrder_MatchesSnapshot.md | 94 ++++-- ...rate_OpenGenericCommand_MatchesSnapshot.md | 36 ++- ...enerate_InternalHandler_MatchesSnapshot.md | 34 ++- ...olution_ICommandGeneric_MatchesSnapshot.md | 36 ++- ...ution_ICommandInterface_MatchesSnapshot.md | 34 ++- ...ypes_AllSymbolsResolved_MatchesSnapshot.md | 128 ++++++--- ...ultAssemblyName_PrefixesWithLastSegment.md | 36 ++- ...rate_DottedAssemblyName_UsesLastSegment.md | 34 ++- ...ModuleFile_ContainsHandlerRegistrations.md | 36 ++- ...erate_BatchEventHandler_MatchesSnapshot.md | 48 +++- ...Generate_SingleConsumer_MatchesSnapshot.md | 48 +++- ...11_DuplicateRequestHandler_ReportsError.md | 75 ++++- ...e_MultipleEventHandlers_MatchesSnapshot.md | 75 ++++- ...dlerMessageDocumentedWithoutJsonContext.md | 78 +++++ ...ectRelativePath_When_ProjectDirProvided.md | 100 +++++++ ...n_MessageAndHandlerHaveXmlDocumentation.md | 100 +++++++ ...ta_When_MessageOnlyComesFromJsonContext.md | 59 ++++ ...etadata_When_EmitSourceMetadataDisabled.md | 76 +++++ ...rate_SingleEventHandler_MatchesSnapshot.md | 48 +++- ...ageMetadata_When_OnlyMessageFileChanges.md | 103 +++++++ ...enerate_AllHandlerKinds_MatchesSnapshot.md | 202 +++++++++++-- ...leName_UsesAssemblyName_MatchesSnapshot.md | 48 +++- ...rate_ExplicitModuleName_MatchesSnapshot.md | 48 +++- ...dEvent_RegistersAsBatch_MatchesSnapshot.md | 48 +++- ...EventHandlerRepeatsInterfaceOnBothParts.md | 71 +++++ ...tialHandlerRestatesInterfaceOnBothParts.md | 71 +++++ ...n_PartialSagaRepeatsBaseTypeOnBothParts.md | 48 ++++ ..._RequestResponseHandler_MatchesSnapshot.md | 48 +++- ...ts.Generate_SendHandler_MatchesSnapshot.md | 48 +++- ...sts.Generate_SimpleSaga_MatchesSnapshot.md | 24 +- ...enerate_AllHandlerTypes_MatchesSnapshot.md | 158 +++++++--- ...rsInDifferentNamespaces_MatchesSnapshot.md | 68 +++-- ...sesLastSegmentSanitized_MatchesSnapshot.md | 34 ++- ...ame_UsesAssemblyDefault_MatchesSnapshot.md | 34 ++- ...rate_NestedClassHandler_MatchesSnapshot.md | 34 ++- ...lersForSameNotification_MatchesSnapshot.md | 64 +++-- ...ngleNotificationHandler_MatchesSnapshot.md | 34 ++- ...ate_PartialClassHandler_MatchesSnapshot.md | 36 ++- ...ueryHandler_AcrossFiles_MatchesSnapshot.md | 36 ++- ...mandHandler_AcrossFiles_MatchesSnapshot.md | 34 ++- ...rtialHandlerRepeatsInterfaceOnBothParts.md | 58 ++++ ...ationHandlerRepeatsInterfaceOnBothParts.md | 57 ++++ ...fferentHandlerTypesImplementSameCommand.md | 102 +++++++ ...e_MultipleQueryHandlers_MatchesSnapshot.md | 68 +++-- ...s.Generate_QueryHandler_MatchesSnapshot.md | 36 ++- ...hod_WithAllHandlerTypes_MatchesSnapshot.md | 128 ++++++--- .../SagaInitializationTests.cs | 94 ++++++ .../Consumers/ConsumerRegistrationTests.cs | 25 ++ .../Inbox/ConsumeInboxMiddlewareTests.cs | 5 - .../MessageTypes/AotMessageTypeTests.cs | 104 ++++--- .../Runtime/MessagingRuntimeTests.cs | 36 +++ ...gistrationTests.cs => SagaBuilderTests.cs} | 54 +++- 145 files changed, 6156 insertions(+), 1517 deletions(-) create mode 100644 src/Mocha/src/Mocha.Abstractions/DeclarationLocation.cs create mode 100644 src/Mocha/src/Mocha.Abstractions/SourceMetadata.cs create mode 100644 src/Mocha/src/Mocha.Analyzers/Filters/AddMessageCallSiteFilter.cs create mode 100644 src/Mocha/src/Mocha.Analyzers/Inspectors/AddMessageDeclarationInspector.cs create mode 100644 src/Mocha/src/Mocha.Analyzers/Models/AddMessageDeclarationInfo.cs create mode 100644 src/Mocha/src/Mocha.Analyzers/Models/DeclaredTypeInfo.cs create mode 100644 src/Mocha/src/Mocha.Analyzers/Models/MessageDeclarationsInfo.cs create mode 100644 src/Mocha/src/Mocha.Analyzers/Models/SourceMetadataOptionsInfo.cs create mode 100644 src/Mocha/src/Mocha.Analyzers/Utils/DeduplicationHelper.cs create mode 100644 src/Mocha/src/Mocha.Analyzers/Utils/GeneratedMetadataWriter.cs delete mode 100644 src/Mocha/src/Mocha/Builder/ConsumerRegistration.cs delete mode 100644 src/Mocha/src/Mocha/Builder/SagaRegistration.cs create mode 100644 src/Mocha/src/Mocha/Configuration/MessagingConfigurationContainer.cs create mode 100644 src/Mocha/src/Mocha/Configuration/MessagingConfigurationContextExtensions.cs create mode 100644 src/Mocha/src/Mocha/Configuration/MessagingConfigurationFeature.cs delete mode 100644 src/Mocha/src/Mocha/Configuration/MessagingHandlerConfiguration.cs delete mode 100644 src/Mocha/src/Mocha/Configuration/MessagingMessageConfiguration.cs delete mode 100644 src/Mocha/src/Mocha/Configuration/MessagingSagaConfiguration.cs create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/MessagingIncrementalGeneratorTests.cs create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/MessagingPartialClassHandlerTests.cs create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/TestAnalyzerConfigOptionsProvider.cs create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitRepositoryUrlAndCommit_When_ProvidedByBuild.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MediatorHandlerHasXmlDocumentation.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitAddMessageAndSource_When_HandlerMessageDocumentedWithoutJsonContext.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MessageAndHandlerHaveXmlDocumentation.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MessageOnlyComesFromJsonContext.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingIncrementalGeneratorTests.Generate_Should_RefreshMessageMetadata_When_OnlyMessageFileChanges.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleConsumerInitializer_When_PartialEventHandlerRepeatsInterfaceOnBothParts.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleInitializer_When_PartialHandlerRestatesInterfaceOnBothParts.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleSagaInitializer_When_PartialSagaRepeatsBaseTypeOnBothParts.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_EmitSingleHandlerInitializer_When_PartialHandlerRepeatsInterfaceOnBothParts.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_EmitSingleNotificationInitializer_When_PartialNotificationHandlerRepeatsInterfaceOnBothParts.md create mode 100644 src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_ReportDuplicateHandler_When_TwoDifferentHandlerTypesImplementSameCommand.md rename src/Mocha/test/Mocha.Tests/Sagas/{SagaRegistrationTests.cs => SagaBuilderTests.cs} (74%) diff --git a/src/Mocha/src/Mocha.Abstractions/DeclarationLocation.cs b/src/Mocha/src/Mocha.Abstractions/DeclarationLocation.cs new file mode 100644 index 00000000000..70c18c32f13 --- /dev/null +++ b/src/Mocha/src/Mocha.Abstractions/DeclarationLocation.cs @@ -0,0 +1,21 @@ +namespace Mocha; + +/// +/// Source declaration location captured by generated configuration metadata. +/// Line and column values are 1-based, matching editor coordinates, and the span covers +/// the entire declaration from its start to its end. +/// +/// +/// The source file path, relative to the project directory using forward slashes, +/// or the file name alone when a project-relative path is not available. +/// +/// The 1-based line number where the declaration starts. +/// The 1-based column number where the declaration starts. +/// The 1-based line number where the declaration ends. +/// The 1-based column number where the declaration ends. +public sealed record DeclarationLocation( + string File, + int StartLine, + int StartColumn, + int EndLine, + int EndColumn); diff --git a/src/Mocha/src/Mocha.Abstractions/SourceMetadata.cs b/src/Mocha/src/Mocha.Abstractions/SourceMetadata.cs new file mode 100644 index 00000000000..4968dcade88 --- /dev/null +++ b/src/Mocha/src/Mocha.Abstractions/SourceMetadata.cs @@ -0,0 +1,33 @@ +namespace Mocha; + +/// +/// Metadata captured from a source declaration. +/// +public sealed record SourceMetadata +{ + /// + /// The simple name of the assembly that declares the type. + /// + public string? Assembly { get; init; } + + /// + /// The source repository URL, or null when the build does not provide it. + /// + public string? RepositoryUrl { get; init; } + + /// + /// The commit identifier the source was built from, or null when the build does not provide it. + /// + public string? Commit { get; init; } + + /// + /// XML documentation captured from the source declaration. + /// + public string? XmlDocumentation { get; init; } + + /// + /// Location of the source declaration, using 1-based line and column coordinates. + /// See for the full contract. + /// + public DeclarationLocation? DeclarationLocation { get; init; } +} diff --git a/src/Mocha/src/Mocha.Analyzers/FileBuilders/DependencyInjectionFileBuilder.cs b/src/Mocha/src/Mocha.Analyzers/FileBuilders/DependencyInjectionFileBuilder.cs index f7758d3aea1..1e27a2c0ca0 100644 --- a/src/Mocha/src/Mocha.Analyzers/FileBuilders/DependencyInjectionFileBuilder.cs +++ b/src/Mocha/src/Mocha.Analyzers/FileBuilders/DependencyInjectionFileBuilder.cs @@ -10,11 +10,16 @@ public sealed class DependencyInjectionFileBuilder : FileBuilderBase { private readonly string _extensionsClassName; private readonly string _methodName; + private readonly SourceMetadataOptionsInfo _sourceMetadataOptions; - public DependencyInjectionFileBuilder(string moduleName, string assemblyName) : base(moduleName) + public DependencyInjectionFileBuilder( + string moduleName, + string assemblyName, + SourceMetadataOptionsInfo sourceMetadataOptions) : base(moduleName, assemblyName) { _extensionsClassName = moduleName + "MediatorBuilderExtensions"; _methodName = "Add" + moduleName; + _sourceMetadataOptions = sourceMetadataOptions; HintName = _extensionsClassName + "." + HashHelper.ComputeSalt(assemblyName); } @@ -27,7 +32,7 @@ public DependencyInjectionFileBuilder(string moduleName, string assemblyName) : public override void WriteBeginClass() { Writer.WriteGeneratedAttribute(); - Writer.WriteIndentedLine("public static class {0}", _extensionsClassName); + Writer.WriteIndentedLine("public static partial class {0}", _extensionsClassName); Writer.WriteIndentedLine("{"); Writer.IncreaseIndent(); } @@ -97,9 +102,32 @@ public void WriteBeginRegistrationMethod( } /// - /// Writes an AddHandlerConfiguration call for a command/query handler. + /// Writes a mediator handler registration that points at a generated initializer method. /// - public void WriteHandlerConfiguration(HandlerInfo handler) + public void WriteHandlerRegistration(string handlerTypeName, string initializerMethodName) + { + Writer.WriteIndentedLine("global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd("); + Writer.IncreaseIndent(); + Writer.WriteIndentedLine("builder.Services,"); + Writer.WriteIndentedLine("new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor("); + Writer.IncreaseIndent(); + Writer.WriteIndentedLine("typeof({0}),", handlerTypeName); + Writer.WriteIndentedLine("typeof({0}),", handlerTypeName); + Writer.WriteIndentedLine("builder.Options.ServiceLifetime));"); + Writer.DecreaseIndent(); + Writer.DecreaseIndent(); + + Writer.WriteIndentedLine("global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator("); + Writer.IncreaseIndent(); + Writer.WriteIndentedLine("builder,"); + Writer.WriteIndentedLine( + "static b => b.AddHandler<{0}>({1}));", + handlerTypeName, + initializerMethodName); + Writer.DecreaseIndent(); + } + + public void WriteHandlerInitializer(string methodName, HandlerInfo handler) { var (kindEnum, terminalCall) = handler.Kind switch { @@ -115,66 +143,75 @@ public void WriteHandlerConfiguration(HandlerInfo handler) _ => throw new ArgumentOutOfRangeException() }; - WriteAddHandlerConfiguration( - handler.HandlerTypeName, + WriteHandlerInitializer( + methodName, handler.MessageTypeName, handler.ResponseTypeName, kindEnum, - $"global::Mocha.Mediator.PipelineBuilder.{terminalCall}"); + $"global::Mocha.Mediator.PipelineBuilder.{terminalCall}", + handler.XmlDocumentation, + handler.Location); } - /// - /// Writes an AddHandlerConfiguration call for a notification handler. - /// - public void WriteNotificationHandlerConfiguration(string notificationType, NotificationHandlerInfo handler) - { - WriteAddHandlerConfiguration( - handler.HandlerTypeName, - notificationType, + public void WriteNotificationHandlerInitializer(string methodName, NotificationHandlerInfo handler) + => WriteHandlerInitializer( + methodName, + handler.NotificationTypeName, responseTypeName: null, "Notification", - $"global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline<{handler.HandlerTypeName}, {notificationType}>()"); + $"global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline<{handler.HandlerTypeName}, {handler.NotificationTypeName}>()", + handler.XmlDocumentation, + handler.Location); + + public void WriteSectionComment(string comment) + { + Writer.WriteLine(); + Writer.WriteIndentedLine("// {0}", comment); } - private void WriteAddHandlerConfiguration( - string handlerTypeName, + public void WriteEndRegistrationMethod() + { + Writer.WriteLine(); + Writer.WriteIndentedLine("return builder;"); + Writer.DecreaseIndent(); + Writer.WriteIndentedLine("}"); + } + + private void WriteHandlerInitializer( + string methodName, string messageTypeName, string? responseTypeName, string kindEnum, - string terminalCall) + string terminalCall, + string? xmlDocumentation, + LocationInfo? location) { + Writer.WriteLine(); Writer.WriteIndentedLine( - "global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration<{0}>(builder,", - handlerTypeName); - Writer.IncreaseIndent(); - Writer.WriteIndentedLine("new global::Mocha.Mediator.MediatorHandlerConfiguration"); + "private static void {0}(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor)", + methodName); Writer.WriteIndentedLine("{"); Writer.IncreaseIndent(); - Writer.WriteIndentedLine("HandlerType = typeof({0}),", handlerTypeName); - Writer.WriteIndentedLine("MessageType = typeof({0}),", messageTypeName); + Writer.WriteIndentedLine("var configuration = descriptor.Extend().Configuration;"); + Writer.WriteIndentedLine("configuration.MessageType = typeof({0});", messageTypeName); if (responseTypeName is not null) { - Writer.WriteIndentedLine("ResponseType = typeof({0}),", responseTypeName); + Writer.WriteIndentedLine("configuration.ResponseType = typeof({0});", responseTypeName); } - Writer.WriteIndentedLine("Kind = global::Mocha.Mediator.MediatorHandlerKind.{0},", kindEnum); - Writer.WriteIndentedLine("Delegate = {0}", terminalCall); - Writer.DecreaseIndent(); - Writer.WriteIndentedLine("});"); - Writer.DecreaseIndent(); - } + Writer.WriteIndentedLine("configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.{0};", kindEnum); + Writer.WriteIndentedLine("configuration.Delegate = {0};", terminalCall); - public void WriteSectionComment(string comment) - { - Writer.WriteLine(); - Writer.WriteIndentedLine("// {0}", comment); - } + Writer.WriteSourceMetadataAssignment( + "configuration.Source", + "global::Mocha.SourceMetadata", + "global::Mocha.DeclarationLocation", + AssemblyName, + xmlDocumentation, + location, + _sourceMetadataOptions); - public void WriteEndRegistrationMethod() - { - Writer.WriteLine(); - Writer.WriteIndentedLine("return builder;"); Writer.DecreaseIndent(); Writer.WriteIndentedLine("}"); } diff --git a/src/Mocha/src/Mocha.Analyzers/FileBuilders/FileBuilderBase.cs b/src/Mocha/src/Mocha.Analyzers/FileBuilders/FileBuilderBase.cs index 70cfabfb442..4ce0bb2a64d 100644 --- a/src/Mocha/src/Mocha.Analyzers/FileBuilders/FileBuilderBase.cs +++ b/src/Mocha/src/Mocha.Analyzers/FileBuilders/FileBuilderBase.cs @@ -10,13 +10,15 @@ namespace Mocha.Analyzers.FileBuilders; /// public abstract class FileBuilderBase : IFileBuilder { + private readonly string _assemblyName; private StringBuilder _sb; private CodeWriter _writer; private bool _disposed; - protected FileBuilderBase(string moduleName) + protected FileBuilderBase(string moduleName, string assemblyName) { ModuleName = moduleName; + _assemblyName = assemblyName; _sb = PooledObjects.GetStringBuilder(); _writer = new CodeWriter(_sb); } @@ -31,6 +33,11 @@ protected FileBuilderBase(string moduleName) /// protected string ModuleName { get; } + /// + /// Gets the simple name of the assembly being compiled. + /// + protected string AssemblyName => _assemblyName; + /// /// Gets the namespace to use in the generated source file. /// @@ -74,6 +81,14 @@ public string ToSourceText() return _sb.ToString(); } + public string CreateGeneratedMethodName(string runtimeTypeName, string suffix) + { + var runtimeName = GetRuntimeName(runtimeTypeName); + var salt = HashHelper.ComputeIdentifierSalt($"{_assemblyName}::{runtimeTypeName}::{suffix}"); + + return $"__Initialize_{runtimeName}_{suffix}_{salt}"; + } + /// public void Dispose() { @@ -88,4 +103,28 @@ public void Dispose() _writer = null!; _disposed = true; } + + private static string GetRuntimeName(string runtimeTypeName) + { + var text = RemoveGlobalPrefix(runtimeTypeName); + var separator = text.LastIndexOf('.'); + var name = separator < 0 ? text : text.Substring(separator + 1); + var builder = new StringBuilder(name.Length); + + foreach (var character in name) + { + builder.Append(char.IsLetterOrDigit(character) ? character : '_'); + } + + return builder.ToString(); + } + + protected static string RemoveGlobalPrefix(string typeName) + { + const string Prefix = "global::"; + + return typeName.StartsWith(Prefix, StringComparison.Ordinal) + ? typeName.Substring(Prefix.Length) + : typeName; + } } diff --git a/src/Mocha/src/Mocha.Analyzers/FileBuilders/MessagingDependencyInjectionFileBuilder.cs b/src/Mocha/src/Mocha.Analyzers/FileBuilders/MessagingDependencyInjectionFileBuilder.cs index e4b4ec385ab..c9d9aab549a 100644 --- a/src/Mocha/src/Mocha.Analyzers/FileBuilders/MessagingDependencyInjectionFileBuilder.cs +++ b/src/Mocha/src/Mocha.Analyzers/FileBuilders/MessagingDependencyInjectionFileBuilder.cs @@ -8,18 +8,26 @@ namespace Mocha.Analyzers.FileBuilders; /// public sealed class MessagingDependencyInjectionFileBuilder : FileBuilderBase { + private readonly string _assemblyName; private readonly string _extensionsClassName; private readonly string _methodName; + private readonly SourceMetadataOptionsInfo _sourceMetadataOptions; /// /// Initializes a new instance of the class. /// /// The module name used to prefix generated type names. /// The assembly name used to compute a unique file hint name. - public MessagingDependencyInjectionFileBuilder(string moduleName, string assemblyName) : base(moduleName) + /// The options controlling SourceMetadata emission and path relativization. + public MessagingDependencyInjectionFileBuilder( + string moduleName, + string assemblyName, + SourceMetadataOptionsInfo sourceMetadataOptions) : base(moduleName, assemblyName) { + _assemblyName = assemblyName; _extensionsClassName = moduleName + "MessageBusBuilderExtensions"; _methodName = "Add" + moduleName; + _sourceMetadataOptions = sourceMetadataOptions; HintName = _extensionsClassName + "." + HashHelper.ComputeSalt(assemblyName); } @@ -35,7 +43,7 @@ public MessagingDependencyInjectionFileBuilder(string moduleName, string assembl public override void WriteBeginClass() { Writer.WriteGeneratedAttribute(); - Writer.WriteIndentedLine("public static class {0}", _extensionsClassName); + Writer.WriteIndentedLine("public static partial class {0}", _extensionsClassName); Writer.WriteIndentedLine("{"); Writer.IncreaseIndent(); } @@ -140,87 +148,47 @@ public void WriteJsonTypeInfoResolverRegistration(string jsonContextTypeName) } /// - /// Writes a message configuration registration with a pre-built JSON serializer. + /// Writes a message configuration registration with a generated initializer method. /// /// The fully qualified message type name. - /// The fully qualified type name of the JsonSerializerContext. - /// - /// The pre-computed enclosed types sorted by specificity (including framework base types - /// such as IEventRequest and closed IEventRequest<T>), or - /// to omit. - /// - public void WriteMessageConfiguration( - string messageTypeName, - string jsonContextTypeName, - IReadOnlyList? enclosedTypes = null) + /// The generated initializer method name. + public void WriteMessageConfiguration(string messageTypeName, string initializerMethodName) { - Writer.WriteIndentedLine("global::Mocha.MessageBusHostBuilderExtensions.AddMessageConfiguration("); - Writer.IncreaseIndent(); - Writer.WriteIndentedLine("builder,"); - Writer.WriteIndentedLine("new global::Mocha.MessagingMessageConfiguration"); - Writer.WriteIndentedLine("{"); - Writer.IncreaseIndent(); - Writer.WriteIndentedLine("MessageType = typeof({0}),", messageTypeName); - Writer.WriteIndentedLine("Serializer = new global::Mocha.JsonMessageSerializer("); - Writer.IncreaseIndent(); - Writer.WriteIndentedLine("{0}.Default.GetTypeInfo(typeof({1}))!),", jsonContextTypeName, messageTypeName); - Writer.DecreaseIndent(); + WriteAddMessage(messageTypeName); + WriteMessageDescriptor(messageTypeName, initializerMethodName); + } - if (enclosedTypes is { Count: > 0 }) - { - Writer.WriteIndentedLine( - "EnclosedTypes = global::System.Collections.Immutable.ImmutableArray.Create("); - Writer.IncreaseIndent(); - for (var i = 0; i < enclosedTypes.Count; i++) - { - var typeName = enclosedTypes[i]; - var isLast = i == enclosedTypes.Count - 1; - Writer.WriteIndentedLine(isLast ? "typeof({0})" : "typeof({0}),", typeName); - } - Writer.DecreaseIndent(); - Writer.WriteIndentedLine("),"); - } + /// + /// Writes the AddMessage<TMessage> registration call for the specified message type. + /// + /// The fully qualified message type name. + public void WriteAddMessage(string messageTypeName) + => Writer.WriteIndentedLine( + "global::Mocha.MessageBusHostBuilderExtensions.AddMessage<{0}>(builder);", + messageTypeName); - Writer.DecreaseIndent(); - Writer.WriteIndentedLine("});"); - Writer.DecreaseIndent(); - } + /// + /// Writes the descriptor callback registration that points at a generated message initializer method. + /// + /// The fully qualified message type name. + /// The generated initializer method name. + public void WriteMessageDescriptor(string messageTypeName, string initializerMethodName) + => WriteDescriptorConfiguration("global::Mocha.IMessageTypeDescriptor", messageTypeName, initializerMethodName); /// - /// Writes a saga configuration registration. When a - /// is provided, includes a pre-built JSON state serializer; otherwise emits a configuration - /// with only the saga type. + /// Writes a saga configuration registration that points at a generated initializer method. /// /// The fully qualified saga type name. /// The fully qualified saga state type name. - /// - /// The fully qualified type name of the JsonSerializerContext, or - /// to omit the state serializer. - /// - public void WriteSagaConfiguration(string sagaTypeName, string stateTypeName, string? jsonContextTypeName) + /// The generated initializer method name. + public void WriteSagaConfiguration(string sagaTypeName, string stateTypeName, string initializerMethodName) { - Writer.WriteIndentedLine("global::Mocha.MessageBusHostBuilderExtensions.AddSagaConfiguration<"); - Writer.IncreaseIndent(); - Writer.WriteIndentedLine("{0}>(", sagaTypeName); - Writer.DecreaseIndent(); - Writer.IncreaseIndent(); - Writer.WriteIndentedLine("builder,"); - Writer.WriteIndentedLine("new global::Mocha.MessagingSagaConfiguration"); - Writer.WriteIndentedLine("{"); - Writer.IncreaseIndent(); - Writer.WriteIndentedLine("SagaType = typeof({0}),", sagaTypeName); - - if (jsonContextTypeName is not null) - { - Writer.WriteIndentedLine("StateSerializer = new global::Mocha.Sagas.JsonSagaStateSerializer("); - Writer.IncreaseIndent(); - Writer.WriteIndentedLine("{0}.Default.GetTypeInfo(typeof({1}))!),", jsonContextTypeName, stateTypeName); - Writer.DecreaseIndent(); - } + Writer.WriteIndentedLine("global::Mocha.MessageBusHostBuilderExtensions.AddSaga<{0}>(builder);", sagaTypeName); - Writer.DecreaseIndent(); - Writer.WriteIndentedLine("});"); - Writer.DecreaseIndent(); + WriteDescriptorConfiguration( + $"global::Mocha.Sagas.ISagaDescriptor<{stateTypeName}>", + sagaTypeName, + initializerMethodName); } /// @@ -229,7 +197,7 @@ public void WriteSagaConfiguration(string sagaTypeName, string stateTypeName, st /// The messaging handler info to register. public void WriteHandlerRegistration(MessagingHandlerInfo handler) { - var factoryCall = handler.Kind switch + var consumerCall = handler.Kind switch { MessagingHandlerKind.Event => $"Subscribe<{handler.HandlerTypeName}, {handler.MessageTypeName}>()", MessagingHandlerKind.Send => $"Send<{handler.HandlerTypeName}, {handler.MessageTypeName}>()", @@ -241,19 +209,148 @@ public void WriteHandlerRegistration(MessagingHandlerInfo handler) }; Writer.WriteIndentedLine( - "global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration<{0}>(builder,", + "global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped<{0}>(", handler.HandlerTypeName); Writer.IncreaseIndent(); - Writer.WriteIndentedLine("new global::Mocha.MessagingHandlerConfiguration"); - Writer.WriteIndentedLine("{"); + Writer.WriteIndentedLine("builder.Services);"); + Writer.DecreaseIndent(); + + Writer.WriteIndentedLine("global::Mocha.MessageBusHostBuilderExtensions.AddConsumer("); Writer.IncreaseIndent(); - Writer.WriteIndentedLine("HandlerType = typeof({0}),", handler.HandlerTypeName); - Writer.WriteIndentedLine("Factory = global::Mocha.ConsumerFactory.{0}", factoryCall); + Writer.WriteIndentedLine("builder,"); + Writer.WriteIndentedLine("static () => global::Mocha.ConsumerFactory.{0});", consumerCall); + Writer.DecreaseIndent(); + } + + public void WriteConsumerConfiguration(MessagingHandlerInfo handler, string initializerMethodName) + => WriteDescriptorConfiguration( + "global::Mocha.IConsumerDescriptor", + handler.HandlerTypeName, + initializerMethodName); + + private void WriteDescriptorConfiguration( + string descriptorTypeName, + string runtimeTypeName, + string initializerMethodName) + { + var keyLiteral = GetConfigurationKey(runtimeTypeName).ToSourceStringLiteral(); + + Writer.WriteIndentedLine("global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext("); + Writer.IncreaseIndent(); + Writer.WriteIndentedLine("builder,"); + Writer.WriteIndentedLine( + "static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features)"); + Writer.IncreaseIndent(); + + Writer.WriteIndentedLine(".Configurations.TryAdd<{0}>(", descriptorTypeName); + Writer.IncreaseIndent(); + Writer.WriteIndentedLine("{0},", keyLiteral); + Writer.WriteIndentedLine("typeof({0}),", runtimeTypeName); + + Writer.WriteIndentedLine("{0}));", initializerMethodName); + Writer.DecreaseIndent(); Writer.DecreaseIndent(); - Writer.WriteIndentedLine("});"); Writer.DecreaseIndent(); } + public void WriteMessageInitializer( + string methodName, + string messageTypeName, + string? jsonContextTypeName, + IReadOnlyList? enclosedTypes, + string? xmlDocumentation, + LocationInfo? location) + { + WriteBeginInitializerMethod(methodName, "global::Mocha.IMessageTypeDescriptor"); + + // The serializer and enclosed types are emitted only for types covered by a JsonSerializerContext. + // Metadata-only registrations (no context) carry Source alone. + if (jsonContextTypeName is not null) + { + Writer.WriteIndentedLine("descriptor.AddSerializer(new global::Mocha.JsonMessageSerializer("); + Writer.IncreaseIndent(); + Writer.WriteIndentedLine("{0}.Default.GetTypeInfo(typeof({1}))!));", jsonContextTypeName, messageTypeName); + Writer.DecreaseIndent(); + + if (enclosedTypes is { Count: > 0 }) + { + Writer.WriteIndentedLine("descriptor.Extend().Configuration.EnclosedTypes ="); + Writer.IncreaseIndent(); + Writer.WriteIndentedLine( + "global::System.Collections.Immutable.ImmutableArray.Create("); + Writer.IncreaseIndent(); + for (var i = 0; i < enclosedTypes.Count; i++) + { + var typeName = enclosedTypes[i]; + var isLast = i == enclosedTypes.Count - 1; + Writer.WriteIndentedLine(isLast ? "typeof({0})" : "typeof({0}),", typeName); + } + Writer.DecreaseIndent(); + Writer.WriteIndentedLine(");"); + Writer.DecreaseIndent(); + } + } + + Writer.WriteSourceMetadataAssignment( + "descriptor.Extend().Configuration.Source", + "global::Mocha.SourceMetadata", + "global::Mocha.DeclarationLocation", + _assemblyName, + xmlDocumentation, + location, + _sourceMetadataOptions); + + WriteEndInitializerMethod(); + } + + public void WriteSagaInitializer( + string methodName, + string stateTypeName, + string? jsonContextTypeName, + string? xmlDocumentation, + LocationInfo? location) + { + WriteBeginInitializerMethod(methodName, $"global::Mocha.Sagas.ISagaDescriptor<{stateTypeName}>"); + + if (jsonContextTypeName is not null) + { + Writer.WriteIndentedLine( + "descriptor.Serializer(static _ => new global::Mocha.Sagas.JsonSagaStateSerializer("); + Writer.IncreaseIndent(); + Writer.WriteIndentedLine("{0}.Default.GetTypeInfo(typeof({1}))!));", jsonContextTypeName, stateTypeName); + Writer.DecreaseIndent(); + } + + Writer.WriteSourceMetadataAssignment( + "descriptor.Extend().Configuration.Source", + "global::Mocha.SourceMetadata", + "global::Mocha.DeclarationLocation", + _assemblyName, + xmlDocumentation, + location, + _sourceMetadataOptions); + + WriteEndInitializerMethod(); + } + + public void WriteConsumerInitializer(string methodName, string? xmlDocumentation, LocationInfo? location) + { + WriteBeginInitializerMethod(methodName, "global::Mocha.IConsumerDescriptor"); + + Writer.WriteSourceMetadataAssignment( + "descriptor.Extend().Configuration.Source", + "global::Mocha.SourceMetadata", + "global::Mocha.DeclarationLocation", + _assemblyName, + xmlDocumentation, + location, + _sourceMetadataOptions); + + WriteEndInitializerMethod(); + } + + private string GetConfigurationKey(string typeName) => $"{_assemblyName}::{RemoveGlobalPrefix(typeName)}"; + /// /// Writes a section comment to visually separate groups of registrations. /// @@ -274,4 +371,18 @@ public void WriteEndRegistrationMethod() Writer.DecreaseIndent(); Writer.WriteIndentedLine("}"); } + + private void WriteBeginInitializerMethod(string methodName, string descriptorTypeName) + { + Writer.WriteLine(); + Writer.WriteIndentedLine("private static void {0}({1} descriptor)", methodName, descriptorTypeName); + Writer.WriteIndentedLine("{"); + Writer.IncreaseIndent(); + } + + private void WriteEndInitializerMethod() + { + Writer.DecreaseIndent(); + Writer.WriteIndentedLine("}"); + } } diff --git a/src/Mocha/src/Mocha.Analyzers/Filters/AddMessageCallSiteFilter.cs b/src/Mocha/src/Mocha.Analyzers/Filters/AddMessageCallSiteFilter.cs new file mode 100644 index 00000000000..c4c822eb4c8 --- /dev/null +++ b/src/Mocha/src/Mocha.Analyzers/Filters/AddMessageCallSiteFilter.cs @@ -0,0 +1,32 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Mocha.Analyzers.Filters; + +/// +/// Provides a singleton that matches invocation expressions whose member-access +/// method name is AddMessage. This is a cheap syntactic pre-screen for explicit message registrations +/// (builder.AddMessage<T>()); the inspector then narrows to the Mocha builder APIs semantically. +/// +public sealed class AddMessageCallSiteFilter : ISyntaxFilter +{ + private AddMessageCallSiteFilter() { } + + /// + public bool IsMatch(SyntaxNode node) + => node is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccess } + && GetMethodName(memberAccess) == "AddMessage"; + + /// + /// Gets the singleton instance of . + /// + public static AddMessageCallSiteFilter Instance { get; } = new(); + + private static string? GetMethodName(MemberAccessExpressionSyntax memberAccess) + => memberAccess.Name switch + { + GenericNameSyntax generic => generic.Identifier.Text, + IdentifierNameSyntax identifier => identifier.Identifier.Text, + _ => null + }; +} diff --git a/src/Mocha/src/Mocha.Analyzers/Generators/DependencyInjectionGenerator.cs b/src/Mocha/src/Mocha.Analyzers/Generators/DependencyInjectionGenerator.cs index 70c15829f21..925301d79d7 100644 --- a/src/Mocha/src/Mocha.Analyzers/Generators/DependencyInjectionGenerator.cs +++ b/src/Mocha/src/Mocha.Analyzers/Generators/DependencyInjectionGenerator.cs @@ -1,6 +1,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Mocha.Analyzers.FileBuilders; +using Mocha.Analyzers.Utils; namespace Mocha.Analyzers.Generators; @@ -18,15 +19,21 @@ public void Generate( ImmutableArray syntaxInfos, Action addSource) { - var handlers = syntaxInfos - .OfType() - .Where(h => h.Diagnostics.Count == 0) + var handlers = DeduplicationHelper + .SelectRepresentatives( + syntaxInfos.OfType().Where(h => h.Diagnostics.Count == 0), + h => h.HandlerTypeName, + h => h.XmlDocumentation, + h => h.Location) .OrderBy(h => h.OrderByKey) .ToList(); - var notificationHandlers = syntaxInfos - .OfType() - .Where(h => h.Diagnostics.Count == 0) + var notificationHandlers = DeduplicationHelper + .SelectRepresentatives( + syntaxInfos.OfType().Where(h => h.Diagnostics.Count == 0), + h => h.HandlerTypeName, + h => h.XmlDocumentation, + h => h.Location) .OrderBy(h => h.OrderByKey) .ToList(); @@ -48,9 +55,7 @@ public void Generate( allMessageTypeNames.Add(handler.NotificationTypeName); } - var sortedMessageTypeNames = allMessageTypeNames - .OrderBy(t => t, StringComparer.Ordinal) - .ToList(); + var sortedMessageTypeNames = allMessageTypeNames.OrderBy(t => t, StringComparer.Ordinal).ToList(); var sortedHandlerTypeNames = handlers .Select(h => h.HandlerTypeName) @@ -59,38 +64,55 @@ public void Generate( .OrderBy(t => t, StringComparer.Ordinal) .ToList(); - using var builder = new DependencyInjectionFileBuilder(moduleName, assemblyName); + var sourceMetadataOptions = + syntaxInfos.OfType().FirstOrDefault() ?? SourceMetadataOptionsInfo.Default; - var notificationGroups = notificationHandlers - .GroupBy(h => h.NotificationTypeName) - .OrderBy(g => g.Key) - .ToList(); + using var builder = new DependencyInjectionFileBuilder(moduleName, assemblyName, sourceMetadataOptions); + + var notificationGroups = notificationHandlers.GroupBy(h => h.NotificationTypeName).OrderBy(g => g.Key).ToList(); builder.WriteHeader(); builder.WriteBeginNamespace(); builder.WriteBeginClass(); builder.WriteBeginRegistrationMethod(sortedMessageTypeNames, sortedHandlerTypeNames); - // Register all handler configurations if (handlers.Count > 0 || notificationGroups.Count > 0) { - builder.WriteSectionComment("Register handler configurations"); + builder.WriteSectionComment("Register handlers"); foreach (var handler in handlers) { - builder.WriteHandlerConfiguration(handler); + var methodName = builder.CreateGeneratedMethodName(handler.HandlerTypeName, "Handler"); + builder.WriteHandlerRegistration(handler.HandlerTypeName, methodName); } foreach (var group in notificationGroups) { foreach (var handler in group.OrderBy(h => h.HandlerTypeName)) { - builder.WriteNotificationHandlerConfiguration(group.Key, handler); + var methodName = builder.CreateGeneratedMethodName(handler.HandlerTypeName, "Handler"); + builder.WriteHandlerRegistration(handler.HandlerTypeName, methodName); } } } builder.WriteEndRegistrationMethod(); + + foreach (var handler in handlers) + { + var methodName = builder.CreateGeneratedMethodName(handler.HandlerTypeName, "Handler"); + builder.WriteHandlerInitializer(methodName, handler); + } + + foreach (var group in notificationGroups) + { + foreach (var handler in group.OrderBy(h => h.HandlerTypeName)) + { + var methodName = builder.CreateGeneratedMethodName(handler.HandlerTypeName, "Handler"); + builder.WriteNotificationHandlerInitializer(methodName, handler); + } + } + builder.WriteEndClass(); builder.WriteEndNamespace(); diff --git a/src/Mocha/src/Mocha.Analyzers/Generators/MessagingDependencyInjectionGenerator.cs b/src/Mocha/src/Mocha.Analyzers/Generators/MessagingDependencyInjectionGenerator.cs index cd4e6c050bb..3742c019af8 100644 --- a/src/Mocha/src/Mocha.Analyzers/Generators/MessagingDependencyInjectionGenerator.cs +++ b/src/Mocha/src/Mocha.Analyzers/Generators/MessagingDependencyInjectionGenerator.cs @@ -20,22 +20,25 @@ public void Generate( ImmutableArray syntaxInfos, Action addSource) { - var handlers = syntaxInfos - .OfType() - .Where(h => h.Diagnostics.Count == 0) + var handlers = DeduplicationHelper + .SelectRepresentatives( + syntaxInfos.OfType().Where(h => h.Diagnostics.Count == 0), + h => h.HandlerTypeName, + h => h.XmlDocumentation, + h => h.Location) .OrderBy(h => h.OrderByKey) .ToList(); - var sagas = syntaxInfos - .OfType() - .Where(s => s.Diagnostics.Count == 0) + var sagas = DeduplicationHelper + .SelectRepresentatives( + syntaxInfos.OfType().Where(s => s.Diagnostics.Count == 0), + s => s.SagaTypeName, + s => s.XmlDocumentation, + s => s.Location) .OrderBy(s => s.OrderByKey) .ToList(); - var contextOnlyTypes = syntaxInfos - .OfType() - .OrderBy(c => c.OrderByKey) - .ToList(); + var contextOnlyTypes = syntaxInfos.OfType().OrderBy(c => c.OrderByKey).ToList(); if (handlers.Count == 0 && sagas.Count == 0 && contextOnlyTypes.Count == 0) { @@ -45,6 +48,7 @@ public void Generate( // Find the module info to check for JsonContext. string? jsonContextTypeName = null; var isAotPublish = false; + SourceMetadataOptionsInfo? sourceMetadataOptions = null; foreach (var info in syntaxInfos) { @@ -57,8 +61,15 @@ public void Generate( { isAotPublish = aotPublishInfo.IsAotPublish; } + + if (info is SourceMetadataOptionsInfo optionsInfo) + { + sourceMetadataOptions = optionsInfo; + } } + sourceMetadataOptions ??= SourceMetadataOptionsInfo.Default; + // Collect type names imported from module methods invoked in this compilation. // Those module methods have already registered their own message, saga, and handler metadata. var importedTypeNames = new HashSet(StringComparer.Ordinal); @@ -96,9 +107,39 @@ public void Generate( } } - // Collect all unique message types that this module actually registers - // serializers for via AddMessageConfiguration. Only types in the local - // JsonSerializerContext and not already registered by invoked modules qualify. + // Collect per-type declaration metadata (doc + span) captured in the declaration pipeline, + // keyed by fully qualified type name for the message/response join in GetMessages. + var declaredTypesByName = new Dictionary(StringComparer.Ordinal); + + // Message types the user registers explicitly via AddMessage(). The generated module emits only the + // descriptor callback for these; the user's own call performs the AddMessage registration. + var explicitAddMessageTypeNames = new HashSet(StringComparer.Ordinal); + + foreach (var info in syntaxInfos) + { + if (info is MessageDeclarationsInfo messageDeclarations) + { + foreach (var declared in messageDeclarations.Declarations) + { + if (!declaredTypesByName.ContainsKey(declared.TypeName)) + { + declaredTypesByName.Add(declared.TypeName, declared); + } + } + + foreach (var typeName in messageDeclarations.ExplicitAddMessageTypeNames) + { + explicitAddMessageTypeNames.Add(typeName); + } + } + } + + var messages = GetMessages(handlers, contextOnlyTypes, importedTypeNames, declaredTypesByName); + var messagesByTypeName = messages.ToDictionary(m => m.MessageTypeName, StringComparer.Ordinal); + + // Collect all unique message types that this module registers with + // generated serializer configuration. Only types in the local JsonSerializerContext + // and not already registered by invoked modules qualify. var messageTypes = new HashSet(StringComparer.Ordinal); if (jsonContextTypeName is not null) @@ -128,9 +169,7 @@ public void Generate( } } - var sortedMessageTypeNames = messageTypes - .OrderBy(t => t, StringComparer.Ordinal) - .ToList(); + var sortedMessageTypeNames = messageTypes.OrderBy(t => t, StringComparer.Ordinal).ToList(); var sortedSagaTypeNames = sagas .Select(s => s.SagaTypeName) @@ -146,7 +185,29 @@ public void Generate( .OrderBy(t => t, StringComparer.Ordinal) .ToList(); - using var builder = new MessagingDependencyInjectionFileBuilder(moduleName, assemblyName); + // Message types discovered from handlers and dispatch call sites that are declared in this compilation + // but are not covered by the local JsonSerializerContext. They get AddMessage plus a Source-only + // descriptor callback, without a serializer or enclosed types. Saga state types are excluded (they are + // registered through the saga path, not as message types) as are types already covered by the context + // path or by an invoked module. + var sagaStateTypeNames = new HashSet( + sagas.Select(s => s.StateTypeName), + StringComparer.Ordinal); + + var metadataOnlyMessageTypeNames = declaredTypesByName.Keys + .Where(t => !messageTypes.Contains(t) + && !importedTypeNames.Contains(t) + && !sagaStateTypeNames.Contains(t)) + .OrderBy(t => t, StringComparer.Ordinal) + .ToList(); + + using var builder = new MessagingDependencyInjectionFileBuilder( + moduleName, + assemblyName, + sourceMetadataOptions); + var messageInitializers = new List(); + var sagaInitializers = new List(); + var consumerInitializers = new List(); builder.WriteHeader(); builder.WriteBeginNamespace(); @@ -241,10 +302,50 @@ public void Generate( foreach (var messageType in messageTypes.OrderBy(t => t, StringComparer.Ordinal)) { enclosedTypesMap.TryGetValue(messageType, out var enclosedTypes); - builder.WriteMessageConfiguration( - messageType, - jsonContextTypeName, - enclosedTypes); + messagesByTypeName.TryGetValue(messageType, out var messageMetadata); + var methodName = builder.CreateGeneratedMethodName(messageType, "MessageType"); + messageInitializers.Add( + new MessageInitializer( + methodName, + messageType, + jsonContextTypeName, + enclosedTypes, + messageMetadata?.XmlDocumentation, + messageMetadata?.Location)); + + builder.WriteMessageConfiguration(messageType, methodName); + } + } + + if (metadataOnlyMessageTypeNames.Count > 0) + { + builder.WriteSectionComment("Message Types"); + + foreach (var messageType in metadataOnlyMessageTypeNames) + { + // Types the user already registers with an explicit AddMessage() get only the descriptor + // callback; every other declared message type also gets a generated AddMessage. + if (!explicitAddMessageTypeNames.Contains(messageType)) + { + builder.WriteAddMessage(messageType); + } + + // Every metadata-only type carries a declaration span, so a Source-only descriptor callback is + // emitted whenever SourceMetadata emission is enabled. + if (sourceMetadataOptions.Emit && declaredTypesByName.TryGetValue(messageType, out var declared)) + { + var methodName = builder.CreateGeneratedMethodName(messageType, "MessageType"); + messageInitializers.Add( + new MessageInitializer( + methodName, + messageType, + null, + null, + declared.XmlDocumentation, + declared.Location)); + + builder.WriteMessageDescriptor(messageType, methodName); + } } } @@ -257,8 +358,10 @@ public void Generate( var sagaJsonContextTypeName = jsonContextTypeNames.Contains(saga.StateTypeName) ? jsonContextTypeName : null; + var methodName = builder.CreateGeneratedMethodName(saga.SagaTypeName, "Saga"); + sagaInitializers.Add(new SagaInitializer(methodName, saga, sagaJsonContextTypeName)); - builder.WriteSagaConfiguration(saga.SagaTypeName, saga.StateTypeName, sagaJsonContextTypeName); + builder.WriteSagaConfiguration(saga.SagaTypeName, saga.StateTypeName, methodName); } } @@ -274,6 +377,7 @@ public void Generate( foreach (var handler in batchHandlers) { builder.WriteHandlerRegistration(handler); + WriteConsumerConfiguration(builder, handler, consumerInitializers); } } @@ -289,6 +393,7 @@ public void Generate( foreach (var handler in consumers) { builder.WriteHandlerRegistration(handler); + WriteConsumerConfiguration(builder, handler, consumerInitializers); } } @@ -304,6 +409,7 @@ public void Generate( foreach (var handler in requestHandlers) { builder.WriteHandlerRegistration(handler); + WriteConsumerConfiguration(builder, handler, consumerInitializers); } } @@ -319,10 +425,41 @@ public void Generate( foreach (var handler in eventHandlers) { builder.WriteHandlerRegistration(handler); + WriteConsumerConfiguration(builder, handler, consumerInitializers); } } builder.WriteEndRegistrationMethod(); + + foreach (var initializer in messageInitializers) + { + builder.WriteMessageInitializer( + initializer.MethodName, + initializer.MessageTypeName, + initializer.JsonContextTypeName, + initializer.EnclosedTypes, + initializer.XmlDocumentation, + initializer.Location); + } + + foreach (var initializer in sagaInitializers) + { + builder.WriteSagaInitializer( + initializer.MethodName, + initializer.Saga.StateTypeName, + initializer.JsonContextTypeName, + initializer.Saga.XmlDocumentation, + initializer.Saga.Location); + } + + foreach (var initializer in consumerInitializers) + { + builder.WriteConsumerInitializer( + initializer.MethodName, + initializer.Handler.XmlDocumentation, + initializer.Handler.DeclarationLocation); + } + builder.WriteEndClass(); builder.WriteEndNamespace(); @@ -344,4 +481,94 @@ private static bool IsFrameworkBaseType(string fullyQualifiedName) return fullyQualifiedName.StartsWith(SyntaxConstants.IEventRequestOfTDisplayPrefix, StringComparison.Ordinal) && fullyQualifiedName.EndsWith(">", StringComparison.Ordinal); } + + private static List GetMessages( + List handlers, + List contextOnlyTypes, + HashSet importedTypeNames, + Dictionary declaredTypesByName) + { + var messages = new Dictionary(); + + foreach (var handler in handlers) + { + Add(handler.MessageTypeName, handler.MessageNamespace, fallbackLocation: null); + + if (handler.ResponseTypeName is not null) + { + Add(handler.ResponseTypeName, handler.ResponseNamespace ?? string.Empty, fallbackLocation: null); + } + } + + foreach (var contextOnly in contextOnlyTypes) + { + Add(contextOnly.MessageTypeName, contextOnly.MessageNamespace, contextOnly.Location); + } + + return messages.Values.OrderBy(m => m.MessageTypeName).ToList(); + + void Add(string messageTypeName, string messageNamespace, LocationInfo? fallbackLocation) + { + if (importedTypeNames.Contains(messageTypeName)) + { + return; + } + + // The type's own declaration (captured in the declaration pipeline) always wins. + // The fallback location is only used for types without a source declaration in this + // compilation (for example the [JsonSerializable] attribute span of a cross-assembly type). + string? xmlDocumentation = null; + LocationInfo? location = null; + + if (declaredTypesByName.TryGetValue(messageTypeName, out var declared)) + { + xmlDocumentation = declared.XmlDocumentation; + location = declared.Location; + } + + location ??= fallbackLocation; + + if (messages.TryGetValue(messageTypeName, out var existing)) + { + messages[messageTypeName] = existing with + { + XmlDocumentation = existing.XmlDocumentation ?? xmlDocumentation, + Location = existing.Location ?? location + }; + return; + } + + messages.Add( + messageTypeName, + new MessageMetadata(messageTypeName, messageNamespace, xmlDocumentation, location)); + } + } + + private sealed record MessageMetadata( + string MessageTypeName, + string MessageNamespace, + string? XmlDocumentation, + LocationInfo? Location); + + private static void WriteConsumerConfiguration( + MessagingDependencyInjectionFileBuilder moduleBuilder, + MessagingHandlerInfo handler, + List initializers) + { + var methodName = moduleBuilder.CreateGeneratedMethodName(handler.HandlerTypeName, "Consumer"); + initializers.Add(new ConsumerInitializer(methodName, handler)); + moduleBuilder.WriteConsumerConfiguration(handler, methodName); + } + + private sealed record MessageInitializer( + string MethodName, + string MessageTypeName, + string? JsonContextTypeName, + IReadOnlyList? EnclosedTypes, + string? XmlDocumentation, + LocationInfo? Location); + + private sealed record SagaInitializer(string MethodName, SagaInfo Saga, string? JsonContextTypeName); + + private sealed record ConsumerInitializer(string MethodName, MessagingHandlerInfo Handler); } diff --git a/src/Mocha/src/Mocha.Analyzers/Inspectors/AddMessageDeclarationInspector.cs b/src/Mocha/src/Mocha.Analyzers/Inspectors/AddMessageDeclarationInspector.cs new file mode 100644 index 00000000000..83e2c32020d --- /dev/null +++ b/src/Mocha/src/Mocha.Analyzers/Inspectors/AddMessageDeclarationInspector.cs @@ -0,0 +1,82 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Mocha.Analyzers.Filters; +using Mocha.Analyzers.Utils; + +namespace Mocha.Analyzers.Inspectors; + +/// +/// Inspects AddMessage<TMessage>() invocations on the Mocha builder APIs +/// (MessageBusHostBuilderExtensions and IMessageBusBuilder) and captures declaration metadata +/// for the registered message type. +/// +/// +/// This is a metadata-only discovery source. It produces an that feeds +/// the message declaration pipeline only; it never participates in AOT JsonSerializerContext validation or code +/// emission, which is why it is a distinct record from . +/// +public sealed class AddMessageDeclarationInspector : ISyntaxInspector +{ + /// + public ImmutableArray Filters { get; } = [AddMessageCallSiteFilter.Instance]; + + /// + public IImmutableSet SupportedKinds { get; } = ImmutableHashSet.Create(SyntaxKind.InvocationExpression); + + /// + public bool TryHandle( + KnownTypeSymbols knownSymbols, + SyntaxNode node, + SemanticModel semanticModel, + CancellationToken cancellationToken, + out SyntaxInfo? syntaxInfo) + { + syntaxInfo = null; + + if (node is not InvocationExpressionSyntax invocation) + { + return false; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var symbolInfo = semanticModel.GetSymbolInfo(invocation, cancellationToken); + if (symbolInfo.Symbol is not IMethodSymbol { Name: "AddMessage" } methodSymbol) + { + return false; + } + + var containingType = methodSymbol.ContainingType?.OriginalDefinition; + if (containingType is null || !IsMochaBuilderApi(knownSymbols, containingType)) + { + return false; + } + + // AddMessage() carries the message type as its single type argument. + if (methodSymbol.TypeArguments.Length != 1 + || methodSymbol.TypeArguments[0] is not INamedTypeSymbol messageType) + { + return false; + } + + // Capture declaration metadata (doc + span) cross-file from the resolved symbol. Metadata-only types + // (declared in another assembly) carry no source declaration, so there is nothing to contribute. This + // is stale only in the IDE's incremental cache; every real compiler invocation runs fresh. + var declaredMessageType = messageType.ToDeclaredTypeInfo(cancellationToken); + if (declaredMessageType is null) + { + return false; + } + + syntaxInfo = new AddMessageDeclarationInfo(declaredMessageType); + return true; + } + + private static bool IsMochaBuilderApi(KnownTypeSymbols knownSymbols, INamedTypeSymbol containingType) + => (knownSymbols.MessageBusHostBuilderExtensions is not null + && SymbolEqualityComparer.Default.Equals(containingType, knownSymbols.MessageBusHostBuilderExtensions)) + || (knownSymbols.IMessageBusBuilder is not null + && SymbolEqualityComparer.Default.Equals(containingType, knownSymbols.IMessageBusBuilder)); +} diff --git a/src/Mocha/src/Mocha.Analyzers/Inspectors/CallSiteMessageTypeInspector.cs b/src/Mocha/src/Mocha.Analyzers/Inspectors/CallSiteMessageTypeInspector.cs index 202de48e19f..9175de7bf59 100644 --- a/src/Mocha/src/Mocha.Analyzers/Inspectors/CallSiteMessageTypeInspector.cs +++ b/src/Mocha/src/Mocha.Analyzers/Inspectors/CallSiteMessageTypeInspector.cs @@ -80,7 +80,7 @@ public bool TryHandle( if (knownSymbols.IPublisher is not null && SymbolEqualityComparer.Default.Equals(receiverType, knownSymbols.IPublisher)) { - return TryHandlePublisher(methodSymbol, invocation, out syntaxInfo); + return TryHandlePublisher(methodSymbol, invocation, cancellationToken, out syntaxInfo); } return false; @@ -130,7 +130,11 @@ private static bool TryHandleMessageBus( { var argTypeName = argType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var argLocation = invocation.GetLocation().ToLocationInfo(); - syntaxInfo = new CallSiteMessageTypeInfo(argTypeName, kind, argLocation); + syntaxInfo = new CallSiteMessageTypeInfo( + argTypeName, + kind, + argLocation, + DeclaredMessageType: argType.ToDeclaredTypeInfo(cancellationToken)); return true; } @@ -161,15 +165,23 @@ private static bool TryHandleMessageBus( // The response type is the type argument TResponse from RequestAsync. string? responseTypeName = null; + DeclaredTypeInfo? declaredResponseType = null; if (methodSymbol.TypeArguments.Length > 0 && !IsOpenTypeParameter(methodSymbol.TypeArguments[0])) { responseTypeName = methodSymbol .TypeArguments[0] .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + declaredResponseType = methodSymbol.TypeArguments[0].ToDeclaredTypeInfo(cancellationToken); } - syntaxInfo = new CallSiteMessageTypeInfo(requestTypeName, kind, location, responseTypeName); + syntaxInfo = new CallSiteMessageTypeInfo( + requestTypeName, + kind, + location, + responseTypeName, + firstArgType.ToDeclaredTypeInfo(cancellationToken), + declaredResponseType); return true; } @@ -183,7 +195,11 @@ private static bool TryHandleMessageBus( var typeName = messageType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var locationInfo = invocation.GetLocation().ToLocationInfo(); - syntaxInfo = new CallSiteMessageTypeInfo(typeName, kind, locationInfo); + syntaxInfo = new CallSiteMessageTypeInfo( + typeName, + kind, + locationInfo, + DeclaredMessageType: messageType.ToDeclaredTypeInfo(cancellationToken)); return true; } @@ -234,13 +250,18 @@ private static bool TryHandleSender( var typeName = messageType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var locationInfo = invocation.GetLocation().ToLocationInfo(); - syntaxInfo = new CallSiteMessageTypeInfo(typeName, kind, locationInfo); + syntaxInfo = new CallSiteMessageTypeInfo( + typeName, + kind, + locationInfo, + DeclaredMessageType: messageType.ToDeclaredTypeInfo(cancellationToken)); return true; } private static bool TryHandlePublisher( IMethodSymbol methodSymbol, InvocationExpressionSyntax invocation, + CancellationToken cancellationToken, out SyntaxInfo? syntaxInfo) { syntaxInfo = null; @@ -264,7 +285,11 @@ private static bool TryHandlePublisher( var typeName = messageType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var locationInfo = invocation.GetLocation().ToLocationInfo(); - syntaxInfo = new CallSiteMessageTypeInfo(typeName, CallSiteKind.MediatorPublish, locationInfo); + syntaxInfo = new CallSiteMessageTypeInfo( + typeName, + CallSiteKind.MediatorPublish, + locationInfo, + DeclaredMessageType: messageType.ToDeclaredTypeInfo(cancellationToken)); return true; } diff --git a/src/Mocha/src/Mocha.Analyzers/Inspectors/HandlerInspector.cs b/src/Mocha/src/Mocha.Analyzers/Inspectors/HandlerInspector.cs index 734814a226d..98adeabee65 100644 --- a/src/Mocha/src/Mocha.Analyzers/Inspectors/HandlerInspector.cs +++ b/src/Mocha/src/Mocha.Analyzers/Inspectors/HandlerInspector.cs @@ -84,6 +84,8 @@ public bool TryHandle( var handlerFullName = namedTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var handlerNamespace = namedTypeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty; + var xmlDocumentation = namedTypeSymbol.GetXmlDocumentation(cancellationToken); + var location = typeDeclaration.ToDeclarationLocationInfo(); syntaxInfo = new HandlerInfo( handlerFullName, @@ -92,7 +94,9 @@ public bool TryHandle( descriptor.HasResponse ? implemented.TypeArguments[1].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) : null, - descriptor.Kind); + descriptor.Kind, + xmlDocumentation, + location); return true; } diff --git a/src/Mocha/src/Mocha.Analyzers/Inspectors/MessagingHandlerInspector.cs b/src/Mocha/src/Mocha.Analyzers/Inspectors/MessagingHandlerInspector.cs index 54a756bee9c..6df9992f840 100644 --- a/src/Mocha/src/Mocha.Analyzers/Inspectors/MessagingHandlerInspector.cs +++ b/src/Mocha/src/Mocha.Analyzers/Inspectors/MessagingHandlerInspector.cs @@ -90,7 +90,13 @@ public bool TryHandle( var handlerNamespace = namedTypeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty; var messageSymbol = (INamedTypeSymbol)implemented.TypeArguments[descriptor.MessageTypeArgIndex]; var messageTypeName = messageSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var messageNamespace = messageSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty; + var responseType = descriptor.HasResponse ? implemented.TypeArguments[1] : null; + var responseNamespace = responseType is INamedTypeSymbol responseNamespaceType + ? responseNamespaceType.ContainingNamespace?.ToDisplayString() ?? string.Empty + : null; var locationInfo = typeDeclaration.Identifier.GetLocation().ToLocationInfo(); + var declarationLocation = typeDeclaration.ToDeclarationLocationInfo(); // Walk the full type hierarchy (base types + interfaces) for AOT enclosed type computation. var hierarchy = new List(); @@ -105,16 +111,27 @@ public bool TryHandle( hierarchy.Add(iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } + // Capture declaration metadata (doc + location) for the message and response types cross-file from + // their resolved symbols, so this handler is one of several discovery sources merged by type name. + // Metadata-only types (declared in another assembly) yield null. This is stale only in the IDE's + // incremental cache; every real compiler invocation runs fresh. + var declaredMessageType = messageSymbol.ToDeclaredTypeInfo(cancellationToken); + var declaredResponseType = responseType.ToDeclaredTypeInfo(cancellationToken); + syntaxInfo = new MessagingHandlerInfo( handlerFullName, handlerNamespace, messageTypeName, - descriptor.HasResponse - ? implemented.TypeArguments[1].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - : null, + messageNamespace, + responseType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + responseNamespace, descriptor.Kind, new ImmutableEquatableArray(hierarchy), - locationInfo); + namedTypeSymbol.GetXmlDocumentation(cancellationToken), + locationInfo, + declarationLocation, + declaredMessageType, + declaredResponseType); return true; } diff --git a/src/Mocha/src/Mocha.Analyzers/Inspectors/NotificationHandlerInspector.cs b/src/Mocha/src/Mocha.Analyzers/Inspectors/NotificationHandlerInspector.cs index 91e1a1f48ce..2ac0dbe14a8 100644 --- a/src/Mocha/src/Mocha.Analyzers/Inspectors/NotificationHandlerInspector.cs +++ b/src/Mocha/src/Mocha.Analyzers/Inspectors/NotificationHandlerInspector.cs @@ -60,11 +60,14 @@ public bool TryHandle( } var notificationType = implemented.TypeArguments[0]; + var notificationTypeName = notificationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); syntaxInfo = new NotificationHandlerInfo( namedTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), namedTypeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty, - notificationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + notificationTypeName, + namedTypeSymbol.GetXmlDocumentation(cancellationToken), + typeDeclaration.ToDeclarationLocationInfo()); return true; } diff --git a/src/Mocha/src/Mocha.Analyzers/Inspectors/SagaInspector.cs b/src/Mocha/src/Mocha.Analyzers/Inspectors/SagaInspector.cs index 1a3496d17ba..745d8e12809 100644 --- a/src/Mocha/src/Mocha.Analyzers/Inspectors/SagaInspector.cs +++ b/src/Mocha/src/Mocha.Analyzers/Inspectors/SagaInspector.cs @@ -60,12 +60,14 @@ public bool TryHandle( var sagaFullName = namedTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var sagaNamespace = namedTypeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty; var stateTypeName = stateType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var xmlDocumentation = namedTypeSymbol.GetXmlDocumentation(cancellationToken); + var location = typeDeclaration.ToDeclarationLocationInfo(); // Check for public parameterless constructor (MO0014) if (!HasPublicParameterlessConstructor(namedTypeSymbol)) { var locationInfo = typeDeclaration.Identifier.GetLocation().ToLocationInfo(); - syntaxInfo = new SagaInfo(sagaFullName, sagaNamespace, stateTypeName) + syntaxInfo = new SagaInfo(sagaFullName, sagaNamespace, stateTypeName, xmlDocumentation, location) { Diagnostics = new([ new DiagnosticInfo( @@ -77,7 +79,7 @@ public bool TryHandle( return true; } - syntaxInfo = new SagaInfo(sagaFullName, sagaNamespace, stateTypeName); + syntaxInfo = new SagaInfo(sagaFullName, sagaNamespace, stateTypeName, xmlDocumentation, location); return true; } diff --git a/src/Mocha/src/Mocha.Analyzers/KnownTypeSymbols.cs b/src/Mocha/src/Mocha.Analyzers/KnownTypeSymbols.cs index 479372af83e..868d7618978 100644 --- a/src/Mocha/src/Mocha.Analyzers/KnownTypeSymbols.cs +++ b/src/Mocha/src/Mocha.Analyzers/KnownTypeSymbols.cs @@ -33,6 +33,8 @@ public sealed class KnownTypeSymbols private Resolved? _messageBus; private Resolved? _sender; private Resolved? _publisher; + private Resolved? _messageBusHostBuilderExtensions; + private Resolved? _messageBusBuilder; private KnownTypeSymbols(Compilation compilation) { @@ -154,6 +156,20 @@ public INamedTypeSymbol? ISender public INamedTypeSymbol? IPublisher => Resolve(SyntaxConstants.IPublisher, ref _publisher); + /// + /// Gets the symbol for the MessageBusHostBuilderExtensions class declaring + /// the AddMessage<TMessage> host builder registration methods. + /// + public INamedTypeSymbol? MessageBusHostBuilderExtensions + => Resolve(SyntaxConstants.MessageBusHostBuilderExtensions, ref _messageBusHostBuilderExtensions); + + /// + /// Gets the symbol for the IMessageBusBuilder interface declaring + /// the AddMessage<TMessage> registration methods. + /// + public INamedTypeSymbol? IMessageBusBuilder + => Resolve(SyntaxConstants.IMessageBusBuilder, ref _messageBusBuilder); + private INamedTypeSymbol? Resolve(string metadataName, ref Resolved? field) { var snapshot = Interlocked.CompareExchange(ref field, null, null); diff --git a/src/Mocha/src/Mocha.Analyzers/MediatorGenerator.cs b/src/Mocha/src/Mocha.Analyzers/MediatorGenerator.cs index 5f45c78feb1..314d3ff1ff2 100644 --- a/src/Mocha/src/Mocha.Analyzers/MediatorGenerator.cs +++ b/src/Mocha/src/Mocha.Analyzers/MediatorGenerator.cs @@ -34,10 +34,7 @@ public sealed class MediatorGenerator : IIncrementalGenerator new ImportedMediatorModuleTypeInspector() ]; - private static readonly ISyntaxGenerator[] s_generators = - [ - new DependencyInjectionGenerator() - ]; + private static readonly ISyntaxGenerator[] s_generators = [new DependencyInjectionGenerator()]; private static readonly Dictionary> s_inspectorLookup; private static readonly Dictionary> s_callSiteInspectorLookup; @@ -128,9 +125,24 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var assemblyName = context.CompilationProvider.Select(static (c, _) => c.AssemblyName ?? "Unknown"); + var sourceMetadataOptions = context.AnalyzerConfigOptionsProvider.Select( + static (options, _) => + { + options.GlobalOptions.TryGetValue("build_property.MochaEmitSourceMetadata", out var emit); + options.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir); + options.GlobalOptions.TryGetValue("build_property.RepositoryUrl", out var repositoryUrl); + options.GlobalOptions.TryGetValue("build_property.SourceRevisionId", out var commit); + return new SourceMetadataOptionsInfo( + !string.Equals(emit, "false", StringComparison.OrdinalIgnoreCase), + string.IsNullOrWhiteSpace(projectDir) ? null : projectDir, + string.IsNullOrWhiteSpace(repositoryUrl) ? null : repositoryUrl, + string.IsNullOrWhiteSpace(commit) ? null : commit); + }); + context.RegisterSourceOutput( - assemblyName.Combine(syntaxInfos).Combine(callSiteInfos), - static (context, source) => Execute(context, source.Left.Left, source.Left.Right, source.Right)); + assemblyName.Combine(syntaxInfos).Combine(callSiteInfos).Combine(sourceMetadataOptions), + static (context, source) => + Execute(context, source.Left.Left.Left, source.Left.Left.Right, source.Left.Right, source.Right)); } private static SyntaxInfo? Transform( @@ -183,7 +195,8 @@ private static void Execute( SourceProductionContext context, string assemblyName, ImmutableArray syntaxInfos, - ImmutableArray callSiteInfos) + ImmutableArray callSiteInfos, + SourceMetadataOptionsInfo sourceMetadataOptions) { var sourceFiles = PooledObjects.GetStringDictionary(); var moduleInfo = GetModuleInfo(syntaxInfos, ModuleNameHelper.CreateModuleName(assemblyName)); @@ -209,14 +222,11 @@ private static void Execute( // Validate call-site types vs handlers (MO0020) ValidateCallSiteNoHandler(context, syntaxInfos, callSiteInfos); + var augmentedInfos = syntaxInfos.Add(sourceMetadataOptions); + foreach (var generator in s_generators) { - generator.Generate( - context, - assemblyName, - moduleInfo.ModuleName, - syntaxInfos, - AddSource); + generator.Generate(context, assemblyName, moduleInfo.ModuleName, augmentedInfos, AddSource); } foreach (var sourceFile in sourceFiles) @@ -287,9 +297,18 @@ private static void ValidateMessageHandlerPairing( } } + // Dedupe handler infos by handler type name so a handler declared across multiple + // partial declaration parts (e.g. restating the handler interface on each part) + // counts once, matching the emission-side deduplication in DependencyInjectionGenerator. + var dedupedHandlers = DeduplicationHelper.SelectRepresentatives( + handlers, + h => h.HandlerTypeName, + h => h.XmlDocumentation, + h => h.Location); + // Build a lookup of handlers by message type name var handlersByMessageType = new Dictionary>(); - foreach (var handler in handlers) + foreach (var handler in dedupedHandlers) { if (!handlersByMessageType.TryGetValue(handler.MessageTypeName, out var list)) { @@ -324,11 +343,7 @@ private static void ValidateMessageHandlerPairing( // tracked by MessageTypeInspector are commands/queries only (not notifications) var handlerNames = string.Join(", ", matchingHandlers.Select(h => h.HandlerTypeName).OrderBy(n => n)); context.ReportDiagnostic( - Diagnostic.Create( - Errors.DuplicateHandler, - location, - messageType.MessageTypeName, - handlerNames)); + Diagnostic.Create(Errors.DuplicateHandler, location, messageType.MessageTypeName, handlerNames)); } } } @@ -369,10 +384,11 @@ private static void ValidateCallSiteNoHandler( // Check each call-site for MediatorSend or MediatorQuery - not MediatorPublish. foreach (var info in callSiteInfos) { - if (info is CallSiteMessageTypeInfo - { - Kind: CallSiteKind.MediatorSend or CallSiteKind.MediatorQuery - } callSite + if (info + is CallSiteMessageTypeInfo + { + Kind: CallSiteKind.MediatorSend or CallSiteKind.MediatorQuery + } callSite && !handlerMessageTypes.Contains(callSite.MessageTypeName)) { var location = ReconstructLocation(callSite.Location); diff --git a/src/Mocha/src/Mocha.Analyzers/MessagingGenerator.cs b/src/Mocha/src/Mocha.Analyzers/MessagingGenerator.cs index 468a0c7d889..2c3b42ad94f 100644 --- a/src/Mocha/src/Mocha.Analyzers/MessagingGenerator.cs +++ b/src/Mocha/src/Mocha.Analyzers/MessagingGenerator.cs @@ -29,7 +29,8 @@ public sealed class MessagingGenerator : IIncrementalGenerator private static readonly ISyntaxInspector[] s_callSiteInspectors = [ new CallSiteMessageTypeInspector(), - new ImportedModuleTypeInspector() + new ImportedModuleTypeInspector(), + new AddMessageDeclarationInspector() ]; private static readonly ISyntaxGenerator[] s_generators = @@ -132,6 +133,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return string.Equals(publishAot, "true", StringComparison.OrdinalIgnoreCase); }); + var sourceMetadataOptions = context.AnalyzerConfigOptionsProvider.Select(static (options, _) => + { + options.GlobalOptions.TryGetValue("build_property.MochaEmitSourceMetadata", out var emit); + options.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir); + options.GlobalOptions.TryGetValue("build_property.RepositoryUrl", out var repositoryUrl); + options.GlobalOptions.TryGetValue("build_property.SourceRevisionId", out var commit); + return new SourceMetadataOptionsInfo( + !string.Equals(emit, "false", StringComparison.OrdinalIgnoreCase), + string.IsNullOrWhiteSpace(projectDir) ? null : projectDir, + string.IsNullOrWhiteSpace(repositoryUrl) ? null : repositoryUrl, + string.IsNullOrWhiteSpace(commit) ? null : commit); + }); + var jsonSerializableInfos = context .SyntaxProvider.CreateSyntaxProvider( predicate: static (s, _) => IsJsonSerializableAttributeCandidate(s), @@ -146,15 +160,30 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Combine(jsonSerializableInfos) .Select(static (source, _) => BuildJsonContextInfo(source.Left, source.Right)); + // Aggregate the per-type declaration metadata (doc + location) captured cross-file from every discovery + // source: handler infos (message + response types), dispatch and explicit AddMessage call sites, and + // the weakest source, the [JsonSerializable] list. Entries are merged by fully qualified type name so a + // type declared in another assembly still resolves whenever any source in this compilation observes it. + var messageDeclarations = jsonContextInfo + .Combine(syntaxInfos) + .Combine(callSiteInfos) + .Select(static (source, _) => + BuildMessageDeclarations(source.Left.Left, source.Left.Right, source.Right)) + .WithTrackingName("MochaMessageDeclarations"); + context.RegisterSourceOutput( assemblyName .Combine(syntaxInfos) .Combine(callSiteInfos) .Combine(isAotPublish) - .Combine(jsonContextInfo), + .Combine(jsonContextInfo) + .Combine(sourceMetadataOptions) + .Combine(messageDeclarations), static (context, source) => Execute( context, - source.Left.Left.Left.Left, + source.Left.Left.Left.Left.Left.Left, + source.Left.Left.Left.Left.Left.Right, + source.Left.Left.Left.Left.Right, source.Left.Left.Left.Right, source.Left.Left.Right, source.Left.Right, @@ -213,7 +242,9 @@ private static void Execute( ImmutableArray syntaxInfos, ImmutableArray callSiteInfos, bool isAotPublish, - JsonContextInfo jsonContextInfo) + JsonContextInfo jsonContextInfo, + SourceMetadataOptionsInfo sourceMetadataOptions, + MessageDeclarationsInfo messageDeclarations) { var sourceFiles = PooledObjects.GetStringDictionary(); var moduleInfo = GetModuleInfo(syntaxInfos, ModuleNameHelper.CreateModuleName(assemblyName)); @@ -244,6 +275,12 @@ private static void Execute( // Extract context-only message types from JsonSerializerContext (types without handlers). var augmentedInfos = ExtractContextOnlyTypes(syntaxInfos, jsonContextInfo); augmentedInfos = augmentedInfos.Add(new AotPublishInfo(isAotPublish)); + augmentedInfos = augmentedInfos.Add(sourceMetadataOptions); + + if (messageDeclarations.Declarations.Count > 0) + { + augmentedInfos = augmentedInfos.Add(messageDeclarations); + } // Pass the full set of JsonContext-serializable type names so the DI generator // can restrict serializer registrations to types that are actually in the context. @@ -350,7 +387,10 @@ private static bool IsJsonSerializableAttributeCandidate(SyntaxNode node) return new JsonSerializableAttributeInfo( contextSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - CreateJsonSerializableTypeInfo(serializableType, attribute.GetLocation().ToLocationInfo())); + CreateJsonSerializableTypeInfo( + serializableType, + attribute.GetLocation().ToDeclarationLocationInfo(), + cancellationToken)); } private static JsonContextInfo BuildJsonContextInfo( @@ -389,11 +429,103 @@ private static JsonContextInfo BuildJsonContextInfo( new ImmutableEquatableArray(serializableTypes)); } + private static MessageDeclarationsInfo BuildMessageDeclarations( + JsonContextInfo jsonContextInfo, + ImmutableArray syntaxInfos, + ImmutableArray callSiteInfos) + { + // Aggregation always runs: message registration and SourceMetadata emission no longer require a + // JsonSerializerContext, so every discovered declared message type must flow to the DI generator. + + // Merge entries by fully qualified type name, deduplicating across sources and across partial parts. + // Every source resolves the same closed symbol, so overlapping entries collapse to an identical value. + var byTypeName = new Dictionary(StringComparer.Ordinal); + + // Type names registered by an explicit user AddMessage() call. The user's own call already emits + // AddMessage, so the generated module contributes only the descriptor callback for these types. + var explicitAddMessageTypeNames = new HashSet(StringComparer.Ordinal); + + // Source A: handler message and response declarations. + foreach (var info in syntaxInfos) + { + if (info is MessagingHandlerInfo handler) + { + AddDeclaration(byTypeName, handler.DeclaredMessageType); + AddDeclaration(byTypeName, handler.DeclaredResponseType); + } + } + + // Sources B and C: dispatch call sites and explicit AddMessage registrations. + foreach (var info in callSiteInfos) + { + if (info is CallSiteMessageTypeInfo callSite) + { + AddDeclaration(byTypeName, callSite.DeclaredMessageType); + AddDeclaration(byTypeName, callSite.DeclaredResponseType); + } + else if (info is AddMessageDeclarationInfo addMessage) + { + AddDeclaration(byTypeName, addMessage.DeclaredMessageType); + explicitAddMessageTypeNames.Add(addMessage.DeclaredMessageType.TypeName); + } + } + + // Weakest source: the [JsonSerializable] list (AOT-only, requires a context). + foreach (var serializableType in jsonContextInfo.SerializableTypes) + { + AddDeclaration(byTypeName, serializableType.Declaration); + } + + var survivors = byTypeName.Values + .OrderBy(d => d.TypeName, StringComparer.Ordinal) + .ToList(); + + var explicitNames = explicitAddMessageTypeNames + .OrderBy(n => n, StringComparer.Ordinal) + .ToList(); + + return new MessageDeclarationsInfo( + new ImmutableEquatableArray(survivors), + new ImmutableEquatableArray(explicitNames)); + } + + private static void AddDeclaration( + Dictionary byTypeName, + DeclaredTypeInfo? declared) + { + // Metadata-only types (declared in another assembly) carry no captured declaration. + if (declared is null) + { + return; + } + + if (!byTypeName.TryGetValue(declared.TypeName, out var existing)) + { + byTypeName[declared.TypeName] = declared; + return; + } + + byTypeName[declared.TypeName] = MergeDeclarations(existing, declared); + } + + private static DeclaredTypeInfo MergeDeclarations(DeclaredTypeInfo first, DeclaredTypeInfo second) + { + // XmlDocumentation is the first non-null; partial parts carry identical symbol-level doc. + var xmlDocumentation = first.XmlDocumentation ?? second.XmlDocumentation; + + // Prefer the entry with the ordinally smallest location; a null location always loses. + var location = LocationInfo.Min(first.Location, second.Location); + + return new DeclaredTypeInfo(first.TypeName, xmlDocumentation, location); + } + private static JsonSerializableTypeInfo CreateJsonSerializableTypeInfo( INamedTypeSymbol serializableType, - LocationInfo? location) + LocationInfo? location, + CancellationToken cancellationToken) { var typeName = serializableType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var typeNamespace = serializableType.ContainingNamespace?.ToDisplayString() ?? string.Empty; // Walk the full type hierarchy (base types + interfaces), same as MessagingHandlerInspector. var hierarchy = new List(); @@ -410,10 +542,17 @@ private static JsonSerializableTypeInfo CreateJsonSerializableTypeInfo( hierarchy.Add(iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } + // Capture the declaration metadata (doc + location) cross-file from the resolved symbol so + // context-only types (serializable types with no handler) still carry their own declaration. + // This is stale only in the IDE's incremental cache; every real compiler invocation runs fresh. + var declaration = serializableType.ToDeclaredTypeInfo(cancellationToken); + return new JsonSerializableTypeInfo( typeName, + typeNamespace, new ImmutableEquatableArray(hierarchy), - location); + location, + declaration); } private sealed record JsonSerializableAttributeInfo( @@ -443,9 +582,18 @@ private static void ValidateRequestHandlerPairing( return; } + // Dedupe handler infos by handler type name so a handler declared across multiple + // partial declaration parts (e.g. restating the handler interface on each part) + // counts once, matching the emission-side deduplication in MessagingDependencyInjectionGenerator. + var dedupedRequestHandlers = DeduplicationHelper.SelectRepresentatives( + requestHandlers, + h => h.HandlerTypeName, + h => h.XmlDocumentation, + h => h.Location); + // Group by message type to detect duplicates var handlersByMessageType = new Dictionary>(); - foreach (var handler in requestHandlers) + foreach (var handler in dedupedRequestHandlers) { if (!handlersByMessageType.TryGetValue(handler.MessageTypeName, out var list)) { @@ -733,6 +881,7 @@ private static ImmutableArray ExtractContextOnlyTypes( contextOnlyInfos.Add(new ContextOnlyMessageInfo( serializableType.TypeName, + serializableType.TypeNamespace, serializableType.TypeHierarchy, serializableType.AttributeLocation)); } diff --git a/src/Mocha/src/Mocha.Analyzers/Models/AddMessageDeclarationInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/AddMessageDeclarationInfo.cs new file mode 100644 index 00000000000..0a483d6fdab --- /dev/null +++ b/src/Mocha/src/Mocha.Analyzers/Models/AddMessageDeclarationInfo.cs @@ -0,0 +1,19 @@ +namespace Mocha.Analyzers; + +/// +/// Represents a message type discovered from an explicit AddMessage<TMessage>() registration on +/// the Mocha builder APIs. This is a metadata-only discovery source: it carries declaration metadata into the +/// message declaration pipeline and is deliberately ignored by the AOT JsonSerializerContext validation +/// (MO0015/MO0016/MO0018) and by code emission, which is why it is a distinct record from +/// . +/// +/// +/// The declaration metadata (doc + span) of the registered message type, captured cross-file from its resolved +/// symbol. Only types with a source declaration in this compilation produce an entry. +/// +public sealed record AddMessageDeclarationInfo( + DeclaredTypeInfo DeclaredMessageType) : SyntaxInfo +{ + /// + public override string OrderByKey => $"AddMessageDeclaration:{DeclaredMessageType.TypeName}"; +} diff --git a/src/Mocha/src/Mocha.Analyzers/Models/CallSiteMessageTypeInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/CallSiteMessageTypeInfo.cs index 777087d05fe..0b1fb973e7b 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/CallSiteMessageTypeInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/CallSiteMessageTypeInfo.cs @@ -13,11 +13,22 @@ namespace Mocha.Analyzers; /// The fully qualified type name of the response type for request-reply call sites, or /// for fire-and-forget calls. /// +/// +/// The declaration metadata (doc + span) of the statically resolved message type, captured cross-file from its +/// resolved symbol, or when the message type has no source declaration in this +/// compilation. Consumed only by the message declaration pipeline, never by validation. +/// +/// +/// The declaration metadata (doc + span) of the resolved response type, or when the call +/// site resolves no response type or the response type has no source declaration in this compilation. +/// public sealed record CallSiteMessageTypeInfo( string MessageTypeName, CallSiteKind Kind, LocationInfo? Location, - string? ResponseTypeName = null) : SyntaxInfo + string? ResponseTypeName = null, + DeclaredTypeInfo? DeclaredMessageType = null, + DeclaredTypeInfo? DeclaredResponseType = null) : SyntaxInfo { /// public override string OrderByKey => $"CallSite:{Kind}:{MessageTypeName}"; diff --git a/src/Mocha/src/Mocha.Analyzers/Models/ContextOnlyMessageInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/ContextOnlyMessageInfo.cs index 835f93a1aa7..26ed3359bd7 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/ContextOnlyMessageInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/ContextOnlyMessageInfo.cs @@ -8,6 +8,7 @@ namespace Mocha.Analyzers; /// registrations for AOT compatibility. /// /// The fully qualified type name of the message. +/// The namespace containing the message type. /// /// The unfiltered type hierarchy of the message type (base types excluding object, plus all interfaces), /// as fully qualified display strings. Filtering to registered types happens in the generator phase. @@ -17,6 +18,7 @@ namespace Mocha.Analyzers; /// public sealed record ContextOnlyMessageInfo( string MessageTypeName, + string MessageNamespace, ImmutableEquatableArray MessageTypeHierarchy, LocationInfo? Location) : SyntaxInfo { diff --git a/src/Mocha/src/Mocha.Analyzers/Models/DeclaredTypeInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/DeclaredTypeInfo.cs new file mode 100644 index 00000000000..a26be49c115 --- /dev/null +++ b/src/Mocha/src/Mocha.Analyzers/Models/DeclaredTypeInfo.cs @@ -0,0 +1,22 @@ +namespace Mocha.Analyzers; + +/// +/// Equatable per-type metadata captured in a pipeline keyed on the type's own declaration. +/// Editing the file that declares the type invalidates exactly this entry, so message and +/// response documentation and locations no longer go stale when only the message file changes. +/// +/// +/// The fully qualified name of the declared type, used as the join key against handler and +/// context message type names. For a declaration this is the original definition +/// (for example global::Ns.Wrapper<T> for a generic type). +/// +/// +/// The XML documentation captured from the declaring symbol, or if none. +/// +/// +/// The full source location of the type declaration, or if unavailable. +/// +public sealed record DeclaredTypeInfo( + string TypeName, + string? XmlDocumentation, + LocationInfo? Location); diff --git a/src/Mocha/src/Mocha.Analyzers/Models/HandlerInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/HandlerInfo.cs index 1dfbb649edd..ebeb191efe8 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/HandlerInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/HandlerInfo.cs @@ -10,12 +10,18 @@ namespace Mocha.Analyzers; /// The simple type name of the response, or if the handler returns no response. /// /// The kind of handler. +/// The XML documentation captured from the handler declaration. +/// +/// The equatable source location of the handler type declaration, or if unavailable. +/// public sealed record HandlerInfo( string HandlerTypeName, string HandlerNamespace, string MessageTypeName, string? ResponseTypeName, - HandlerKind Kind) : SyntaxInfo + HandlerKind Kind, + string? XmlDocumentation, + LocationInfo? Location) : SyntaxInfo { /// public override string OrderByKey => $"{Kind}:{MessageTypeName}"; diff --git a/src/Mocha/src/Mocha.Analyzers/Models/JsonContextSerializableTypesInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/JsonContextSerializableTypesInfo.cs index cd6b9c150c6..88667a0c392 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/JsonContextSerializableTypesInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/JsonContextSerializableTypesInfo.cs @@ -5,8 +5,7 @@ namespace Mocha.Analyzers; /// /// Carries the complete set of type names declared as [JsonSerializable] on the /// module's JsonSerializerContext. Used by downstream generators to determine which -/// types have local serializer support and should receive AddMessageConfiguration -/// registrations with pre-built serializers. +/// types have local serializer support and should receive generated message configuration. /// /// /// The fully qualified type names of all types declared via [JsonSerializable(typeof(T))] diff --git a/src/Mocha/src/Mocha.Analyzers/Models/JsonSerializableTypeInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/JsonSerializableTypeInfo.cs index bf696fd0cd8..f5dd485a3b4 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/JsonSerializableTypeInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/JsonSerializableTypeInfo.cs @@ -7,6 +7,7 @@ namespace Mocha.Analyzers; /// JsonSerializerContext, including its type hierarchy for context-only message resolution. /// /// The fully qualified type name of the serializable type. +/// The namespace containing the serializable type. /// /// The unfiltered type hierarchy (base types excluding object, plus all interfaces), /// as fully qualified display strings. @@ -14,7 +15,13 @@ namespace Mocha.Analyzers; /// /// The equatable source location of the [JsonSerializable] attribute, or if unavailable. /// +/// +/// The declaration metadata (doc + span) of the serializable type, captured cross-file from its resolved +/// symbol, or when the type has no source declaration in this compilation. +/// public sealed record JsonSerializableTypeInfo( string TypeName, + string TypeNamespace, ImmutableEquatableArray TypeHierarchy, - LocationInfo? AttributeLocation) : IEquatable; + LocationInfo? AttributeLocation, + DeclaredTypeInfo? Declaration) : IEquatable; diff --git a/src/Mocha/src/Mocha.Analyzers/Models/LocationInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/LocationInfo.cs index 38baa448979..d47156b194d 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/LocationInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/LocationInfo.cs @@ -9,4 +9,67 @@ namespace Mocha.Analyzers; /// The zero-based start column number. /// The zero-based end line number. /// The zero-based end column number. -public sealed record LocationInfo(string FilePath, int StartLine, int StartColumn, int EndLine, int EndColumn); +public sealed record LocationInfo(string FilePath, int StartLine, int StartColumn, int EndLine, int EndColumn) + : IComparable +{ + /// + /// Orders locations by file path (ordinal), then by start and end position. The ordering + /// covers every field so it is consistent with value equality, and it is deterministic across + /// compilations and machines, which reproducible builds and snapshot tests depend on. + /// + public int CompareTo(LocationInfo? other) + { + if (other is null) + { + return 1; + } + + var pathComparison = string.CompareOrdinal(FilePath, other.FilePath); + + if (pathComparison != 0) + { + return pathComparison; + } + + if (StartLine != other.StartLine) + { + return StartLine < other.StartLine ? -1 : 1; + } + + if (StartColumn != other.StartColumn) + { + return StartColumn < other.StartColumn ? -1 : 1; + } + + if (EndLine != other.EndLine) + { + return EndLine < other.EndLine ? -1 : 1; + } + + if (EndColumn != other.EndColumn) + { + return EndColumn < other.EndColumn ? -1 : 1; + } + + return 0; + } + + /// + /// Returns the smaller of two locations by ordering, treating a + /// location as absent so the other is returned. + /// + public static LocationInfo? Min(LocationInfo? first, LocationInfo? second) + { + if (first is null) + { + return second; + } + + if (second is null) + { + return first; + } + + return first.CompareTo(second) <= 0 ? first : second; + } +} diff --git a/src/Mocha/src/Mocha.Analyzers/Models/MessageDeclarationsInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/MessageDeclarationsInfo.cs new file mode 100644 index 00000000000..39c1943e1a4 --- /dev/null +++ b/src/Mocha/src/Mocha.Analyzers/Models/MessageDeclarationsInfo.cs @@ -0,0 +1,26 @@ +using Mocha.Analyzers.Utils; + +namespace Mocha.Analyzers; + +/// +/// Carries the aggregated per-type declaration metadata through the shared +/// ImmutableArray<SyntaxInfo> pipeline so the dependency injection generator can +/// resolve message and response documentation and spans by fully qualified type name. +/// +/// +/// The declaration metadata for every message type declared in this compilation and observed by any +/// discovery source, sorted by and deduplicated so the carrier +/// has stable value equality. +/// +/// +/// The fully qualified names of message types the user registers with an explicit AddMessage<T>() +/// call. The generated module emits only the descriptor callback for these, never a second +/// AddMessage, because the user's own call already performs the registration. +/// +public sealed record MessageDeclarationsInfo( + ImmutableEquatableArray Declarations, + ImmutableEquatableArray ExplicitAddMessageTypeNames) : SyntaxInfo +{ + /// + public override string OrderByKey => "MsgDeclarations"; +} diff --git a/src/Mocha/src/Mocha.Analyzers/Models/MessageTypeInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/MessageTypeInfo.cs index f2c6c4dd5bb..99d2d4a30a3 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/MessageTypeInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/MessageTypeInfo.cs @@ -1,14 +1,14 @@ namespace Mocha.Analyzers; /// -/// Represents the extracted metadata for a message type (command, query, or stream) discovered +/// Represents the extracted metadata for a message type (command or query) discovered /// during source generation. /// /// The simple type name of the message. /// The namespace containing the message type. /// The kind of message. /// -/// The equatable source location of the message type declaration, or if +/// The equatable source location of the message type identifier, or if /// unavailable. /// public sealed record MessageTypeInfo( diff --git a/src/Mocha/src/Mocha.Analyzers/Models/MessagingHandlerInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/MessagingHandlerInfo.cs index 534b59a0a82..6349e624f9b 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/MessagingHandlerInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/MessagingHandlerInfo.cs @@ -8,25 +8,49 @@ namespace Mocha.Analyzers; /// The fully qualified type name of the handler class. /// The namespace containing the handler class. /// The fully qualified type name of the message the handler processes. +/// The namespace containing the message type. /// /// The fully qualified type name of the response, or if the handler returns no response. /// +/// +/// The namespace containing the response type, or if the handler returns no response. +/// /// The kind of messaging handler. /// /// The unfiltered type hierarchy of the message type (base types excluding object, plus all interfaces), /// as fully qualified display strings. Filtering to registered types happens in the generator phase. /// +/// The XML documentation captured from the handler declaration. /// +/// The equatable source location used for diagnostics, or if unavailable. +/// +/// /// The equatable source location of the handler type declaration, or if unavailable. /// +/// +/// The declaration metadata (doc + span) of the message type, captured cross-file from its resolved symbol, +/// or when the message type has no source declaration in this compilation. This is +/// one of several discovery sources merged by fully qualified name in the message declaration pipeline. +/// +/// +/// The declaration metadata (doc + span) of the response type, captured cross-file from its resolved symbol, +/// or when the handler returns no response or the response type has no source +/// declaration in this compilation. +/// public sealed record MessagingHandlerInfo( string HandlerTypeName, string HandlerNamespace, string MessageTypeName, + string MessageNamespace, string? ResponseTypeName, + string? ResponseNamespace, MessagingHandlerKind Kind, ImmutableEquatableArray MessageTypeHierarchy, - LocationInfo? Location) : SyntaxInfo + string? XmlDocumentation, + LocationInfo? Location, + LocationInfo? DeclarationLocation, + DeclaredTypeInfo? DeclaredMessageType, + DeclaredTypeInfo? DeclaredResponseType) : SyntaxInfo { /// public override string OrderByKey => $"MsgHandler:{Kind}:{MessageTypeName}:{HandlerTypeName}"; diff --git a/src/Mocha/src/Mocha.Analyzers/Models/NotificationHandlerInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/NotificationHandlerInfo.cs index b3b84311736..950606e0ec7 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/NotificationHandlerInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/NotificationHandlerInfo.cs @@ -6,10 +6,16 @@ namespace Mocha.Analyzers; /// The simple type name of the notification handler class. /// The namespace containing the notification handler class. /// The simple type name of the notification the handler processes. +/// The XML documentation captured from the handler declaration. +/// +/// The equatable source location of the handler type declaration, or if unavailable. +/// public sealed record NotificationHandlerInfo( string HandlerTypeName, string HandlerNamespace, - string NotificationTypeName) : SyntaxInfo + string NotificationTypeName, + string? XmlDocumentation, + LocationInfo? Location) : SyntaxInfo { /// public override string OrderByKey => $"Notification:{NotificationTypeName}:{HandlerTypeName}"; diff --git a/src/Mocha/src/Mocha.Analyzers/Models/SagaInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/SagaInfo.cs index 3b82e9e9a84..eb082bbd0f1 100644 --- a/src/Mocha/src/Mocha.Analyzers/Models/SagaInfo.cs +++ b/src/Mocha/src/Mocha.Analyzers/Models/SagaInfo.cs @@ -6,10 +6,16 @@ namespace Mocha.Analyzers; /// The fully qualified type name of the saga class. /// The namespace containing the saga class. /// The fully qualified type name of the saga state type (TState). +/// The XML documentation captured from the saga declaration. +/// +/// The equatable source location of the saga type declaration, or if unavailable. +/// public sealed record SagaInfo( string SagaTypeName, string SagaNamespace, - string StateTypeName) : SyntaxInfo + string StateTypeName, + string? XmlDocumentation, + LocationInfo? Location) : SyntaxInfo { /// public override string OrderByKey => $"Saga:{SagaTypeName}"; diff --git a/src/Mocha/src/Mocha.Analyzers/Models/SourceMetadataOptionsInfo.cs b/src/Mocha/src/Mocha.Analyzers/Models/SourceMetadataOptionsInfo.cs new file mode 100644 index 00000000000..e7df409c8b7 --- /dev/null +++ b/src/Mocha/src/Mocha.Analyzers/Models/SourceMetadataOptionsInfo.cs @@ -0,0 +1,35 @@ +namespace Mocha.Analyzers; + +/// +/// Carries the evaluated MochaEmitSourceMetadata, ProjectDir, RepositoryUrl, +/// and SourceRevisionId MSBuild properties to source generators. +/// +/// +/// A value indicating whether SourceMetadata assignments are emitted into generated +/// registration code. +/// +/// +/// The MSBuild project directory used to relativize declaration file paths, or +/// when unavailable. +/// +/// +/// The source repository URL supplied by the build, or when unavailable. +/// +/// +/// The commit identifier supplied by the build, or when unavailable. +/// +public sealed record SourceMetadataOptionsInfo( + bool Emit, + string? ProjectDir, + string? RepositoryUrl, + string? Commit) : SyntaxInfo +{ + /// + /// Gets the default options: metadata emission enabled and no project directory, repository URL, + /// or commit available. + /// + public static SourceMetadataOptionsInfo Default { get; } = new(true, null, null, null); + + /// + public override string OrderByKey => "SourceMetadataOptions"; +} diff --git a/src/Mocha/src/Mocha.Analyzers/README.md b/src/Mocha/src/Mocha.Analyzers/README.md index 890cbb72330..c6e74c2e904 100644 --- a/src/Mocha/src/Mocha.Analyzers/README.md +++ b/src/Mocha/src/Mocha.Analyzers/README.md @@ -122,7 +122,7 @@ Invocations on `IMessageBus`, `ISender`, and `IPublisher` are inspected to disco When code calls a method annotated with `[MessagingModuleInfo]` (e.g., `builder.AddOrders()`), the inspector reads the `MessageTypes` array from the attribute. These types are treated as "already registered" and: -- Excluded from local serializer registration (no duplicate `AddMessageConfiguration`) +- Excluded from local serializer registration (no duplicate generated message configuration) - Counted as "covered" in AOT validation (MO0015, MO0016, MO0018) --- @@ -154,19 +154,18 @@ Registrations are emitted in this order: 1. **AOT Configuration** (if JsonContext specified) - `ModifyOptions(builder, o => o.IsAotCompatible = true)` - `AddJsonTypeInfoResolver(builder, {JsonContext}.Default)` -2. **Message Type Serializers** - `AddMessageConfiguration` per type -3. **Saga Configuration** - `AddSagaConfiguration` with state serializer +2. **Message Types** - `AddMessage` plus descriptor context configuration per type +3. **Saga Configuration** - `AddSaga` plus descriptor context configuration 4. **Batch Handlers** - sorted by handler type name 5. **Consumers** - sorted by handler type name 6. **Request Handlers** (RequestResponse + Send) - sorted by handler type name 7. **Event Handlers** - sorted by handler type name -8. **Saga Registrations** - `AddSaga` ### Handler Registration -Each handler emits `AddHandlerConfiguration` with a factory: +Each handler emits `AddConsumer(builder, ConsumerFactory...)` plus descriptor context configuration that points at a generated initializer type: -| Kind | Factory | +| Kind | Consumer helper | |------|---------| | Event | `ConsumerFactory.Subscribe()` | | Send | `ConsumerFactory.Send()` | @@ -176,7 +175,7 @@ Each handler emits `AddHandlerConfiguration` with a factory: ### `[MessagingModuleInfo]` Attribute Population -The `MessageTypes` array on the generated method contains **only types that receive `AddMessageConfiguration` calls** in the method body. This means: +The `MessageTypes` array on the generated method contains **only types that receive generated serializer configuration** in the method body. This means: - Only types present in the **local** `JsonSerializerContext` (not from referenced assemblies) - Excluding types already covered by imported modules @@ -198,7 +197,7 @@ Validation diagnostics fire when **`PublishAot` is true**. Serializer code gener ### What changes when JsonContext is specified -1. **Serializer registrations are emitted** - `AddMessageConfiguration` with pre-built `JsonMessageSerializer` for each message type in the local JsonContext. +1. **Serializer registrations are emitted** - `AddMessage` with generated descriptor configuration for each message type in the local JsonContext. 2. **JsonTypeInfoResolver is registered** - the specified `JsonSerializerContext` is added as a resolver. @@ -208,14 +207,14 @@ Validation diagnostics fire when **`PublishAot` is true**. Serializer code gener ### Which types get serializer registrations -A type gets an `AddMessageConfiguration` call if **all** of these are true: +A type gets generated message configuration if **all** of these are true: - It is declared as `[JsonSerializable(typeof(T))]` on the **local** `JsonSerializerContext` - It is NOT already imported from a referenced module - It is either a handler message/response type OR a context-only type ### Context-Only Types -Types declared in the `JsonSerializerContext` that have no corresponding handler or saga in the current assembly still receive `AddMessageConfiguration` registrations. These are types the module needs to serialize but doesn't consume. +Types declared in the `JsonSerializerContext` that have no corresponding handler or saga in the current assembly still receive generated message configuration. These are types the module needs to serialize but doesn't consume. ### Enclosed Types @@ -281,7 +280,7 @@ For each message type registration, the generator computes an "enclosed types" a | MO0016 | `PublishAot == true` | | MO0018 | `PublishAot == true` | -Handlers or sagas that carry a diagnostic (e.g., MO0012, MO0013, MO0014) are **excluded from code generation** - no `AddHandlerConfiguration` or `AddSagaConfiguration` is emitted for them. Only entries with zero diagnostics flow to the generator. +Handlers or sagas that carry a diagnostic (e.g., MO0012, MO0013, MO0014) are **excluded from code generation**. Only entries with zero diagnostics flow to the generator. --- @@ -305,4 +304,4 @@ The module system enables multiple assemblies to register their handlers indepen ### Key constraint -The `[MessagingModuleInfo]` attribute only advertises types for which the module emits `AddMessageConfiguration` calls. Types that are handled but don't have local serializer support (not in the local JsonContext) are **not** included in the attribute. This prevents downstream modules from incorrectly assuming serialization is covered. +The `[MessagingModuleInfo]` attribute only advertises types for which the module emits generated message configuration. Types that are handled but don't have local serializer support (not in the local JsonContext) are **not** included in the attribute. This prevents downstream modules from incorrectly assuming serialization is covered. diff --git a/src/Mocha/src/Mocha.Analyzers/SyntaxConstants.cs b/src/Mocha/src/Mocha.Analyzers/SyntaxConstants.cs index c80bc2c9dc0..4c0fdd4dc98 100644 --- a/src/Mocha/src/Mocha.Analyzers/SyntaxConstants.cs +++ b/src/Mocha/src/Mocha.Analyzers/SyntaxConstants.cs @@ -156,4 +156,16 @@ public static class SyntaxConstants /// Gets the metadata name for the IPublisher interface (Mediator). /// public const string IPublisher = "Mocha.Mediator.IPublisher"; + + /// + /// Gets the metadata name for the MessageBusHostBuilderExtensions class that declares + /// the AddMessage<TMessage> host builder registration methods. + /// + public const string MessageBusHostBuilderExtensions = "Mocha.MessageBusHostBuilderExtensions"; + + /// + /// Gets the metadata name for the IMessageBusBuilder interface that declares + /// the AddMessage<TMessage> registration methods. + /// + public const string IMessageBusBuilder = "Mocha.IMessageBusBuilder"; } diff --git a/src/Mocha/src/Mocha.Analyzers/Utils/DeduplicationHelper.cs b/src/Mocha/src/Mocha.Analyzers/Utils/DeduplicationHelper.cs new file mode 100644 index 00000000000..ec2010c9070 --- /dev/null +++ b/src/Mocha/src/Mocha.Analyzers/Utils/DeduplicationHelper.cs @@ -0,0 +1,45 @@ +namespace Mocha.Analyzers.Utils; + +/// +/// Collapses duplicate syntax information entries that share a generated method name. +/// A partial handler class that repeats its handler interface, or a partial saga that +/// repeats its base type, on more than one declaration part produces one entry per part. +/// Those entries differ only by source location, so record equality cannot collapse them, +/// and emitting one initializer per entry would produce duplicate method declarations. +/// +internal static class DeduplicationHelper +{ + /// + /// Groups the entries by their fully qualified type name and keeps a single deterministic + /// representative per group. The representative preference is: an entry that carries XML + /// documentation first, then an entry with a non-null location, then the smallest location + /// file path (ordinal), then the smallest location start line. The grouping preserves + /// first-occurrence order, so for inputs without duplicates the output sequence is + /// element-for-element identical to the input. + /// + /// The syntax information type being deduplicated. + /// The entries to deduplicate. + /// + /// Selects the fully qualified global::-prefixed type name that keys each group. + /// This is the same string the generated method name is salted on, so one group maps to + /// one generated initializer method name. + /// + /// Selects the XML documentation of an entry, if any. + /// Selects the source location of an entry, if any. + /// One representative entry per distinct type name, in first-occurrence order. + public static IEnumerable SelectRepresentatives( + IEnumerable infos, + Func typeNameSelector, + Func xmlDocumentationSelector, + Func locationSelector) + { + return infos + .GroupBy(typeNameSelector, StringComparer.Ordinal) + .Select(group => group + .OrderBy(info => xmlDocumentationSelector(info) is null ? 1 : 0) + .ThenBy(info => locationSelector(info) is null ? 1 : 0) + .ThenBy(info => locationSelector(info)?.FilePath ?? string.Empty, StringComparer.Ordinal) + .ThenBy(info => locationSelector(info)?.StartLine ?? 0) + .First()); + } +} diff --git a/src/Mocha/src/Mocha.Analyzers/Utils/GeneratedMetadataWriter.cs b/src/Mocha/src/Mocha.Analyzers/Utils/GeneratedMetadataWriter.cs new file mode 100644 index 00000000000..5b047186d0a --- /dev/null +++ b/src/Mocha/src/Mocha.Analyzers/Utils/GeneratedMetadataWriter.cs @@ -0,0 +1,222 @@ +using Microsoft.CodeAnalysis.CSharp; + +namespace Mocha.Analyzers.Utils; + +internal static class GeneratedMetadataWriter +{ + public static string ToSourceStringLiteral(this string value) + => SymbolDisplay.FormatLiteral(value, quote: true); + + public static void WriteSourceMetadata( + this CodeWriter writer, + string metadataTypeName, + string locationTypeName, + string assemblyName, + string? xmlDocumentation, + LocationInfo? location, + SourceMetadataOptionsInfo options, + bool trailingComma = true) + { + if (!options.Emit) + { + return; + } + + location = NormalizeLocation(location, options.ProjectDir); + + if (xmlDocumentation is null && location is null) + { + return; + } + + var suffix = trailingComma ? "," : string.Empty; + + writer.WriteIndentedLine("Source = new {0}", metadataTypeName); + writer.WriteIndentedLine("{"); + writer.IncreaseIndent(); + WriteAssembly(writer, assemblyName); + WriteRepositoryUrl(writer, options.RepositoryUrl); + WriteCommit(writer, options.Commit); + WriteXmlDocumentation(writer, xmlDocumentation, trailingComma: location is not null); + WriteDeclarationLocation(writer, locationTypeName, location, trailingComma: false); + writer.DecreaseIndent(); + writer.WriteIndentedLine("}}{0}", suffix); + } + + public static void WriteSourceMetadataAssignment( + this CodeWriter writer, + string target, + string metadataTypeName, + string locationTypeName, + string assemblyName, + string? xmlDocumentation, + LocationInfo? location, + SourceMetadataOptionsInfo options) + { + if (!options.Emit) + { + return; + } + + location = NormalizeLocation(location, options.ProjectDir); + + if (xmlDocumentation is null && location is null) + { + return; + } + + writer.WriteIndentedLine("{0} = new {1}", target, metadataTypeName); + writer.WriteIndentedLine("{"); + writer.IncreaseIndent(); + WriteAssembly(writer, assemblyName); + WriteRepositoryUrl(writer, options.RepositoryUrl); + WriteCommit(writer, options.Commit); + WriteXmlDocumentation(writer, xmlDocumentation, trailingComma: location is not null); + WriteDeclarationLocation(writer, locationTypeName, location, trailingComma: false); + writer.DecreaseIndent(); + writer.WriteIndentedLine("};"); + } + + private static LocationInfo? NormalizeLocation(LocationInfo? location, string? projectDir) + { + if (location is null) + { + return null; + } + + return location with { FilePath = NormalizeFilePath(location.FilePath, projectDir) }; + } + + private static string NormalizeFilePath(string filePath, string? projectDir) + { + if (filePath.Length == 0) + { + return filePath; + } + + var normalized = filePath.Replace('\\', '/'); + + if (!string.IsNullOrEmpty(projectDir)) + { + var normalizedProjectDir = projectDir!.Replace('\\', '/'); + + if (!normalizedProjectDir.EndsWith("/", StringComparison.Ordinal)) + { + normalizedProjectDir += "/"; + } + + if (normalized.StartsWith(normalizedProjectDir, StringComparison.OrdinalIgnoreCase)) + { + return normalized.Substring(normalizedProjectDir.Length); + } + } + + var lastSeparator = normalized.LastIndexOf('/'); + + return lastSeparator < 0 ? normalized : normalized.Substring(lastSeparator + 1); + } + + private static void WriteAssembly(CodeWriter writer, string assemblyName) + => writer.WriteIndentedLine("Assembly = {0},", assemblyName.ToSourceStringLiteral()); + + private static void WriteRepositoryUrl(CodeWriter writer, string? repositoryUrl) + { + if (repositoryUrl is null) + { + return; + } + + writer.WriteIndentedLine("RepositoryUrl = {0},", repositoryUrl.ToSourceStringLiteral()); + } + + private static void WriteCommit(CodeWriter writer, string? commit) + { + if (commit is null) + { + return; + } + + writer.WriteIndentedLine("Commit = {0},", commit.ToSourceStringLiteral()); + } + + private static void WriteXmlDocumentation( + CodeWriter writer, + string? xmlDocumentation, + bool trailingComma = true) + { + if (xmlDocumentation is null) + { + return; + } + + var delimiter = new string('"', GetRawStringDelimiterLength(xmlDocumentation)); + var indent = writer.GetIndentString(); + var suffix = trailingComma ? "," : string.Empty; + + writer.WriteIndentedLine("XmlDocumentation = {0}", delimiter); + + foreach (var line in SplitLines(xmlDocumentation)) + { + writer.Write(indent); + writer.Write(line); + writer.WriteLine(); + } + + writer.WriteIndentedLine("{0}{1}", delimiter, suffix); + } + + private static void WriteDeclarationLocation( + CodeWriter writer, + string locationTypeName, + LocationInfo? location, + bool trailingComma = true) + { + if (location is null) + { + return; + } + + var suffix = trailingComma ? "," : string.Empty; + + writer.WriteIndentedLine( + "DeclarationLocation = new {0}({1}, {2}, {3}, {4}, {5}){6}", + locationTypeName, + location.FilePath.ToSourceStringLiteral(), + location.StartLine, + location.StartColumn, + location.EndLine, + location.EndColumn, + suffix); + } + + private static int GetRawStringDelimiterLength(string value) + { + var maxQuotes = 0; + var currentQuotes = 0; + + foreach (var character in value) + { + if (character == '"') + { + currentQuotes++; + maxQuotes = Math.Max(maxQuotes, currentQuotes); + } + else + { + currentQuotes = 0; + } + } + + return Math.Max(3, maxQuotes + 1); + } + + private static IEnumerable SplitLines(string value) + { + using var reader = new StringReader(value); + string? line; + while ((line = reader.ReadLine()) is not null) + { + yield return line; + } + } +} diff --git a/src/Mocha/src/Mocha.Analyzers/Utils/HashHelper.cs b/src/Mocha/src/Mocha.Analyzers/Utils/HashHelper.cs index 2addb5d4f9b..6f182d671d8 100644 --- a/src/Mocha/src/Mocha.Analyzers/Utils/HashHelper.cs +++ b/src/Mocha/src/Mocha.Analyzers/Utils/HashHelper.cs @@ -28,4 +28,7 @@ public static string ComputeSalt(string assemblyName) return base64.Replace("+", "-").Replace("/", "_").TrimEnd('='); } + + public static string ComputeIdentifierSalt(string value) + => ModuleNameHelper.SanitizeIdentifier(ComputeSalt(value)); } diff --git a/src/Mocha/src/Mocha.Analyzers/Utils/RoslynExtensions.cs b/src/Mocha/src/Mocha.Analyzers/Utils/RoslynExtensions.cs index c14f1298319..792b599546f 100644 --- a/src/Mocha/src/Mocha.Analyzers/Utils/RoslynExtensions.cs +++ b/src/Mocha/src/Mocha.Analyzers/Utils/RoslynExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Mocha.Analyzers.Utils; @@ -58,7 +59,8 @@ public static bool ImplementsInterface(this INamedTypeSymbol type, INamedTypeSym } /// - /// Extracts an equatable from a Roslyn . + /// Extracts an equatable from a Roslyn , + /// preserving Roslyn's 0-based line and column values for diagnostic reconstruction. /// /// The Roslyn location to convert. /// @@ -80,4 +82,114 @@ public static bool ImplementsInterface(this INamedTypeSymbol type, INamedTypeSym span.EndLinePosition.Line, span.EndLinePosition.Character); } + + /// + /// Extracts an equatable from a Roslyn for + /// declaration metadata, producing 1-based line and column values matching editor coordinates. + /// + /// The Roslyn location to convert. + /// + /// A if the location is in source; otherwise, . + /// + public static LocationInfo? ToDeclarationLocationInfo(this Location location) + { + if (!location.IsInSource) + { + return null; + } + + var span = location.GetLineSpan(); + + return new LocationInfo( + span.Path, + span.StartLinePosition.Line + 1, + span.StartLinePosition.Character + 1, + span.EndLinePosition.Line + 1, + span.EndLinePosition.Character + 1); + } + + /// + /// Extracts the source location for an entire type declaration, producing 1-based line and + /// column values matching editor coordinates. + /// + public static LocationInfo? ToDeclarationLocationInfo(this TypeDeclarationSyntax typeDeclaration) + { + return typeDeclaration.GetLocation().ToDeclarationLocationInfo(); + } + + /// + /// Extracts XML documentation for a source declaration. + /// + public static string? GetXmlDocumentation( + this INamedTypeSymbol type, + CancellationToken cancellationToken) + { + var xml = type.GetDocumentationCommentXml( + preferredCulture: null, + expandIncludes: false, + cancellationToken: cancellationToken); + + return string.IsNullOrWhiteSpace(xml) ? null : xml; + } + + /// + /// Resolves the declaration location for a type symbol from its declaring syntax, choosing the + /// ordinally smallest location when the type is declared across multiple partial parts. + /// + /// + /// A for a source declaration; for a + /// metadata-only type that has no source declaration in the current compilation. + /// + public static LocationInfo? GetDeclarationLocationInfo( + this INamedTypeSymbol type, + CancellationToken cancellationToken) + { + // DeclaringSyntaxReferences has no guaranteed order across compilations or machines, so a + // partial type spread across parts must collapse to one stable location. Imposing a total order + // (path, then start line, then start column) and always taking the minimum keeps the emitted + // DeclarationLocation byte-stable, which reproducible builds and snapshot tests depend on. + // Smallest is an arbitrary but deterministic canonical choice; any consistent rule would do. + LocationInfo? smallest = null; + + foreach (var syntaxReference in type.DeclaringSyntaxReferences) + { + if (syntaxReference.GetSyntax(cancellationToken) is TypeDeclarationSyntax typeDeclaration) + { + smallest = LocationInfo.Min(smallest, typeDeclaration.ToDeclarationLocationInfo()); + } + } + + return smallest; + } + + /// + /// Captures equatable declaration metadata (fully qualified name, XML documentation, and location) + /// for a referenced type from its resolved symbol. + /// + /// + /// A for a source declaration; for a + /// metadata-only type that has no source declaration in the current compilation, or a non-named + /// type that cannot carry source declaration metadata. + /// + public static DeclaredTypeInfo? ToDeclaredTypeInfo( + this ITypeSymbol? type, + CancellationToken cancellationToken) + { + if (type is not INamedTypeSymbol namedType) + { + return null; + } + + var location = namedType.GetDeclarationLocationInfo(cancellationToken); + + if (location is null) + { + return null; + } + + return new DeclaredTypeInfo( + namedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + namedType.GetXmlDocumentation(cancellationToken), + location); + } } diff --git a/src/Mocha/src/Mocha.Analyzers/buildTransitive/Mocha.Analyzers.props b/src/Mocha/src/Mocha.Analyzers/buildTransitive/Mocha.Analyzers.props index 05c7c3b4610..7b3549d0ccb 100644 --- a/src/Mocha/src/Mocha.Analyzers/buildTransitive/Mocha.Analyzers.props +++ b/src/Mocha/src/Mocha.Analyzers/buildTransitive/Mocha.Analyzers.props @@ -2,5 +2,9 @@ + + + + diff --git a/src/Mocha/src/Mocha.Mediator/Configuration/MediatorHandlerConfiguration.cs b/src/Mocha/src/Mocha.Mediator/Configuration/MediatorHandlerConfiguration.cs index 96ac517e83b..9384b82c83f 100644 --- a/src/Mocha/src/Mocha.Mediator/Configuration/MediatorHandlerConfiguration.cs +++ b/src/Mocha/src/Mocha.Mediator/Configuration/MediatorHandlerConfiguration.cs @@ -37,4 +37,9 @@ public class MediatorHandlerConfiguration : MediatorConfiguration /// It is wrapped in middleware during pipeline compilation. /// public MediatorDelegate? Delegate { get; set; } + + /// + /// Source metadata captured from the handler declaration. + /// + public SourceMetadata? Source { get; set; } } diff --git a/src/Mocha/src/Mocha.Mediator/DependencyInjection/IMediatorBuilder.cs b/src/Mocha/src/Mocha.Mediator/DependencyInjection/IMediatorBuilder.cs index 5d09f817bf9..75f691db1c8 100644 --- a/src/Mocha/src/Mocha.Mediator/DependencyInjection/IMediatorBuilder.cs +++ b/src/Mocha/src/Mocha.Mediator/DependencyInjection/IMediatorBuilder.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using Microsoft.Extensions.DependencyInjection; namespace Mocha.Mediator; @@ -46,11 +45,4 @@ public interface IMediatorBuilder /// The handler implementation type. /// An optional action to configure the handler descriptor. void AddHandler(Action? configure = null) where THandler : class; - - /// - /// Registers a handler with a pre-built configuration. - /// This method is intended for use by source-generated code. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - void AddHandlerConfiguration(MediatorHandlerConfiguration configuration); } diff --git a/src/Mocha/src/Mocha.Mediator/DependencyInjection/MediatorBuilder.cs b/src/Mocha/src/Mocha.Mediator/DependencyInjection/MediatorBuilder.cs index 00cd1c14d82..99054fa2ef2 100644 --- a/src/Mocha/src/Mocha.Mediator/DependencyInjection/MediatorBuilder.cs +++ b/src/Mocha/src/Mocha.Mediator/DependencyInjection/MediatorBuilder.cs @@ -1,6 +1,5 @@ using System.Collections.Frozen; using System.Collections.Immutable; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.Extensions.DependencyInjection; @@ -128,40 +127,6 @@ public void AddHandler(Action? configure = } } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public void AddHandlerConfiguration(MediatorHandlerConfiguration configuration) - { - ArgumentNullException.ThrowIfNull(configuration); - - var handlerType = configuration.HandlerType!; - - void ApplyConfig(MediatorHandlerDescriptor d) - { - var config = d.Extend().Configuration; - config.MessageType = configuration.MessageType; - config.ResponseType = configuration.ResponseType; - config.Kind = configuration.Kind; - config.Delegate = configuration.Delegate; - } - - var existing = _handlerDescriptors.GetValueOrDefault(handlerType); - - if (existing is not null) - { - var inner = existing; - _handlerDescriptors[handlerType] = d => - { - inner(d); - ApplyConfig(d); - }; - } - else - { - _handlerDescriptors[handlerType] = ApplyConfig; - } - } - /// /// Builds the by creating an internal service provider, /// applying deferred service configurations, and compiling all registered pipelines. @@ -279,8 +244,8 @@ public MediatorRuntime Build(IServiceProvider applicationServices) _options.NotificationPublishMode); } - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use source-generated mediator module registration for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated mediator module registration for AOT compatibility.")] private static MediatorDelegate BuildPipelineViaReflection(MediatorHandlerConfiguration config) { return config.Kind switch diff --git a/src/Mocha/src/Mocha.Mediator/DependencyInjection/MediatorHostBuilderHandlerExtensions.cs b/src/Mocha/src/Mocha.Mediator/DependencyInjection/MediatorHostBuilderHandlerExtensions.cs index 994e952f1f8..4d45d535591 100644 --- a/src/Mocha/src/Mocha.Mediator/DependencyInjection/MediatorHostBuilderHandlerExtensions.cs +++ b/src/Mocha/src/Mocha.Mediator/DependencyInjection/MediatorHostBuilderHandlerExtensions.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -21,8 +20,8 @@ public static class MediatorHostBuilderHandlerExtensions /// The handler implementation type. /// The mediator host builder. /// An optional action to configure the handler descriptor. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use the source-generated mediator module registration for AOT compatibility.")] + [RequiresUnreferencedCode("Use the source-generated mediator module registration for AOT compatibility.")] public static IMediatorHostBuilder AddHandler( this IMediatorHostBuilder builder, Action? configure = null) @@ -37,29 +36,4 @@ public static IMediatorHostBuilder AddHandler( return builder; } - - /// - /// Registers a handler with the mediator using a pre-built configuration. - /// This method is intended for use by source-generated code. - /// - /// The handler implementation type. - /// The mediator host builder. - /// The pre-built handler configuration. - [EditorBrowsable(EditorBrowsableState.Never)] - public static IMediatorHostBuilder AddHandlerConfiguration< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( - this IMediatorHostBuilder builder, - MediatorHandlerConfiguration configuration) - where THandler : class - { - ArgumentNullException.ThrowIfNull(builder); - ArgumentNullException.ThrowIfNull(configuration); - - builder.Services.TryAdd(new ServiceDescriptor( - typeof(THandler), typeof(THandler), builder.Options.ServiceLifetime)); - - builder.ConfigureMediator(b => b.AddHandlerConfiguration(configuration)); - - return builder; - } } diff --git a/src/Mocha/src/Mocha.Mediator/Descriptors/MediatorHandlerDescriptor.cs b/src/Mocha/src/Mocha.Mediator/Descriptors/MediatorHandlerDescriptor.cs index 25f7dc83e76..26a782a2467 100644 --- a/src/Mocha/src/Mocha.Mediator/Descriptors/MediatorHandlerDescriptor.cs +++ b/src/Mocha/src/Mocha.Mediator/Descriptors/MediatorHandlerDescriptor.cs @@ -47,7 +47,7 @@ public MediatorHandlerConfiguration CreateConfiguration() } [RequiresUnreferencedCode( - "Handler detection uses reflection. Use source-generated AddHandlerConfiguration for AOT compatibility.")] + "Handler detection uses reflection. Use source-generated mediator module registration for AOT compatibility.")] private void DetectHandler(Type handlerType) { var found = false; diff --git a/src/Mocha/src/Mocha.Utilities/Features/FeatureCollectionExtensions.cs b/src/Mocha/src/Mocha.Utilities/Features/FeatureCollectionExtensions.cs index f40bbcb9f70..f144d2bf42e 100644 --- a/src/Mocha/src/Mocha.Utilities/Features/FeatureCollectionExtensions.cs +++ b/src/Mocha/src/Mocha.Utilities/Features/FeatureCollectionExtensions.cs @@ -183,4 +183,21 @@ public static void CopyTo(this IFeatureCollection source, IFeatureCollection tar target[type] = feature; } } + + /// + /// Removes the specified feature from the collection. + /// + /// The feature key. + /// The feature collection. + /// + /// true if the feature was present and removed; otherwise, false. + /// + public static bool Remove(this IFeatureCollection featureCollection) + { + ArgumentNullException.ThrowIfNull(featureCollection); + + var existed = featureCollection[typeof(TFeature)] is not null; + featureCollection[typeof(TFeature)] = null; + return existed; + } } diff --git a/src/Mocha/src/Mocha/Abstractions/IMessageBusBuilder.cs b/src/Mocha/src/Mocha/Abstractions/IMessageBusBuilder.cs index 06c551b34bc..dd545b7501e 100644 --- a/src/Mocha/src/Mocha/Abstractions/IMessageBusBuilder.cs +++ b/src/Mocha/src/Mocha/Abstractions/IMessageBusBuilder.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Mocha.Middlewares; @@ -18,18 +17,18 @@ public interface IMessageBusBuilder /// The handler type implementing . /// Optional action to configure the consumer descriptor. /// The builder instance for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - IMessageBusBuilder AddHandler(Action? configure = null) + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + IMessageBusBuilder AddHandler( + Action? configure = null) where THandler : class, IHandler; /// - /// Registers a handler using pre-built configuration from the source generator. + /// Registers a pre-built consumer with the message bus. /// - /// The pre-built handler configuration. + /// The consumer to register. /// The builder instance for method chaining. - [EditorBrowsable(EditorBrowsableState.Never)] - IMessageBusBuilder AddHandlerConfiguration(MessagingHandlerConfiguration configuration); + IMessageBusBuilder AddConsumer(Consumer consumer); /// /// Registers a batch event handler that collects messages and delivers them in batches. @@ -37,9 +36,10 @@ IMessageBusBuilder AddHandler(Action? configure = /// The batch handler type. /// Optional action to configure batch options. /// The builder instance for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - IMessageBusBuilder AddBatchHandler(Action? configure = null) + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + IMessageBusBuilder AddBatchHandler( + Action? configure = null) where THandler : class, IBatchEventHandler; /// @@ -100,21 +100,11 @@ IMessageBusBuilder AddBatchHandler(Action? configure = n IMessageBusBuilder Host(Action configure); /// - /// Registers a message type using pre-built configuration from the source generator. + /// Registers a message type with the bus. /// - /// The pre-built message configuration. - /// The builder instance for method chaining. - [EditorBrowsable(EditorBrowsableState.Never)] - IMessageBusBuilder AddMessageConfiguration(MessagingMessageConfiguration configuration); - - /// - /// Registers a saga using pre-built configuration from the source generator. - /// - /// The saga type. - /// The pre-built saga configuration. + /// The CLR type of the message. /// The builder instance for method chaining. - [EditorBrowsable(EditorBrowsableState.Never)] - IMessageBusBuilder AddSagaConfiguration(MessagingSagaConfiguration configuration) where TSaga : Saga, new(); + IMessageBusBuilder AddMessage() where TMessage : notnull; /// /// Registers a message type with the bus and configures its serialization, routing, and metadata. @@ -122,7 +112,7 @@ IMessageBusBuilder AddBatchHandler(Action? configure = n /// The CLR type of the message. /// An action to configure the message type descriptor. /// The builder instance for method chaining. - IMessageBusBuilder AddMessage(Action configure) where TMessage : class; + IMessageBusBuilder AddMessage(Action configure) where TMessage : notnull; /// /// Builds and returns the fully configured using the specified diff --git a/src/Mocha/src/Mocha/Builder/ConsumerRegistration.cs b/src/Mocha/src/Mocha/Builder/ConsumerRegistration.cs deleted file mode 100644 index cf6ae756adb..00000000000 --- a/src/Mocha/src/Mocha/Builder/ConsumerRegistration.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Mocha; - -/// -/// Captures a deferred consumer registration with its factory and stacked configuration. -/// -internal sealed class ConsumerRegistration -{ - /// - /// The handler type that identifies this registration. Used for linear scan lookups. - /// - public required Type HandlerType { get; init; } - - /// - /// The composed consumer descriptor configuration action. - /// Multiple AddHandler calls stack onto this via closure composition. - /// Null when no configuration has been applied. - /// - public Action? Configure { get; set; } - - /// - /// Factory that creates the consumer instance. - /// First registration wins - this is never replaced after initial creation. - /// - public required Func?, Consumer> Factory { get; init; } -} diff --git a/src/Mocha/src/Mocha/Builder/MessageBusBuilder.cs b/src/Mocha/src/Mocha/Builder/MessageBusBuilder.cs index 7fc4dc14225..f6afb2b6d49 100644 --- a/src/Mocha/src/Mocha/Builder/MessageBusBuilder.cs +++ b/src/Mocha/src/Mocha/Builder/MessageBusBuilder.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization.Metadata; using Microsoft.Extensions.DependencyInjection; @@ -19,11 +18,11 @@ public partial class MessageBusBuilder : IMessageBusBuilder { private readonly MessagingOptions _messagingOptions = new(); - private readonly Dictionary> _messageDescriptors = []; + private readonly Dictionary _messageTypes = []; - private readonly List _consumerRegistrations = []; + private readonly List _consumers = []; - private readonly List _sagaRegistrations = []; + private readonly Dictionary> _sagas = []; private readonly List _transports = []; @@ -33,6 +32,7 @@ public partial class MessageBusBuilder : IMessageBusBuilder private readonly List> _configureServices = []; private readonly List> _configureFeatures = []; + private readonly List> _configureDescriptorContext = []; /// public IMessageBusBuilder ConfigureServices(Action configure) @@ -66,128 +66,103 @@ public IMessageBusBuilder ConfigureFeature(Action configure) return this; } - /// - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - public IMessageBusBuilder AddHandler(Action? configure = null) + internal void ConfigureDescriptorContext(Action configure) + { + ArgumentNullException.ThrowIfNull(configure); + + _configureDescriptorContext.Add(configure); + } + + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + public IMessageBusBuilder AddHandler( + Action? configure = null) where THandler : class, IHandler { var handlerType = typeof(THandler); - var existing = _consumerRegistrations.Find(r => r.HandlerType == handlerType); + var existing = _consumers.Find(c => c.Identity == handlerType); if (existing is not null) { if (configure is not null) { - var inner = existing.Configure; - existing.Configure = inner is not null - ? d => - { - inner(d); - configure(d); - } - : configure; + ConfigureDescriptorContext(context => + context.Configurations.Add(handlerType, configure)); } return this; } - // New registration - detect kind and create factory - Func?, Consumer> factory; + Consumer consumer; if (typeof(IBatchEventHandler).IsAssignableFrom(typeof(THandler)) && THandler.EventType is not null) { - factory = static c => - { - var consumerType = typeof(BatchConsumer<,>).MakeGenericType(typeof(THandler), THandler.EventType); - return (Consumer)Activator.CreateInstance(consumerType, c)!; - }; + var consumerType = typeof(BatchConsumer<,>).MakeGenericType(typeof(THandler), THandler.EventType); + consumer = (Consumer)Activator.CreateInstance(consumerType)!; } else if (typeof(IConsumer).IsAssignableFrom(typeof(THandler)) && THandler.EventType is not null) { - factory = static c => - { - var consumerType = typeof(ConsumerAdapter<,>).MakeGenericType(typeof(THandler), THandler.EventType); - return (Consumer)Activator.CreateInstance(consumerType, c)!; - }; + var consumerType = typeof(ConsumerAdapter<,>).MakeGenericType(typeof(THandler), THandler.EventType); + consumer = (Consumer)Activator.CreateInstance(consumerType)!; } else if (THandler.RequestType is not null && THandler.ResponseType is not null) { - factory = static c => - { - var consumerType = typeof(RequestConsumer<,,>).MakeGenericType( - typeof(THandler), - THandler.RequestType, - THandler.ResponseType); + var consumerType = typeof(RequestConsumer<,,>).MakeGenericType( + typeof(THandler), + THandler.RequestType, + THandler.ResponseType); - return (Consumer)Activator.CreateInstance(consumerType, c)!; - }; + consumer = (Consumer)Activator.CreateInstance(consumerType)!; } else if (THandler.RequestType is not null) { - factory = static c => - { - var consumerType = typeof(SendConsumer<,>).MakeGenericType(typeof(THandler), THandler.RequestType); - return (Consumer)Activator.CreateInstance(consumerType, c)!; - }; + var consumerType = typeof(SendConsumer<,>).MakeGenericType(typeof(THandler), THandler.RequestType); + consumer = (Consumer)Activator.CreateInstance(consumerType)!; } else if (THandler.EventType is not null) { - factory = static c => - { - var consumerType = typeof(SubscribeConsumer<,>).MakeGenericType(typeof(THandler), THandler.EventType); - return (Consumer)Activator.CreateInstance(consumerType, c)!; - }; + var consumerType = typeof(SubscribeConsumer<,>).MakeGenericType(typeof(THandler), THandler.EventType); + consumer = (Consumer)Activator.CreateInstance(consumerType)!; } else { throw ThrowHelper.InvalidHandlerType(); } - _consumerRegistrations.Add( - new ConsumerRegistration - { - HandlerType = handlerType, - Configure = configure, - Factory = factory - }); + AddConsumer(consumer); + + if (configure is not null) + { + ConfigureDescriptorContext(context => + context.Configurations.Add(handlerType, configure)); + } return this; } - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IMessageBusBuilder AddHandlerConfiguration(MessagingHandlerConfiguration configuration) + public IMessageBusBuilder AddConsumer(Consumer consumer) { - var handlerType = configuration.HandlerType; - var existing = _consumerRegistrations.Find(r => r.HandlerType == handlerType); + ArgumentNullException.ThrowIfNull(consumer); - if (existing is not null) + if (_consumers.Exists(c => c.Identity == consumer.Identity)) { - // Factory first-wins, nothing else to stack from SG path return this; } - _consumerRegistrations.Add( - new ConsumerRegistration - { - HandlerType = handlerType, - Configure = null, - Factory = configuration.Factory - }); + _consumers.Add(consumer); return this; } - /// /// Registers a batch event handler with the message bus. /// /// The batch handler type. /// Optional action to configure batch options. /// The builder instance for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - public IMessageBusBuilder AddBatchHandler(Action? configure = null) + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + public IMessageBusBuilder AddBatchHandler( + Action? configure = null) where THandler : class, IBatchEventHandler { var options = new BatchOptions(); @@ -201,88 +176,34 @@ public IMessageBusBuilder AddBatchHandler(Action? config /// public IMessageBusBuilder AddSaga() where TSaga : Saga, new() { - var sagaType = typeof(TSaga); - - _sagaRegistrations.RemoveAll(r => r.SagaType == sagaType); - - _sagaRegistrations.Add(new SagaRegistration { SagaType = sagaType, Factory = static () => new TSaga() }); + _sagas.TryAdd(typeof(TSaga), static () => new TSaga()); return this; } /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IMessageBusBuilder AddMessageConfiguration(MessagingMessageConfiguration configuration) + public IMessageBusBuilder AddMessage() + where TMessage : notnull { - var messageType = configuration.MessageType; - var serializer = configuration.Serializer; - var enclosedTypes = configuration.EnclosedTypes; - - var existingDelegate = _messageDescriptors.GetValueOrDefault(messageType); + var runtimeType = typeof(TMessage); - if (existingDelegate is not null) + if (!_messageTypes.ContainsKey(runtimeType)) { - var innerDelegate = existingDelegate; - _messageDescriptors[messageType] = descriptor => - { - innerDelegate(descriptor); - Configure(descriptor); - }; - } - else - { - _messageDescriptors[messageType] = Configure; + _messageTypes.Add(runtimeType, new MessageType(runtimeType)); } return this; - - void Configure(IMessageTypeDescriptor descriptor) - { - descriptor.AddSerializer(serializer); - - if (!enclosedTypes.IsDefaultOrEmpty) - { - descriptor.Extend().Configuration.EnclosedTypes = enclosedTypes; - } - } - } - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IMessageBusBuilder AddSagaConfiguration(MessagingSagaConfiguration configuration) - where TSaga : Saga, new() - { - var sagaType = typeof(TSaga); - - _sagaRegistrations.RemoveAll(r => r.SagaType == sagaType); - - _sagaRegistrations.Add( - new SagaRegistration - { - SagaType = sagaType, - Factory = static () => new TSaga(), - StateSerializer = configuration.StateSerializer - }); - - return this; } /// - public IMessageBusBuilder AddMessage(Action configure) where TMessage : class + public IMessageBusBuilder AddMessage(Action configure) where TMessage : notnull { - var configureDelegate = _messageDescriptors.GetValueOrDefault(typeof(TMessage)); + ArgumentNullException.ThrowIfNull(configure); - if (configureDelegate is not null) - { - var innerDelegate = configureDelegate; - configureDelegate = descriptor => - { - innerDelegate(descriptor); - configure(descriptor); - }; - } + AddMessage(); - _messageDescriptors[typeof(TMessage)] = configureDelegate ?? configure; + ConfigureDescriptorContext(context => + context.Configurations.Add(typeof(TMessage), configure)); return this; } @@ -436,32 +357,23 @@ public MessagingRuntime Build(IServiceProvider applicationServices) var responseManager = applicationServices.GetRequiredService(); - // Materialize consumers from registrations var consumerList = new List { - // Infrastructure consumer new ReplyConsumer(responseManager) }; - // Handler consumers from registrations - foreach (var reg in _consumerRegistrations) + foreach (var consumer in _consumers) { - consumerList.Add(reg.Factory(reg.Configure)); + consumerList.Add(consumer); } - // Saga consumers var sagas = new List(); - foreach (var reg in _sagaRegistrations) + foreach (var createSaga in _sagas.Values) { - var saga = reg.Factory(); + var saga = createSaga(); - if (reg.StateSerializer is not null) - { - saga.StateSerializer = reg.StateSerializer; - } - - sagas.Add(saga); consumerList.Add(saga.Consumer); + sagas.Add(saga); } var consumers = consumerList.ToImmutableArray(); @@ -479,6 +391,7 @@ public MessagingRuntime Build(IServiceProvider applicationServices) var features = new FeatureCollection(); servicesCollection.AddSingleton(features); + features.Set(new MessagingConfigurationFeature(new MessagingConfigurationContainer())); var services = servicesCollection.BuildServiceProvider(); @@ -517,20 +430,17 @@ public MessagingRuntime Build(IServiceProvider applicationServices) Conventions = conventions }; - foreach (var (type, configureDelegate) in _messageDescriptors) + foreach (var configure in _configureDescriptorContext) { - var descriptor = new MessageTypeDescriptor(setupContext, type); - - configureDelegate(descriptor); - - var configuration = descriptor.CreateConfiguration(); + configure(setupContext); + } - var messageType = new MessageType(); - messageType.Initialize(setupContext, configuration); + foreach (var messageType in _messageTypes.Values) + { + messageType.Initialize(setupContext); messageRegistry.AddMessageType(messageType); } - // sagas have to be initialized before consumers, because of the saga consumer foreach (var saga in sagas) { saga.Initialize(setupContext); @@ -613,6 +523,9 @@ public MessagingRuntime Build(IServiceProvider applicationServices) setupContext.Transport = null; + // The configuration container is setup scratch state; remove it before the features are sealed into the runtime. + features.Remove(); + var runtime = new MessagingRuntime( services, _messagingOptions, diff --git a/src/Mocha/src/Mocha/Builder/MessageBusSetup.cs b/src/Mocha/src/Mocha/Builder/MessageBusSetup.cs index 9011364720e..52a422ebf33 100644 --- a/src/Mocha/src/Mocha/Builder/MessageBusSetup.cs +++ b/src/Mocha/src/Mocha/Builder/MessageBusSetup.cs @@ -2,5 +2,5 @@ namespace Mocha; internal sealed class MessageBusSetup { - public List> ConfigureMessageBus { get; set; } = []; + public List> ConfigureMessageBus { get; set; } = []; } diff --git a/src/Mocha/src/Mocha/Builder/SagaRegistration.cs b/src/Mocha/src/Mocha/Builder/SagaRegistration.cs deleted file mode 100644 index 80ea7090a06..00000000000 --- a/src/Mocha/src/Mocha/Builder/SagaRegistration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Mocha.Sagas; - -namespace Mocha; - -/// -/// Captures a deferred saga registration for deduplication. -/// -internal sealed class SagaRegistration -{ - /// - /// The saga type that identifies this registration. - /// - public required Type SagaType { get; init; } - - /// - /// Factory that creates the saga instance. - /// - public required Func Factory { get; init; } - - /// - /// Optional pre-built state serializer provided by the source generator. - /// When set, the saga uses this serializer instead of resolving one from the factory. - /// - public ISagaStateSerializer? StateSerializer { get; init; } -} diff --git a/src/Mocha/src/Mocha/Configuration/MessagingConfigurationContainer.cs b/src/Mocha/src/Mocha/Configuration/MessagingConfigurationContainer.cs new file mode 100644 index 00000000000..3e412dffdac --- /dev/null +++ b/src/Mocha/src/Mocha/Configuration/MessagingConfigurationContainer.cs @@ -0,0 +1,80 @@ +namespace Mocha; + +/// +/// Stores descriptor configuration actions that are applied when messaging types are initialized. +/// +public sealed class MessagingConfigurationContainer +{ + private readonly HashSet<(Type DescriptorType, string Key)> _created = []; + private readonly Dictionary<(Type RuntimeType, Type DescriptorType), List> _configurations = []; + + public void Add( + Type runtimeType, + Action configure) + where TDescriptor : IMessagingDescriptor + { + ArgumentNullException.ThrowIfNull(runtimeType); + ArgumentNullException.ThrowIfNull(configure); + + AddInternal(runtimeType, typeof(TDescriptor), configure); + } + + public void TryAdd( + string key, + Type runtimeType, + Action configure) + where TDescriptor : IMessagingDescriptor + { + ArgumentException.ThrowIfNullOrEmpty(key); + ArgumentNullException.ThrowIfNull(runtimeType); + ArgumentNullException.ThrowIfNull(configure); + + TryAddInternal(key, typeof(TDescriptor), runtimeType, configure); + } + + internal void Apply( + Type runtimeType, + TDescriptor descriptor) + where TDescriptor : IMessagingDescriptor + { + ArgumentNullException.ThrowIfNull(runtimeType); + ArgumentNullException.ThrowIfNull(descriptor); + + if (!_configurations.TryGetValue((runtimeType, typeof(TDescriptor)), out var configurations)) + { + return; + } + + foreach (var item in configurations) + { + ((Action)item)(descriptor); + } + } + + private void AddInternal(Type runtimeType, Type descriptorType, object configuration) + { + if (!_configurations.TryGetValue((runtimeType, descriptorType), out var list)) + { + list = []; + _configurations[(runtimeType, descriptorType)] = list; + } + + list.Add(configuration); + } + + private void TryAddInternal(string key, Type descriptorType, Type runtimeType, object configuration) + { + if (!_created.Add((descriptorType, key))) + { + return; + } + + if (!_configurations.TryGetValue((runtimeType, descriptorType), out var list)) + { + list = []; + _configurations[(runtimeType, descriptorType)] = list; + } + + list.Add(configuration); + } +} diff --git a/src/Mocha/src/Mocha/Configuration/MessagingConfigurationContextExtensions.cs b/src/Mocha/src/Mocha/Configuration/MessagingConfigurationContextExtensions.cs new file mode 100644 index 00000000000..ce9a7f4b857 --- /dev/null +++ b/src/Mocha/src/Mocha/Configuration/MessagingConfigurationContextExtensions.cs @@ -0,0 +1,21 @@ +using Mocha.Features; + +namespace Mocha; + +internal static class MessagingConfigurationContextExtensions +{ + extension(IMessagingConfigurationContext context) + { + internal MessagingConfigurationContainer Configurations + => context.Features.GetRequired().Configurations; + + internal void ApplyConfigurations(Type runtimeType, TDescriptor descriptor) + where TDescriptor : IMessagingDescriptor + { + if (context.Features.Get() is { } feature) + { + feature.Configurations.Apply(runtimeType, descriptor); + } + } + } +} diff --git a/src/Mocha/src/Mocha/Configuration/MessagingConfigurationFeature.cs b/src/Mocha/src/Mocha/Configuration/MessagingConfigurationFeature.cs new file mode 100644 index 00000000000..1fa914ac69f --- /dev/null +++ b/src/Mocha/src/Mocha/Configuration/MessagingConfigurationFeature.cs @@ -0,0 +1,9 @@ +namespace Mocha; + +/// +/// Provides generated and user descriptor configuration during messaging setup. +/// +public sealed class MessagingConfigurationFeature(MessagingConfigurationContainer configurations) +{ + public MessagingConfigurationContainer Configurations { get; } = configurations; +} diff --git a/src/Mocha/src/Mocha/Configuration/MessagingHandlerConfiguration.cs b/src/Mocha/src/Mocha/Configuration/MessagingHandlerConfiguration.cs deleted file mode 100644 index 262bd0b3de0..00000000000 --- a/src/Mocha/src/Mocha/Configuration/MessagingHandlerConfiguration.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.ComponentModel; - -namespace Mocha; - -/// -/// Pre-built handler configuration emitted by the source generator. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -public sealed class MessagingHandlerConfiguration -{ - /// - /// The concrete handler implementation type. - /// - public required Type HandlerType { get; init; } - - /// - /// Factory that creates the consumer instance for this handler. - /// - public required Func?, Consumer> Factory { get; init; } -} diff --git a/src/Mocha/src/Mocha/Configuration/MessagingMessageConfiguration.cs b/src/Mocha/src/Mocha/Configuration/MessagingMessageConfiguration.cs deleted file mode 100644 index 0732e59e21a..00000000000 --- a/src/Mocha/src/Mocha/Configuration/MessagingMessageConfiguration.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Immutable; -using System.ComponentModel; - -namespace Mocha; - -/// -/// Represents a pre-built message type configuration emitted by the source generator. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -public sealed class MessagingMessageConfiguration -{ - /// - /// Gets the CLR type of the message. - /// - public required Type MessageType { get; init; } - - /// - /// Gets the pre-built serializer for this message type. - /// - public required IMessageSerializer Serializer { get; init; } - - /// - /// Gets the pre-computed type hierarchy sorted by specificity (most specific first). Includes both - /// registered user types and framework base types (, - /// ); branches on whether - /// each entry is a framework base type to decide registration versus identity-only recording. - /// When is , - /// falls back to reflection-based hierarchy discovery - /// (non-AOT only). - /// - public ImmutableArray EnclosedTypes { get; init; } -} diff --git a/src/Mocha/src/Mocha/Configuration/MessagingSagaConfiguration.cs b/src/Mocha/src/Mocha/Configuration/MessagingSagaConfiguration.cs deleted file mode 100644 index 2a16e4a9c6f..00000000000 --- a/src/Mocha/src/Mocha/Configuration/MessagingSagaConfiguration.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel; -using Mocha.Sagas; - -namespace Mocha; - -/// -/// Pre-built saga configuration emitted by the source generator. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -public sealed class MessagingSagaConfiguration -{ - /// - /// The CLR type of the saga. - /// - public required Type SagaType { get; init; } - - /// - /// The pre-built state serializer for this saga, or to use the runtime fallback. - /// - public ISagaStateSerializer? StateSerializer { get; init; } -} diff --git a/src/Mocha/src/Mocha/ConsumerFactory.cs b/src/Mocha/src/Mocha/ConsumerFactory.cs index 565e5a2205d..308bf7f5855 100644 --- a/src/Mocha/src/Mocha/ConsumerFactory.cs +++ b/src/Mocha/src/Mocha/ConsumerFactory.cs @@ -4,30 +4,29 @@ namespace Mocha; /// /// Provides factory methods for creating consumer instances. -/// These methods bridge internal consumer types for source-generator use. /// [EditorBrowsable(EditorBrowsableState.Never)] public static class ConsumerFactory { - public static Func?, Consumer> Subscribe() - where THandler : IEventHandler - => static c => new SubscribeConsumer(c ?? (static _ => { })); + public static Consumer Subscribe() + where THandler : class, IEventHandler + => new SubscribeConsumer(); - public static Func?, Consumer> Send() - where THandler : IEventRequestHandler + public static Consumer Send() + where THandler : class, IEventRequestHandler where TRequest : notnull - => static c => new SendConsumer(c ?? (static _ => { })); + => new SendConsumer(); - public static Func?, Consumer> Request() - where THandler : IEventRequestHandler + public static Consumer Request() + where THandler : class, IEventRequestHandler where TRequest : IEventRequest - => static c => new RequestConsumer(c ?? (static _ => { })); + => new RequestConsumer(); - public static Func?, Consumer> Consume() - where TConsumer : IConsumer - => static c => new ConsumerAdapter(c ?? (static _ => { })); + public static Consumer Consume() + where TConsumer : class, IConsumer + => new ConsumerAdapter(); - public static Func?, Consumer> Batch() - where THandler : IBatchEventHandler - => static c => new BatchConsumer(c ?? (static _ => { })); + public static Consumer Batch() + where THandler : class, IBatchEventHandler + => new BatchConsumer(); } diff --git a/src/Mocha/src/Mocha/Consumers/Configurations/ConsumerConfiguration.cs b/src/Mocha/src/Mocha/Consumers/Configurations/ConsumerConfiguration.cs index 7c1c04f87e5..1238d072610 100644 --- a/src/Mocha/src/Mocha/Consumers/Configurations/ConsumerConfiguration.cs +++ b/src/Mocha/src/Mocha/Consumers/Configurations/ConsumerConfiguration.cs @@ -28,4 +28,9 @@ public class ConsumerConfiguration : MessagingConfiguration /// at build time. /// public List>> ConsumerPipelineModifiers { get; set; } = []; + + /// + /// Source metadata captured from the handler declaration. + /// + public SourceMetadata? Source { get; set; } } diff --git a/src/Mocha/src/Mocha/Consumers/Consumer.cs b/src/Mocha/src/Mocha/Consumers/Consumer.cs index a52416c3902..11862f2f01a 100644 --- a/src/Mocha/src/Mocha/Consumers/Consumer.cs +++ b/src/Mocha/src/Mocha/Consumers/Consumer.cs @@ -13,26 +13,18 @@ namespace Mocha; /// public abstract class Consumer { - private readonly Action _configure; - - /// - /// Creates a consumer with an external configuration action for the consumer descriptor. - /// - /// - /// The action used to configure the consumer descriptor during initialization. - /// - protected Consumer(Action configure) - { - _configure = configure; - } - /// /// Creates a consumer that uses the virtual method /// for descriptor setup. /// protected Consumer() { - _configure = Configure; + Identity = GetType(); + } + + protected Consumer(Type identity) + { + Identity = identity; } /// @@ -54,7 +46,7 @@ protected virtual void Configure(IConsumerDescriptor descriptor) { } /// /// Gets the CLR type that identifies this consumer, typically the handler type it wraps. /// - public Type Identity { get; private set; } = null!; + public Type Identity { get; } private protected ConsumerDelegate Pipeline { get; private set; } = null!; @@ -119,17 +111,10 @@ internal void Initialize(IMessagingSetupContext context) OnBeforeInitialize(context); - Configuration = CreateConfiguration(context); - - if (Configuration is null) - { - throw ThrowHelper.HandlerConfigurationMissing(); - } + Configuration = CreateConfiguration(context) ?? throw ThrowHelper.HandlerConfigurationMissing(); // TODO should we assign a default name in the Action? GetType().Name? Name = Configuration.Name ?? throw ThrowHelper.ConsumerNameRequired(); - Identity ??= GetType(); - foreach (var route in Configuration!.Routes) { route.Consumer = this; @@ -145,11 +130,6 @@ internal void Initialize(IMessagingSetupContext context) MarkInitialized(); } - protected void SetIdentity(Type identity) - { - Identity = identity; - } - protected virtual void OnBeforeInitialize(IMessagingSetupContext context) { } protected virtual void OnAfterInitialize(IMessagingSetupContext context) { } @@ -217,7 +197,11 @@ public virtual ConsumerDescription Describe() private ConsumerConfiguration CreateConfiguration(IMessagingSetupContext discoveryContext) { var descriptor = new ConsumerDescriptor(discoveryContext); - _configure(descriptor); + + Configure(descriptor); + + discoveryContext.ApplyConfigurations(Identity, descriptor); + return descriptor.CreateConfiguration(); } } diff --git a/src/Mocha/src/Mocha/Consumers/Implementations/BatchConsumer.cs b/src/Mocha/src/Mocha/Consumers/Implementations/BatchConsumer.cs index 4c886cf93cc..3fc37971d72 100644 --- a/src/Mocha/src/Mocha/Consumers/Implementations/BatchConsumer.cs +++ b/src/Mocha/src/Mocha/Consumers/Implementations/BatchConsumer.cs @@ -15,10 +15,11 @@ namespace Mocha; /// completes. This preserves existing middleware semantics (ACK, fault, circuit breaker) /// without any modifications to the middleware chain. /// -internal sealed class BatchConsumer( - Action? configure = null) - : Consumer where THandler : IBatchEventHandler +internal sealed class BatchConsumer : Consumer + where THandler : class, IBatchEventHandler { + public BatchConsumer() : base(typeof(THandler)) { } + private BatchCollector _collector = null!; private Channel> _channel = null!; private ChannelProcessor> _processor = null!; @@ -31,14 +32,11 @@ protected override void Configure(IConsumerDescriptor descriptor) descriptor .Name(typeof(THandler).Name) .AddRoute(r => r.MessageType(typeof(TEvent)).Kind(InboundRouteKind.Subscribe)); - - configure?.Invoke(descriptor); } protected override void OnAfterInitialize(IMessagingSetupContext context) { base.OnAfterInitialize(context); - SetIdentity(typeof(THandler)); var options = Configuration!.Features.Get() ?? new BatchOptions(); options.Validate(); diff --git a/src/Mocha/src/Mocha/Consumers/Implementations/ConsumerAdapter.cs b/src/Mocha/src/Mocha/Consumers/Implementations/ConsumerAdapter.cs index 33cbecea7f2..99e57df529c 100644 --- a/src/Mocha/src/Mocha/Consumers/Implementations/ConsumerAdapter.cs +++ b/src/Mocha/src/Mocha/Consumers/Implementations/ConsumerAdapter.cs @@ -10,30 +10,16 @@ namespace Mocha; /// as a bus-internal , but passes the full /// instead of just the deserialized message. /// -internal sealed class ConsumerAdapter : Consumer where TConsumer : IConsumer +internal sealed class ConsumerAdapter : Consumer + where TConsumer : class, IConsumer { - private readonly Action? _configure; - - public ConsumerAdapter(Action configure) - { - _configure = configure; - } - - public ConsumerAdapter() { } + public ConsumerAdapter() : base(typeof(TConsumer)) { } protected override void Configure(IConsumerDescriptor descriptor) { descriptor .Name(typeof(TConsumer).Name) .AddRoute(r => r.MessageType(typeof(TMessage)).Kind(InboundRouteKind.Subscribe)); - - _configure?.Invoke(descriptor); - } - - protected override void OnAfterInitialize(IMessagingSetupContext context) - { - base.OnAfterInitialize(context); - SetIdentity(typeof(TConsumer)); } protected override async ValueTask ConsumeAsync(IConsumeContext context) diff --git a/src/Mocha/src/Mocha/Consumers/Implementations/RequestConsumer.cs b/src/Mocha/src/Mocha/Consumers/Implementations/RequestConsumer.cs index 68484751a55..f074311c0e0 100644 --- a/src/Mocha/src/Mocha/Consumers/Implementations/RequestConsumer.cs +++ b/src/Mocha/src/Mocha/Consumers/Implementations/RequestConsumer.cs @@ -11,17 +11,10 @@ namespace Mocha; /// responses. /// internal sealed class RequestConsumer : Consumer - where THandler : IEventRequestHandler + where THandler : class, IEventRequestHandler where TRequest : IEventRequest { - private readonly Action? _configure; - - public RequestConsumer(Action configure) - { - _configure = configure; - } - - public RequestConsumer() { } + public RequestConsumer() : base(typeof(THandler)) { } protected override void Configure(IConsumerDescriptor descriptor) { @@ -30,14 +23,6 @@ protected override void Configure(IConsumerDescriptor descriptor) .AddRoute(r => r.MessageType(typeof(TRequest)).ResponseType(typeof(TResponse)).Kind(InboundRouteKind.Request) ); - - _configure?.Invoke(descriptor); - } - - protected override void OnAfterInitialize(IMessagingSetupContext context) - { - base.OnAfterInitialize(context); - SetIdentity(typeof(THandler)); } protected override async ValueTask ConsumeAsync(IConsumeContext context) diff --git a/src/Mocha/src/Mocha/Consumers/Implementations/SendConsumer.cs b/src/Mocha/src/Mocha/Consumers/Implementations/SendConsumer.cs index 7194e8fa481..042b46c9e88 100644 --- a/src/Mocha/src/Mocha/Consumers/Implementations/SendConsumer.cs +++ b/src/Mocha/src/Mocha/Consumers/Implementations/SendConsumer.cs @@ -14,31 +14,16 @@ namespace Mocha; /// success via timeout behavior. /// internal sealed class SendConsumer : Consumer - where THandler : IEventRequestHandler + where THandler : class, IEventRequestHandler where TRequest : notnull { - private readonly Action? _configure; - - public SendConsumer(Action configure) - { - _configure = configure; - } - - public SendConsumer() { } - - protected override void OnAfterInitialize(IMessagingSetupContext context) - { - base.OnAfterInitialize(context); - SetIdentity(typeof(THandler)); - } + public SendConsumer() : base(typeof(THandler)) { } protected override void Configure(IConsumerDescriptor descriptor) { descriptor .Name(typeof(THandler).Name) .AddRoute(r => r.MessageType(typeof(TRequest)).Kind(InboundRouteKind.Send)); - - _configure?.Invoke(descriptor); } protected override async ValueTask ConsumeAsync(IConsumeContext context) diff --git a/src/Mocha/src/Mocha/Consumers/Implementations/SubscribeConsumer.cs b/src/Mocha/src/Mocha/Consumers/Implementations/SubscribeConsumer.cs index e577ccf395f..ada4711e961 100644 --- a/src/Mocha/src/Mocha/Consumers/Implementations/SubscribeConsumer.cs +++ b/src/Mocha/src/Mocha/Consumers/Implementations/SubscribeConsumer.cs @@ -10,30 +10,16 @@ namespace Mocha; /// Keeping subscribe behavior separate from request/send consumers avoids accidental response /// semantics on broadcast event flows. /// -internal sealed class SubscribeConsumer : Consumer where THandler : IEventHandler +internal sealed class SubscribeConsumer : Consumer + where THandler : class, IEventHandler { - private readonly Action? _configure; - - public SubscribeConsumer(Action configure) - { - _configure = configure; - } - - public SubscribeConsumer() { } + public SubscribeConsumer() : base(typeof(THandler)) { } protected override void Configure(IConsumerDescriptor descriptor) { descriptor .Name(typeof(THandler).Name) .AddRoute(r => r.MessageType(typeof(TEvent)).Kind(InboundRouteKind.Subscribe)); - - _configure?.Invoke(descriptor); - } - - protected override void OnAfterInitialize(IMessagingSetupContext context) - { - base.OnAfterInitialize(context); - SetIdentity(typeof(THandler)); } protected override async ValueTask ConsumeAsync(IConsumeContext context) diff --git a/src/Mocha/src/Mocha/Extensions/IMessageBusHostBuilderExtensions.cs b/src/Mocha/src/Mocha/Extensions/IMessageBusHostBuilderExtensions.cs index 3818f7d6d69..28992abe340 100644 --- a/src/Mocha/src/Mocha/Extensions/IMessageBusHostBuilderExtensions.cs +++ b/src/Mocha/src/Mocha/Extensions/IMessageBusHostBuilderExtensions.cs @@ -19,8 +19,8 @@ public static class MessageBusHostBuilderExtensions /// The event handler type. /// The host builder. /// The builder for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] public static IMessageBusHostBuilder AddEventHandler< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( this IMessageBusHostBuilder builder) @@ -40,8 +40,8 @@ public static IMessageBusHostBuilder AddEventHandler< /// The host builder. /// The action to configure the consumer descriptor. /// The builder for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] public static IMessageBusHostBuilder AddEventHandler< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( this IMessageBusHostBuilder builder, @@ -55,53 +55,18 @@ public static IMessageBusHostBuilder AddEventHandler< } /// - /// Registers a handler using pre-built configuration from the source generator. + /// Registers a consumer with the message bus using a factory. The factory is invoked on each + /// message bus build so every build receives a fresh consumer instance. /// - /// The handler type. /// The host builder. - /// The pre-built handler configuration. + /// The factory that creates the consumer to register. /// The builder for method chaining. - [EditorBrowsable(EditorBrowsableState.Never)] - public static IMessageBusHostBuilder AddHandlerConfiguration< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( - this IMessageBusHostBuilder builder, - MessagingHandlerConfiguration configuration) - where THandler : class, IHandler + public static IMessageBusHostBuilder AddConsumer(this IMessageBusHostBuilder builder, Func consumerFactory) { - builder.Services.TryAddScoped(); - builder.ConfigureMessageBus(h => h.AddHandlerConfiguration(configuration)); - return builder; - } + ArgumentNullException.ThrowIfNull(consumerFactory); - /// - /// Registers a message type using pre-built configuration from the source generator. - /// - /// The host builder. - /// The pre-built message configuration. - /// The builder for method chaining. - [EditorBrowsable(EditorBrowsableState.Never)] - public static IMessageBusHostBuilder AddMessageConfiguration( - this IMessageBusHostBuilder builder, - MessagingMessageConfiguration configuration) - { - builder.ConfigureMessageBus(h => h.AddMessageConfiguration(configuration)); - return builder; - } + builder.ConfigureMessageBus(h => h.AddConsumer(consumerFactory())); - /// - /// Registers a saga using pre-built configuration from the source generator. - /// - /// The saga type. - /// The host builder. - /// The pre-built saga configuration. - /// The builder for method chaining. - [EditorBrowsable(EditorBrowsableState.Never)] - public static IMessageBusHostBuilder AddSagaConfiguration( - this IMessageBusHostBuilder builder, - MessagingSagaConfiguration configuration) - where TSaga : Saga, new() - { - builder.ConfigureMessageBus(h => h.AddSagaConfiguration(configuration)); return builder; } @@ -127,8 +92,8 @@ public static IMessageBusHostBuilder AddJsonTypeInfoResolver( /// The host builder. /// Optional action to configure batch options. /// The builder for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] public static IMessageBusHostBuilder AddBatchHandler< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( this IMessageBusHostBuilder builder, @@ -147,8 +112,7 @@ public static IMessageBusHostBuilder AddBatchHandler< /// The saga type. /// The host builder. /// The builder for method chaining. - public static IMessageBusHostBuilder AddSaga< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TSaga>( + public static IMessageBusHostBuilder AddSaga( this IMessageBusHostBuilder builder) where TSaga : Saga, new() { @@ -162,8 +126,8 @@ public static IMessageBusHostBuilder AddSaga< /// The request handler type. /// The host builder. /// The builder for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] public static IMessageBusHostBuilder AddRequestHandler< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( this IMessageBusHostBuilder builder) @@ -183,8 +147,8 @@ public static IMessageBusHostBuilder AddRequestHandler< /// The host builder. /// The action to configure the consumer descriptor. /// The builder for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] public static IMessageBusHostBuilder AddRequestHandler< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>( this IMessageBusHostBuilder builder, @@ -203,8 +167,8 @@ public static IMessageBusHostBuilder AddRequestHandler< /// The consumer type implementing . /// The host builder. /// The builder for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] public static IMessageBusHostBuilder AddConsumer< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TConsumer>( this IMessageBusHostBuilder builder) @@ -224,8 +188,8 @@ public static IMessageBusHostBuilder AddConsumer< /// The host builder. /// The action to configure the consumer descriptor. /// The builder for method chaining. - [RequiresDynamicCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] - [RequiresUnreferencedCode("Use source-generated AddHandlerConfiguration for AOT compatibility.")] + [RequiresDynamicCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] + [RequiresUnreferencedCode("Use source-generated AddConsumer(ConsumerFactory...) for AOT compatibility.")] public static IMessageBusHostBuilder AddConsumer< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TConsumer>( this IMessageBusHostBuilder builder, @@ -268,6 +232,19 @@ public static IMessageBusHostBuilder ConfigureBusServices( return builder; } + /// + /// Registers a message type through the host builder. + /// + /// The message type to register. + /// The host builder. + /// The builder for method chaining. + public static IMessageBusHostBuilder AddMessage(this IMessageBusHostBuilder builder) + where TMessage : notnull + { + builder.ConfigureMessageBus(static h => h.AddMessage()); + return builder; + } + /// /// Registers a message type with custom configuration through the host builder. /// @@ -278,7 +255,7 @@ public static IMessageBusHostBuilder ConfigureBusServices( public static IMessageBusHostBuilder AddMessage( this IMessageBusHostBuilder builder, Action configure) - where TMessage : class + where TMessage : notnull { builder.ConfigureMessageBus(h => h.AddMessage(configure)); return builder; @@ -319,7 +296,20 @@ public static IMessageBusHostBuilder ModifyOptions( /// The action to configure the message bus builder. public static void ConfigureMessageBus(this IMessageBusHostBuilder builder, Action configure) { - builder.Configure(options => options.ConfigureMessageBus.Add(configure)); + builder.Configure(options => options.ConfigureMessageBus.Add(b => configure(b))); + } + + /// + /// Applies a configuration action to the descriptor context used during message bus setup. + /// + /// The host builder. + /// The action to configure the descriptor context. + public static void ConfigureDescriptorContext( + this IMessageBusHostBuilder builder, + Action configure) + { + builder.Configure(options => + options.ConfigureMessageBus.Add(b => b.ConfigureDescriptorContext(configure))); } private static void Configure(this IMessageBusHostBuilder builder, Action configure) diff --git a/src/Mocha/src/Mocha/MessageTypes/Configurations/MessageTypeConfiguration.cs b/src/Mocha/src/Mocha/MessageTypes/Configurations/MessageTypeConfiguration.cs index 6e056f6aea8..8008f11aed4 100644 --- a/src/Mocha/src/Mocha/MessageTypes/Configurations/MessageTypeConfiguration.cs +++ b/src/Mocha/src/Mocha/MessageTypes/Configurations/MessageTypeConfiguration.cs @@ -44,4 +44,9 @@ public class MessageTypeConfiguration : MessagingConfiguration /// indicates the source generator did not supply this information. /// public ImmutableArray EnclosedTypes { get; set; } + + /// + /// Source metadata captured from the message declaration. + /// + public SourceMetadata? Source { get; set; } } diff --git a/src/Mocha/src/Mocha/MessageTypes/MessageType.cs b/src/Mocha/src/Mocha/MessageTypes/MessageType.cs index bc56beaa969..a7d165e45ca 100644 --- a/src/Mocha/src/Mocha/MessageTypes/MessageType.cs +++ b/src/Mocha/src/Mocha/MessageTypes/MessageType.cs @@ -8,7 +8,7 @@ namespace Mocha; /// /// Represents a registered message type in the messaging system, holding identity, serialization, and type hierarchy metadata. /// -public sealed class MessageType +public class MessageType { /// /// Gets a value indicating whether the message type has been fully initialized with its type hierarchy and enclosed types. @@ -65,23 +65,44 @@ public sealed class MessageType public bool IsInternal { get; private set; } /// - /// Initializes this message type from configuration, applying conventions and registering outbound routes. + /// Source metadata captured from the message declaration. /// - /// The messaging configuration context. - /// The configuration to initialize from. - /// - /// Thrown when the configuration is missing a required identity, runtime type, or serializer registry. - /// - public void Initialize(IMessagingConfigurationContext context, MessageTypeConfiguration configuration) + public SourceMetadata? Source { get; private set; } + + public MessageType() + { + } + + internal MessageType(Type runtimeType) { + ArgumentNullException.ThrowIfNull(runtimeType); + + RuntimeType = runtimeType; + } + + internal void Initialize(IMessagingConfigurationContext context) + { + if (RuntimeType is null) + { + throw new InvalidOperationException( + "Message type requires a runtime type to create configuration from a descriptor."); + } + + var descriptor = new MessageTypeDescriptor(context, RuntimeType); + + context.ApplyConfigurations(RuntimeType, descriptor); + + var configuration = descriptor.CreateConfiguration(); + context.Conventions.Configure(context, configuration); Identity = configuration.Identity ?? throw new InvalidOperationException("Message requires and identity"); - RuntimeType = - configuration.RuntimeType ?? throw new InvalidOperationException("Message requires a runtime type"); + RuntimeType = configuration.RuntimeType ?? RuntimeType + ?? throw new InvalidOperationException("Message requires a runtime type"); IsInterface = RuntimeType.IsInterface; IsInternal = configuration.IsInternal; DefaultContentType = configuration.DefaultContentType; + Source = configuration.Source; Features = configuration.GetFeatures().ToReadOnly(); diff --git a/src/Mocha/src/Mocha/Sagas/Definitions/SagaConfiguration.cs b/src/Mocha/src/Mocha/Sagas/Definitions/SagaConfiguration.cs index 78cb1ce6579..eef7255706e 100644 --- a/src/Mocha/src/Mocha/Sagas/Definitions/SagaConfiguration.cs +++ b/src/Mocha/src/Mocha/Sagas/Definitions/SagaConfiguration.cs @@ -30,4 +30,9 @@ public sealed class SagaConfiguration : MessagingConfiguration /// Gets or sets a factory for creating a custom saga state serializer. /// public Func? StateSerializer { get; set; } + + /// + /// Source metadata captured from the saga declaration. + /// + public SourceMetadata? Source { get; set; } } diff --git a/src/Mocha/src/Mocha/Sagas/Saga.Initialization.cs b/src/Mocha/src/Mocha/Sagas/Saga.Initialization.cs index 8281a7a8d30..fcd7acb842a 100644 --- a/src/Mocha/src/Mocha/Sagas/Saga.Initialization.cs +++ b/src/Mocha/src/Mocha/Sagas/Saga.Initialization.cs @@ -20,17 +20,19 @@ public override void Initialize(IMessagingSetupContext context) { var descriptor = new SagaDescriptor(context); - Configuration = CreateConfiguration(context); descriptor.Name(context.Naming.GetSagaName(GetType())); - Configure(descriptor); + _configure(descriptor); + context.ApplyConfigurations>(GetType(), descriptor); var definition = descriptor.CreateConfiguration(); + Configuration = definition; _logger = context.Services.GetRequiredService>>(); Name = definition.Name ?? throw new SagaInitializationException(this, "Saga name is not defined."); - StateSerializer ??= + StateSerializer = definition.StateSerializer?.Invoke(context.Services) + ?? StateSerializer ?? context.Services.GetRequiredService().GetSerializer(typeof(TState)); var duringAnyTransitions = InitializeTransitions(definition.DuringAny!); @@ -176,11 +178,4 @@ private static ImmutableArray InitializeEventSend(List(discoveryContext); - _configure(descriptor); - return descriptor.CreateConfiguration(); - } } diff --git a/src/Mocha/src/Mocha/Sagas/Saga.cs b/src/Mocha/src/Mocha/Sagas/Saga.cs index c80c6b5dc29..89e228a29ac 100644 --- a/src/Mocha/src/Mocha/Sagas/Saga.cs +++ b/src/Mocha/src/Mocha/Sagas/Saga.cs @@ -59,6 +59,11 @@ protected Saga() /// public abstract IReadOnlyDictionary States { get; } + /// + /// Gets the compiled saga configuration containing the resolved state machine definition. + /// + protected internal SagaConfiguration Configuration { get; private protected set; } = null!; + /// /// Processes an incoming message by loading or creating saga state, executing transitions, and persisting the result. /// @@ -202,11 +207,6 @@ protected Saga() _configure = Configure; } - /// - /// Gets the compiled saga configuration containing the resolved state machine definition. - /// - protected internal SagaConfiguration Configuration { get; private set; } = null!; - private Dictionary? _states; private SagaState _initialState = null!; diff --git a/src/Mocha/src/Mocha/Sagas/SagaEventListener.cs b/src/Mocha/src/Mocha/Sagas/SagaEventListener.cs index 5dc9858ddfc..41ad45f581c 100644 --- a/src/Mocha/src/Mocha/Sagas/SagaEventListener.cs +++ b/src/Mocha/src/Mocha/Sagas/SagaEventListener.cs @@ -7,7 +7,7 @@ namespace Mocha.Sagas; /// A consumer that routes incoming messages to the appropriate saga state machine for processing. /// /// The saga definition that this consumer handles. -public sealed class SagaConsumer(Saga saga) : Consumer +public sealed class SagaConsumer(Saga saga) : Consumer(saga.GetType()) { /// /// Gets the saga definition that this consumer handles. @@ -81,13 +81,6 @@ public override ConsumerDescription Describe() false); } - /// - protected override void OnAfterInitialize(IMessagingSetupContext context) - { - base.OnAfterInitialize(context); - SetIdentity(saga.GetType()); - } - /// protected override async ValueTask ConsumeAsync(IConsumeContext context) { diff --git a/src/Mocha/src/Mocha/Serialization/MessageTypeRegistry.cs b/src/Mocha/src/Mocha/Serialization/MessageTypeRegistry.cs index d36694c8111..6467a38bb9d 100644 --- a/src/Mocha/src/Mocha/Serialization/MessageTypeRegistry.cs +++ b/src/Mocha/src/Mocha/Serialization/MessageTypeRegistry.cs @@ -63,7 +63,7 @@ public MessageType GetOrAdd(IMessagingConfigurationContext context, Type type) { throw new InvalidOperationException( $"Message type '{type.FullName}' was not registered at startup. " - + "Register it via the source generator or AddMessageConfiguration(). " + + "Register it via the source generator or AddMessage(). " + "Set IsAotCompatible = false to allow runtime type registration."); } @@ -75,9 +75,8 @@ public MessageType GetOrAdd(IMessagingConfigurationContext context, Type type) return messageType; } - messageType = new MessageType(); - var configuration = new MessageTypeConfiguration { RuntimeType = type }; - messageType.Initialize(context, configuration); + messageType = new MessageType(type); + messageType.Initialize(context); AddMessageType(messageType); messageType.Complete(context); diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/CommandHandlerGeneratorTests.cs b/src/Mocha/test/Mocha.Analyzers.Tests/CommandHandlerGeneratorTests.cs index 84aa2714ff9..3f26907e895 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/CommandHandlerGeneratorTests.cs +++ b/src/Mocha/test/Mocha.Analyzers.Tests/CommandHandlerGeneratorTests.cs @@ -77,4 +77,117 @@ public ValueTask HandleAsync(CreateOrderCommand command, CancellationToken """ ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); } + + [Fact] + public async Task Generate_Should_EmitTrackingMetadata_When_MediatorHandlerHasXmlDocumentation() + { + await TestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha.Mediator; + + namespace TestApp; + + /// + /// Deletes an order from the system. + /// + public record DeleteOrderCommand(int OrderId) : ICommand; + + /// + /// Handles delete order commands. + /// + public class DeleteOrderHandler : ICommandHandler + { + public ValueTask HandleAsync(DeleteOrderCommand command, CancellationToken cancellationToken) + => default; + } + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled() + { + await TestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha.Mediator; + + namespace TestApp; + + /// + /// Deletes an order from the system. + /// + public record DeleteOrderCommand(int OrderId) : ICommand; + + /// + /// Handles delete order commands. + /// + public class DeleteOrderHandler : ICommandHandler + { + public ValueTask HandleAsync(DeleteOrderCommand command, CancellationToken cancellationToken) + => default; + } + """ + ], + emitSourceMetadata: false).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided() + { + await TestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha.Mediator; + + namespace TestApp; + + /// + /// Deletes an order from the system. + /// + public record DeleteOrderCommand(int OrderId) : ICommand; + + /// + /// Handles delete order commands. + /// + public class DeleteOrderHandler : ICommandHandler + { + public ValueTask HandleAsync(DeleteOrderCommand command, CancellationToken cancellationToken) + => default; + } + """ + ], + projectDir: "/app/", + sourcePaths: ["/app/src/Handlers/DeleteOrderHandler.cs"]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_EmitRepositoryUrlAndCommit_When_ProvidedByBuild() + { + await TestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha.Mediator; + + namespace TestApp; + + /// + /// Deletes an order from the system. + /// + public record DeleteOrderCommand(int OrderId) : ICommand; + + /// + /// Handles delete order commands. + /// + public class DeleteOrderHandler : ICommandHandler + { + public ValueTask HandleAsync(DeleteOrderCommand command, CancellationToken cancellationToken) + => default; + } + """ + ], + repositoryUrl: "https://github.com/example/repo", + commit: "abc123def456").MatchMarkdownAsync(TestContext.Current.CancellationToken); + } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingDiagnosticTests.cs b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingDiagnosticTests.cs index 8b166adeff5..bf291ad90f4 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingDiagnosticTests.cs +++ b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingDiagnosticTests.cs @@ -309,6 +309,110 @@ public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancell AssertNoErrors(diagnostics); } + [Fact] + public void Generate_Should_Compile_When_MessageAndResponseAreRecordStructs() + { + // arrange + const string source = + """ + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Text.Json.Serialization.Metadata; + using System.Threading; + using System.Threading.Tasks; + using Mocha; + + [assembly: MessagingModule("TestApp", JsonContext = typeof(TestApp.TestJsonContext))] + + namespace TestApp; + + [JsonSerializable(typeof(InventorySnapshot))] + [JsonSerializable(typeof(GetStockRequest))] + [JsonSerializable(typeof(StockLevel))] + public sealed class TestJsonContext : IJsonTypeInfoResolver + { + public static TestJsonContext Default { get; } = new(); + + public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) + => null; + + public JsonTypeInfo? GetTypeInfo(Type type) + => null; + } + + public record struct InventorySnapshot(int Sku, int Quantity); + + public record struct StockLevel(int Quantity); + + public record GetStockRequest(int Sku) : IEventRequest; + + public class InventorySnapshotHandler : IEventHandler + { + public ValueTask HandleAsync(InventorySnapshot message, CancellationToken cancellationToken) + => default; + } + + public class GetStockHandler : IEventRequestHandler + { + public ValueTask HandleAsync(GetStockRequest request, CancellationToken cancellationToken) + => new(default(StockLevel)); + } + """; + + // act + var diagnostics = MessagingTestHelper.GetCompilationDiagnostics([source]); + + // assert + AssertNoErrors(diagnostics); + } + + [Fact] + public void Generate_Should_Compile_When_MessageIsStruct() + { + // arrange + const string source = + """ + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Text.Json.Serialization.Metadata; + using System.Threading; + using System.Threading.Tasks; + using Mocha; + + [assembly: MessagingModule("TestApp", JsonContext = typeof(TestApp.TestJsonContext))] + + namespace TestApp; + + [JsonSerializable(typeof(OrderPlacedEvent))] + public sealed class TestJsonContext : IJsonTypeInfoResolver + { + public static TestJsonContext Default { get; } = new(); + + public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) + => null; + + public JsonTypeInfo? GetTypeInfo(Type type) + => null; + } + + public readonly record struct OrderPlacedEvent(int OrderId); + + public class OrderPlacedHandler : IEventHandler + { + public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken) + => default; + } + """; + + // act + var diagnostics = MessagingTestHelper.GetCompilationDiagnostics([source]); + + // assert + AssertNoErrors(diagnostics); + } + [Fact] public void MO0018_Should_Report_When_MessageBusCallSiteTypesAreMissingFromJsonContext() { diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingEventHandlerGeneratorTests.cs b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingEventHandlerGeneratorTests.cs index 93b9ed44a5d..9d4371cd833 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingEventHandlerGeneratorTests.cs +++ b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingEventHandlerGeneratorTests.cs @@ -49,4 +49,151 @@ public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancell """ ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); } + + [Fact] + public async Task Generate_Should_EmitAddMessageAndSource_When_HandlerMessageDocumentedWithoutJsonContext() + { + await MessagingTestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha; + + namespace TestApp; + + /// + /// Published after an order is placed. + /// + public record OrderPlacedEvent(int OrderId); + + public class OrderPlacedHandler : IEventHandler + { + public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken) + => default; + } + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_EmitTrackingMetadata_When_MessageAndHandlerHaveXmlDocumentation() + { + await MessagingTestHelper.GetGeneratedSourceSnapshot( + [ + """ + using System.Text.Json.Serialization; + using Mocha; + + [assembly: MessagingModule("TestApp", JsonContext = typeof(TestApp.TestJsonContext))] + + namespace TestApp; + + /// + /// Published after an order is placed. + /// + public record OrderPlacedEvent(int OrderId); + + /// + /// Handles order placed events. + /// + public class OrderPlacedHandler : IEventHandler + { + public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken) + => default; + } + + [JsonSerializable(typeof(OrderPlacedEvent))] + public partial class TestJsonContext : JsonSerializerContext; + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_EmitTrackingMetadata_When_MessageOnlyComesFromJsonContext() + { + await MessagingTestHelper.GetGeneratedSourceSnapshot( + [ + """ + using System.Text.Json.Serialization; + using Mocha; + + [assembly: MessagingModule("TestApp", JsonContext = typeof(TestApp.TestJsonContext))] + + namespace TestApp; + + public record InventoryAdjustedEvent(int ProductId); + + [JsonSerializable(typeof(InventoryAdjustedEvent))] + public partial class TestJsonContext : JsonSerializerContext; + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled() + { + await MessagingTestHelper.GetGeneratedSourceSnapshot( + [ + """ + using System.Text.Json.Serialization; + using Mocha; + + [assembly: MessagingModule("TestApp", JsonContext = typeof(TestApp.TestJsonContext))] + + namespace TestApp; + + /// + /// Published after an order is placed. + /// + public record OrderPlacedEvent(int OrderId); + + /// + /// Handles order placed events. + /// + public class OrderPlacedHandler : IEventHandler + { + public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken) + => default; + } + + [JsonSerializable(typeof(OrderPlacedEvent))] + public partial class TestJsonContext : JsonSerializerContext; + """ + ], + emitSourceMetadata: false).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided() + { + await MessagingTestHelper.GetGeneratedSourceSnapshot( + [ + """ + using System.Text.Json.Serialization; + using Mocha; + + [assembly: MessagingModule("TestApp", JsonContext = typeof(TestApp.TestJsonContext))] + + namespace TestApp; + + /// + /// Published after an order is placed. + /// + public record OrderPlacedEvent(int OrderId); + + /// + /// Handles order placed events. + /// + public class OrderPlacedHandler : IEventHandler + { + public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken) + => default; + } + + [JsonSerializable(typeof(OrderPlacedEvent))] + public partial class TestJsonContext : JsonSerializerContext; + """ + ], + projectDir: "C:\\app\\", + sourcePaths: ["C:\\app\\Handlers\\OrderHandlers.cs"]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingIncrementalGeneratorTests.cs b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingIncrementalGeneratorTests.cs new file mode 100644 index 00000000000..2606850b1ad --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingIncrementalGeneratorTests.cs @@ -0,0 +1,59 @@ +namespace Mocha.Analyzers.Tests; + +public class MessagingIncrementalGeneratorTests +{ + [Fact] + public async Task Generate_Should_RefreshMessageMetadata_When_OnlyMessageFileChanges() + { + // arrange + // The handler and JsonContext live in a file that never changes; only the message file is edited. + const string handlerFile = + """ + using System.Text.Json.Serialization; + using Mocha; + + [assembly: MessagingModule("TestApp", JsonContext = typeof(TestApp.TestJsonContext))] + + namespace TestApp; + + public class OrderPlacedHandler : IEventHandler + { + public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken) => default; + } + + [JsonSerializable(typeof(OrderPlacedEvent))] + public partial class TestJsonContext : JsonSerializerContext; + """; + + const string messageFile = + """ + namespace TestApp; + + /// + /// Old doc. + /// + public record OrderPlacedEvent(int OrderId); + """; + + // The doc text changes and two extra blank lines shift both the start and end line of the record. + const string updatedMessageFile = + """ + namespace TestApp; + + + + /// + /// New doc. + /// + public record OrderPlacedEvent(int OrderId); + """; + + // act + assert + await MessagingTestHelper + .GetIncrementalGeneratedSourceSnapshot( + [handlerFile, messageFile], + replaceIndex: 1, + updatedMessageFile) + .MatchMarkdownAsync(TestContext.Current.CancellationToken); + } +} diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingPartialClassHandlerTests.cs b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingPartialClassHandlerTests.cs new file mode 100644 index 00000000000..8d76b747a66 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingPartialClassHandlerTests.cs @@ -0,0 +1,121 @@ +using Microsoft.CodeAnalysis; + +namespace Mocha.Analyzers.Tests; + +public class MessagingPartialClassHandlerTests +{ + private static readonly string[] s_partialHandlerRestatesInterfaceSources = + [ + """ + using System.Threading; + using System.Threading.Tasks; + using Mocha; + + namespace TestApp; + + public record OrderPlacedEvent(int OrderId); + + public partial class OrderPlacedHandler : IEventHandler + { + public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken) + => default; + } + """, + """ + using Mocha; + + namespace TestApp; + + public partial class OrderPlacedHandler : IEventHandler; + """ + ]; + + [Fact] + public async Task Generate_Should_EmitSingleInitializer_When_PartialHandlerRestatesInterfaceOnBothParts() + { + // The generator must collapse the two per-part event handler infos into one consumer + // registration and one consumer initializer method. + await MessagingTestHelper.GetGeneratedSourceSnapshot(s_partialHandlerRestatesInterfaceSources) + .MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public void Generate_Should_Compile_When_PartialHandlerRestatesInterfaceOnBothParts() + { + // act + var diagnostics = MessagingTestHelper.GetCompilationDiagnostics(s_partialHandlerRestatesInterfaceSources); + + // assert + AssertNoErrors(diagnostics); + } + + private static void AssertNoErrors(IReadOnlyCollection diagnostics) + { + var errors = diagnostics + .Where(static d => d.Severity == DiagnosticSeverity.Error) + .ToArray(); + + Assert.True(errors.Length == 0, string.Join(Environment.NewLine, errors.Select(static d => d.ToString()))); + } + + [Fact] + public async Task Generate_Should_EmitSingleConsumerInitializer_When_PartialEventHandlerRepeatsInterfaceOnBothParts() + { + // The generator must collapse the two per-part event handler infos into one consumer + // registration and one consumer initializer method. + await MessagingTestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha; + + namespace TestApp; + + public record OrderPlacedEvent(int OrderId); + + public partial class OrderPlacedHandler : IEventHandler + { + public ValueTask HandleAsync(OrderPlacedEvent message, CancellationToken cancellationToken) + => default; + } + """, + """ + using Mocha; + + namespace TestApp; + + public partial class OrderPlacedHandler : IEventHandler; + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_EmitSingleSagaInitializer_When_PartialSagaRepeatsBaseTypeOnBothParts() + { + // The generator must collapse the two per-part saga infos into one saga registration and + // one saga initializer method. + await MessagingTestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha.Sagas; + + namespace TestApp; + + public class OrderState : SagaStateBase; + + public partial class OrderFulfillmentSaga : Saga + { + protected override void Configure(ISagaDescriptor descriptor) + { + } + } + """, + """ + using Mocha.Sagas; + + namespace TestApp; + + public partial class OrderFulfillmentSaga : Saga; + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } +} diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs index c4802d47f97..02a83bce522 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs +++ b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs @@ -21,9 +21,80 @@ internal static class MessagingTestHelper public static Snapshot GetGeneratedSourceSnapshot( string[] sourceTexts, string? assemblyName = "Tests", + bool publishAot = false, + bool emitSourceMetadata = true, + string? projectDir = null, + string[]? sourcePaths = null) + { + var driver = RunGenerator(sourceTexts, assemblyName, publishAot, emitSourceMetadata, projectDir, sourcePaths); + + return CreateSnapshot(driver); + } + + public static Snapshot GetIncrementalGeneratedSourceSnapshot( + string[] sourceTexts, + int replaceIndex, + string replacementSourceText, + string? assemblyName = "Tests", bool publishAot = false) { - var driver = RunGenerator(sourceTexts, assemblyName, publishAot); + var compilation = CreateCompilation(sourceTexts, assemblyName); + var driver = CreateDriver(publishAot, emitSourceMetadata: true, projectDir: null, trackIncrementalSteps: true) + .RunGenerators(compilation); + + // Replacing a single tree preserves the other tree instances, so Roslyn can reuse the cached + // messaging syntax-info transforms for the unchanged handler file across the second run. + var oldTree = compilation.SyntaxTrees.ElementAt(replaceIndex); + var updatedCompilation = compilation.ReplaceSyntaxTree( + oldTree, + CSharpSyntaxTree.ParseText(replacementSourceText)); + + driver = driver.RunGenerators(updatedCompilation); + + var runResult = driver.GetRunResult(); + var snapshot = new Snapshot(); + + // The second-run step reasons are the discriminator: a message-only edit must leave the + // handler/messaging collection cached (the fix moves message metadata out of that pipeline + // into MochaMessageDeclarations), while still refreshing the generated output below. + snapshot.Add( + FormatStepReasons(runResult, "MochaMessagingSyntaxInfos", "MochaMessagingCollectedInfos"), + "Incremental Steps (second run)"); + + foreach (var result in runResult.Results) + { + foreach (var source in result.GeneratedSources.OrderBy(s => s.HintName)) + { + snapshot.Add(source.SourceText.ToString(), source.HintName, MarkdownLanguages.CSharp); + } + } + + return snapshot; + } + + private static string FormatStepReasons( + GeneratorDriverRunResult runResult, + params string[] stepNames) + { + var builder = new StringBuilder(); + + foreach (var stepName in stepNames) + { + var reasons = runResult.Results + .SelectMany(r => r.TrackedSteps.TryGetValue(stepName, out var steps) + ? steps + : ImmutableArray.Empty) + .SelectMany(s => s.Outputs) + .Select(o => o.Reason.ToString()); + + builder.Append(stepName).Append(": ").AppendLine(string.Join(", ", reasons)); + } + + return builder.ToString(); + } + + private static Snapshot CreateSnapshot(GeneratorDriver driver) + { var snapshot = new Snapshot(); foreach (var result in driver.GetRunResult().Results) @@ -46,9 +117,12 @@ public static Snapshot GetGeneratedSourceSnapshot( public static ImmutableArray GetGeneratorDiagnostics( string[] sourceTexts, string? assemblyName = "Tests", - bool publishAot = false) + bool publishAot = false, + bool emitSourceMetadata = true, + string? projectDir = null, + string[]? sourcePaths = null) { - var driver = RunGenerator(sourceTexts, assemblyName, publishAot); + var driver = RunGenerator(sourceTexts, assemblyName, publishAot, emitSourceMetadata, projectDir, sourcePaths); return driver.GetRunResult().Results .SelectMany(static r => r.Diagnostics) @@ -58,10 +132,13 @@ public static ImmutableArray GetGeneratorDiagnostics( public static ImmutableArray GetCompilationDiagnostics( string[] sourceTexts, string? assemblyName = "Tests", - bool publishAot = false) + bool publishAot = false, + bool emitSourceMetadata = true, + string? projectDir = null, + string[]? sourcePaths = null) { - var compilation = CreateCompilation(sourceTexts, assemblyName); - var driver = CreateDriver(publishAot); + var compilation = CreateCompilation(sourceTexts, assemblyName, sourcePaths); + var driver = CreateDriver(publishAot, emitSourceMetadata, projectDir); driver = driver.RunGeneratorsAndUpdateCompilation( compilation, @@ -77,9 +154,12 @@ public static ImmutableArray GetCompilationDiagnostics( public static ImmutableArray GetGeneratedSourceTexts( string[] sourceTexts, string? assemblyName = "Tests", - bool publishAot = false) + bool publishAot = false, + bool emitSourceMetadata = true, + string? projectDir = null, + string[]? sourcePaths = null) { - var driver = RunGenerator(sourceTexts, assemblyName, publishAot); + var driver = RunGenerator(sourceTexts, assemblyName, publishAot, emitSourceMetadata, projectDir, sourcePaths); return driver.GetRunResult().Results .SelectMany(static r => r.GeneratedSources) @@ -91,16 +171,20 @@ public static ImmutableArray GetGeneratedSourceTexts( private static GeneratorDriver RunGenerator( string[] sourceTexts, string? assemblyName, - bool publishAot) + bool publishAot, + bool emitSourceMetadata, + string? projectDir, + string[]? sourcePaths) { - var compilation = CreateCompilation(sourceTexts, assemblyName); + var compilation = CreateCompilation(sourceTexts, assemblyName, sourcePaths); - return CreateDriver(publishAot).RunGenerators(compilation); + return CreateDriver(publishAot, emitSourceMetadata, projectDir).RunGenerators(compilation); } private static CSharpCompilation CreateCompilation( string[] sourceTexts, - string? assemblyName) + string? assemblyName, + string[]? sourcePaths = null) { IEnumerable references = [ @@ -117,6 +201,9 @@ private static CSharpCompilation CreateCompilation( // Mocha (IConsumer, IBatchEventHandler, Saga, SagaStateBase) MetadataReference.CreateFromFile(typeof(IConsumer).Assembly.Location), + // Mocha.Utilities (FeatureCollectionExtensions) + MetadataReference.CreateFromFile(typeof(Mocha.Features.FeatureCollectionExtensions).Assembly.Location), + #if NET11_0 // System.Collections.Immutable MetadataReference.CreateFromFile(typeof(ImmutableArray<>).Assembly.Location), @@ -144,16 +231,39 @@ private static CSharpCompilation CreateCompilation( return CSharpCompilation.Create( assemblyName: assemblyName, - syntaxTrees: sourceTexts.Select(s => CSharpSyntaxTree.ParseText(s)), + syntaxTrees: sourceTexts.Select((s, i) => CSharpSyntaxTree.ParseText(s, path: sourcePaths?[i] ?? "")), references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } - private static GeneratorDriver CreateDriver(bool publishAot) + private static GeneratorDriver CreateDriver( + bool publishAot, + bool emitSourceMetadata, + string? projectDir, + bool trackIncrementalSteps = false) { + var globalOptions = new Dictionary + { + ["build_property.PublishAot"] = publishAot ? "true" : "false", + ["build_property.MochaEmitSourceMetadata"] = emitSourceMetadata ? "true" : "false" + }; + + if (projectDir is not null) + { + globalOptions["build_property.ProjectDir"] = projectDir; + } + var generator = new MessagingGenerator(); - return CSharpGeneratorDriver.Create(generator) - .WithUpdatedAnalyzerConfigOptions(new TestAnalyzerConfigOptionsProvider(publishAot)); + + // Step tracking retains the incremental state tables across RunGenerators calls, which is + // what lets driver reuse expose cross-file cache staleness in the incremental test. + var driver = trackIncrementalSteps + ? CSharpGeneratorDriver.Create( + [generator.AsSourceGenerator()], + driverOptions: new GeneratorDriverOptions(default, trackIncrementalGeneratorSteps: true)) + : CSharpGeneratorDriver.Create(generator); + + return driver.WithUpdatedAnalyzerConfigOptions(new TestAnalyzerConfigOptionsProvider(globalOptions)); } private static void AddDiagnosticsToSnapshot( @@ -239,42 +349,4 @@ private static void AddDiagnosticsToSnapshot( snapshot.Add(Encoding.UTF8.GetString(stream.ToArray()), title, MarkdownLanguages.Json); } } - - private sealed class TestAnalyzerConfigOptionsProvider(bool publishAot) - : AnalyzerConfigOptionsProvider - { - private readonly AnalyzerConfigOptions _globalOptions = - new TestAnalyzerConfigOptions( - new Dictionary - { - ["build_property.PublishAot"] = publishAot ? "true" : "false" - }); - - public override AnalyzerConfigOptions GlobalOptions => _globalOptions; - - public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) - => TestAnalyzerConfigOptions.Empty; - - public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) - => TestAnalyzerConfigOptions.Empty; - } - - private sealed class TestAnalyzerConfigOptions(IReadOnlyDictionary options) - : AnalyzerConfigOptions - { - public static readonly AnalyzerConfigOptions Empty = - new TestAnalyzerConfigOptions(new Dictionary()); - - public override bool TryGetValue(string key, out string value) - { - if (options.TryGetValue(key, out var result)) - { - value = result; - return true; - } - - value = ""; - return false; - } - } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/PartialClassHandlerTests.cs b/src/Mocha/test/Mocha.Analyzers.Tests/PartialClassHandlerTests.cs index 69bc7587439..34c3ca3345a 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/PartialClassHandlerTests.cs +++ b/src/Mocha/test/Mocha.Analyzers.Tests/PartialClassHandlerTests.cs @@ -70,6 +70,111 @@ private void Process(int orderId) ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); } + [Fact] + public async Task Generate_Should_EmitSingleHandlerInitializer_When_PartialHandlerRepeatsInterfaceOnBothParts() + { + // The generator must collapse the two per-part handler infos into one registration and + // one initializer method, and ValidateMessageHandlerPairing must dedupe by handler type + // name before counting, so no MO0002 is emitted for this single handler restated + // across both partial declaration parts. + await TestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha.Mediator; + + namespace TestApp; + + public record CreateOrderCommand(string Name) : ICommand; + + public partial class CreateOrderHandler : ICommandHandler + { + public ValueTask HandleAsync(CreateOrderCommand command, CancellationToken cancellationToken) + => new(42); + } + """, + """ + using Mocha.Mediator; + + namespace TestApp; + + public partial class CreateOrderHandler : ICommandHandler; + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_EmitSingleNotificationInitializer_When_PartialNotificationHandlerRepeatsInterfaceOnBothParts() + { + // The generator must collapse the two per-part notification handler infos into one + // registration and one initializer method. + await TestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha.Mediator; + + namespace TestApp; + + public record OrderCreated(int OrderId) : INotification; + + public partial class OrderCreatedEmailHandler : INotificationHandler + { + public ValueTask HandleAsync(OrderCreated notification, CancellationToken cancellationToken) + => default; + } + """, + """ + using Mocha.Mediator; + + namespace TestApp; + + public partial class OrderCreatedEmailHandler : INotificationHandler; + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + + [Fact] + public async Task Generate_Should_ReportDuplicateHandler_When_TwoDifferentHandlerTypesImplementSameCommand() + { + // The dedup fix in ValidateMessageHandlerPairing collapses a handler restated across + // partial declaration parts by handler type name. It must not over-dedupe: two DIFFERENT + // handler types for the same command, one of them partial, still have distinct fully + // qualified names, so MO0002 must still be reported for both. + await TestHelper.GetGeneratedSourceSnapshot( + [ + """ + using Mocha.Mediator; + + namespace TestApp; + + public record CreateOrderCommand(string Name) : ICommand; + + public partial class CreateOrderHandlerA : ICommandHandler + { + public ValueTask HandleAsync(CreateOrderCommand command, CancellationToken cancellationToken) + => new(1); + } + """, + """ + using Mocha.Mediator; + + namespace TestApp; + + public partial class CreateOrderHandlerA : ICommandHandler; + """, + """ + using Mocha.Mediator; + + namespace TestApp; + + public class CreateOrderHandlerB : ICommandHandler + { + public ValueTask HandleAsync(CreateOrderCommand command, CancellationToken cancellationToken) + => new(2); + } + """ + ]).MatchMarkdownAsync(TestContext.Current.CancellationToken); + } + [Fact] public async Task Generate_PartialQueryHandler_AcrossFiles_MatchesSnapshot() { diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/TestAnalyzerConfigOptionsProvider.cs b/src/Mocha/test/Mocha.Analyzers.Tests/TestAnalyzerConfigOptionsProvider.cs new file mode 100644 index 00000000000..0a1ea5a26fa --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/TestAnalyzerConfigOptionsProvider.cs @@ -0,0 +1,37 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Mocha.Analyzers.Tests; + +internal sealed class TestAnalyzerConfigOptionsProvider(IReadOnlyDictionary globalOptions) + : AnalyzerConfigOptionsProvider +{ + private readonly AnalyzerConfigOptions _globalOptions = new TestAnalyzerConfigOptions(globalOptions); + + public override AnalyzerConfigOptions GlobalOptions => _globalOptions; + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) + => TestAnalyzerConfigOptions.Empty; + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + => TestAnalyzerConfigOptions.Empty; +} + +internal sealed class TestAnalyzerConfigOptions(IReadOnlyDictionary options) + : AnalyzerConfigOptions +{ + public static readonly AnalyzerConfigOptions Empty = + new TestAnalyzerConfigOptions(new Dictionary()); + + public override bool TryGetValue(string key, out string value) + { + if (options.TryGetValue(key, out var result)) + { + value = result; + return true; + } + + value = ""; + return false; + } +} diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs b/src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs index 8e9b277c1ea..addd21f6c39 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs +++ b/src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs @@ -17,7 +17,12 @@ internal static class TestHelper public static Snapshot GetGeneratedSourceSnapshot( string[] sourceTexts, - string? assemblyName = "Tests") + string? assemblyName = "Tests", + bool emitSourceMetadata = true, + string? projectDir = null, + string? repositoryUrl = null, + string? commit = null, + string[]? sourcePaths = null) { IEnumerable references = [ @@ -47,12 +52,33 @@ public static Snapshot GetGeneratedSourceSnapshot( var compilation = CSharpCompilation.Create( assemblyName: assemblyName, - syntaxTrees: sourceTexts.Select(s => CSharpSyntaxTree.ParseText(s)), + syntaxTrees: sourceTexts.Select((s, i) => CSharpSyntaxTree.ParseText(s, path: sourcePaths?[i] ?? "")), references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + var globalOptions = new Dictionary + { + ["build_property.MochaEmitSourceMetadata"] = emitSourceMetadata ? "true" : "false" + }; + + if (projectDir is not null) + { + globalOptions["build_property.ProjectDir"] = projectDir; + } + + if (repositoryUrl is not null) + { + globalOptions["build_property.RepositoryUrl"] = repositoryUrl; + } + + if (commit is not null) + { + globalOptions["build_property.SourceRevisionId"] = commit; + } + var generator = new MediatorGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator) + .WithUpdatedAnalyzerConfigOptions(new TestAnalyzerConfigOptionsProvider(globalOptions)); driver = driver.RunGenerators(compilation); var snapshot = new Snapshot(); diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_CommandWithResponseHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_CommandWithResponseHandler_MatchesSnapshot.md index 7249f0a5ffd..6c6aae39503 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_CommandWithResponseHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_CommandWithResponseHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,19 +25,33 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.CreateOrderHandler), - MessageType = typeof(global::TestApp.CreateOrderCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandler), + typeof(global::TestApp.CreateOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ)); return builder; } + + private static void __Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_MultipleCommandHandlers_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_MultipleCommandHandlers_MatchesSnapshot.md index 48a2eb72939..f58bec0c00f 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_MultipleCommandHandlers_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_MultipleCommandHandlers_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -27,27 +27,55 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteOrderHandler), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.CreateOrderHandler), - MessageType = typeof(global::TestApp.CreateOrderCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandler), + typeof(global::TestApp.CreateOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ)); return builder; } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } + + private static void __Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided.md new file mode 100644 index 00000000000..2c1f3b735d6 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided.md @@ -0,0 +1,64 @@ +# Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMediatorBuilderExtensions + { + [global::Mocha.Mediator.MediatorModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.DeleteOrderCommand), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.DeleteOrderHandler), + } + )] + public static global::Mocha.Mediator.IMediatorHostBuilder AddTests( + this global::Mocha.Mediator.IMediatorHostBuilder builder) + { + + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); + + return builder; + } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + XmlDocumentation = """ + + + Handles delete order commands. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("src/Handlers/DeleteOrderHandler.cs", 13, 1, 17, 2) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitRepositoryUrlAndCommit_When_ProvidedByBuild.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitRepositoryUrlAndCommit_When_ProvidedByBuild.md new file mode 100644 index 00000000000..3fbc45312de --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitRepositoryUrlAndCommit_When_ProvidedByBuild.md @@ -0,0 +1,66 @@ +# Generate_Should_EmitRepositoryUrlAndCommit_When_ProvidedByBuild + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMediatorBuilderExtensions + { + [global::Mocha.Mediator.MediatorModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.DeleteOrderCommand), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.DeleteOrderHandler), + } + )] + public static global::Mocha.Mediator.IMediatorHostBuilder AddTests( + this global::Mocha.Mediator.IMediatorHostBuilder builder) + { + + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); + + return builder; + } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + RepositoryUrl = "https://github.com/example/repo", + Commit = "abc123def456", + XmlDocumentation = """ + + + Handles delete order commands. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("", 13, 1, 17, 2) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MediatorHandlerHasXmlDocumentation.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MediatorHandlerHasXmlDocumentation.md new file mode 100644 index 00000000000..e1b5f9cc578 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MediatorHandlerHasXmlDocumentation.md @@ -0,0 +1,64 @@ +# Generate_Should_EmitTrackingMetadata_When_MediatorHandlerHasXmlDocumentation + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMediatorBuilderExtensions + { + [global::Mocha.Mediator.MediatorModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.DeleteOrderCommand), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.DeleteOrderHandler), + } + )] + public static global::Mocha.Mediator.IMediatorHostBuilder AddTests( + this global::Mocha.Mediator.IMediatorHostBuilder builder) + { + + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); + + return builder; + } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + XmlDocumentation = """ + + + Handles delete order commands. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("", 13, 1, 17, 2) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled.md new file mode 100644 index 00000000000..5af797df327 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled.md @@ -0,0 +1,52 @@ +# Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMediatorBuilderExtensions + { + [global::Mocha.Mediator.MediatorModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.DeleteOrderCommand), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.DeleteOrderHandler), + } + )] + public static global::Mocha.Mediator.IMediatorHostBuilder AddTests( + this global::Mocha.Mediator.IMediatorHostBuilder builder) + { + + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); + + return builder; + } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_VoidCommandHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_VoidCommandHandler_MatchesSnapshot.md index 14b6a82ce6b..ba98989691a 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_VoidCommandHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/CommandHandlerGeneratorTests.Generate_VoidCommandHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteOrderHandler), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); return builder; } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.MO0002_CommandWithTwoHandlers_ReportsError.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.MO0002_CommandWithTwoHandlers_ReportsError.md index ea9e24ee371..9c62c0a5908 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.MO0002_CommandWithTwoHandlers_ReportsError.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.MO0002_CommandWithTwoHandlers_ReportsError.md @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -28,28 +28,56 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.CreateOrderHandlerA), - MessageType = typeof(global::TestApp.CreateOrderCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.CreateOrderHandlerB), - MessageType = typeof(global::TestApp.CreateOrderCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandlerA), + typeof(global::TestApp.CreateOrderHandlerA), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandlerA_Handler_uTMg2w9cGonlRZoD_13ykQ)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandlerB), + typeof(global::TestApp.CreateOrderHandlerB), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandlerB_Handler__2n_De_4zEJ1yBcBEgUJuMw)); return builder; } + + private static void __Initialize_CreateOrderHandlerA_Handler_uTMg2w9cGonlRZoD_13ykQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } + + private static void __Initialize_CreateOrderHandlerB_Handler__2n_De_4zEJ1yBcBEgUJuMw(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 13, 1, 17, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.MO0002_VoidCommandWithTwoHandlers_ReportsError.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.MO0002_VoidCommandWithTwoHandlers_ReportsError.md index bbdfcaec3ab..c39d81f088a 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.MO0002_VoidCommandWithTwoHandlers_ReportsError.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.MO0002_VoidCommandWithTwoHandlers_ReportsError.md @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -28,26 +28,54 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteOrderHandlerA), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteOrderHandlerB), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandlerA), + typeof(global::TestApp.DeleteOrderHandlerA), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandlerA_Handler__6A_21jkTlzm2NjTpE40Jpg)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandlerB), + typeof(global::TestApp.DeleteOrderHandlerB), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandlerB_Handler__9u0jgUHdbQwGLC4WnEMIWA)); return builder; } + + private static void __Initialize_DeleteOrderHandlerA_Handler__6A_21jkTlzm2NjTpE40Jpg(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } + + private static void __Initialize_DeleteOrderHandlerB_Handler__9u0jgUHdbQwGLC4WnEMIWA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 13, 1, 17, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_ClosedConcreteHandler_NoDiagnostic.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_ClosedConcreteHandler_NoDiagnostic.md index 61b31116543..12cb83609c2 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_ClosedConcreteHandler_NoDiagnostic.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_ClosedConcreteHandler_NoDiagnostic.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.ConcreteHandler), - MessageType = typeof(global::TestApp.MyCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.ConcreteHandler), + typeof(global::TestApp.ConcreteHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_ConcreteHandler_Handler_WqA80wsTDTAl7crZjmI3fA)); return builder; } + + private static void __Initialize_ConcreteHandler_Handler_WqA80wsTDTAl7crZjmI3fA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.MyCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_CommandWithHandler_NoDiagnostic.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_CommandWithHandler_NoDiagnostic.md index 3300993b0e0..d77f5eaccfb 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_CommandWithHandler_NoDiagnostic.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_CommandWithHandler_NoDiagnostic.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteOrderHandler), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); return builder; } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_SingleHandlerInterface_NoDiagnostic.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_SingleHandlerInterface_NoDiagnostic.md index b381e279f49..e3db91713d4 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_SingleHandlerInterface_NoDiagnostic.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/DiagnosticTests.NoWarning_SingleHandlerInterface_NoDiagnostic.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.SomethingHappenedHandler), - MessageType = typeof(global::TestApp.SomethingHappened), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.SomethingHappenedHandler), + typeof(global::TestApp.SomethingHappenedHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_SomethingHappenedHandler_Handler_b11vZBgKIidQLz4b0bujEg)); return builder; } + + private static void __Initialize_SomethingHappenedHandler_Handler_b11vZBgKIidQLz4b0bujEg(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.SomethingHappened); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ExplicitModuleNameTests.Generate_ModuleWithOnlyName_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ExplicitModuleNameTests.Generate_ModuleWithOnlyName_MatchesSnapshot.md index 0424cc62015..1491166c71c 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ExplicitModuleNameTests.Generate_ModuleWithOnlyName_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ExplicitModuleNameTests.Generate_ModuleWithOnlyName_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class Test2MediatorBuilderExtensions + public static partial class Test2MediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteOrderHandler), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); return builder; } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 9, 1, 13, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/GenericHandlerTests.Generate_MultipleHandlersSameNamespace_DeterministicOrder_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/GenericHandlerTests.Generate_MultipleHandlersSameNamespace_DeterministicOrder_MatchesSnapshot.md index 836371f840b..29102a793bc 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/GenericHandlerTests.Generate_MultipleHandlersSameNamespace_DeterministicOrder_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/GenericHandlerTests.Generate_MultipleHandlersSameNamespace_DeterministicOrder_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -29,34 +29,76 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.AlphaHandler), - MessageType = typeof(global::TestApp.AlphaCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.MidHandler), - MessageType = typeof(global::TestApp.MidCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.ZetaHandler), - MessageType = typeof(global::TestApp.ZetaCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.AlphaHandler), + typeof(global::TestApp.AlphaHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_AlphaHandler_Handler_sNGdIz224MxtnWrel6uWkA)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.MidHandler), + typeof(global::TestApp.MidHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_MidHandler_Handler_NqA1kB9hPAUn92tjVmxsgw)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.ZetaHandler), + typeof(global::TestApp.ZetaHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_ZetaHandler_Handler_lNiJhsCwdk_wWPMm6KWULw)); return builder; } + + private static void __Initialize_AlphaHandler_Handler_sNGdIz224MxtnWrel6uWkA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.AlphaCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 14, 1, 17, 2) + }; + } + + private static void __Initialize_MidHandler_Handler_NqA1kB9hPAUn92tjVmxsgw(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.MidCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 19, 1, 22, 2) + }; + } + + private static void __Initialize_ZetaHandler_Handler_lNiJhsCwdk_wWPMm6KWULw(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.ZetaCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 9, 1, 12, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/GenericHandlerTests.Generate_OpenGenericCommand_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/GenericHandlerTests.Generate_OpenGenericCommand_MatchesSnapshot.md index fc16e3b6cc6..c166ed0c334 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/GenericHandlerTests.Generate_OpenGenericCommand_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/GenericHandlerTests.Generate_OpenGenericCommand_MatchesSnapshot.md @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -27,19 +27,33 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.StringProcessor), - MessageType = typeof(global::TestApp.ProcessCommand), - ResponseType = typeof(string), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline, string>() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.StringProcessor), + typeof(global::TestApp.StringProcessor), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_StringProcessor_Handler___bGXonLyn85U0vz_5W5U0A)); return builder; } + + private static void __Initialize_StringProcessor_Handler___bGXonLyn85U0vz_5W5U0A(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.ProcessCommand); + configuration.ResponseType = typeof(string); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline, string>(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/InternalHandlerTests.Generate_InternalHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/InternalHandlerTests.Generate_InternalHandler_MatchesSnapshot.md index 7604802cfff..7981bea697d 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/InternalHandlerTests.Generate_InternalHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/InternalHandlerTests.Generate_InternalHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestMediatorBuilderExtensions + public static partial class TestMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteOrderHandler), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); return builder; } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 9, 1, 13, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_CommandOfTResolution_ICommandGeneric_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_CommandOfTResolution_ICommandGeneric_MatchesSnapshot.md index d4ba158f179..93a36902e3a 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_CommandOfTResolution_ICommandGeneric_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_CommandOfTResolution_ICommandGeneric_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,19 +25,33 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.ComputeHandler), - MessageType = typeof(global::TestApp.ComputeCommand), - ResponseType = typeof(long), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.ComputeHandler), + typeof(global::TestApp.ComputeHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_ComputeHandler_Handler_sFbWNYKSMldrL4t9IN2NpQ)); return builder; } + + private static void __Initialize_ComputeHandler_Handler_sFbWNYKSMldrL4t9IN2NpQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.ComputeCommand); + configuration.ResponseType = typeof(long); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_CommandVoidResolution_ICommandInterface_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_CommandVoidResolution_ICommandInterface_MatchesSnapshot.md index 826889fce74..caf74d5eee4 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_CommandVoidResolution_ICommandInterface_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_CommandVoidResolution_ICommandInterface_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.FireAndForgetHandler), - MessageType = typeof(global::TestApp.FireAndForgetCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.FireAndForgetHandler), + typeof(global::TestApp.FireAndForgetHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_FireAndForgetHandler_Handler__0YxqL3bxmLx9_U8o3kSeBQ)); return builder; } + + private static void __Initialize_FireAndForgetHandler_Handler__0YxqL3bxmLx9_U8o3kSeBQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.FireAndForgetCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 10, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_WithAllHandlerTypes_AllSymbolsResolved_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_WithAllHandlerTypes_AllSymbolsResolved_MatchesSnapshot.md index 29ee2fe5ea7..0314170050c 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_WithAllHandlerTypes_AllSymbolsResolved_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/KnownTypeSymbolsTests.Generate_WithAllHandlerTypes_AllSymbolsResolved_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -31,44 +31,100 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.VoidCommandHandler), - MessageType = typeof(global::TestApp.VoidCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.ResponseCommandHandler), - MessageType = typeof(global::TestApp.ResponseCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.MyQueryHandler), - MessageType = typeof(global::TestApp.MyQuery), - ResponseType = typeof(string), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.MyEventHandler), - MessageType = typeof(global::TestApp.MyEvent), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.VoidCommandHandler), + typeof(global::TestApp.VoidCommandHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_VoidCommandHandler_Handler_sA_Q_8WtMH_l1VSXpHuoeA)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.ResponseCommandHandler), + typeof(global::TestApp.ResponseCommandHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_ResponseCommandHandler_Handler_GHCvLyLTo5ACOqqAJqdP3g)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.MyQueryHandler), + typeof(global::TestApp.MyQueryHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_MyQueryHandler_Handler_tlXN2umVlBZGuyacqK1rqA)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.MyEventHandler), + typeof(global::TestApp.MyEventHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_MyEventHandler_Handler_Pt_RNA6jcbE13LqPyyPFJQ)); return builder; } + + private static void __Initialize_VoidCommandHandler_Handler_sA_Q_8WtMH_l1VSXpHuoeA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.VoidCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 10, 2) + }; + } + + private static void __Initialize_ResponseCommandHandler_Handler_GHCvLyLTo5ACOqqAJqdP3g(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.ResponseCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 14, 1, 17, 2) + }; + } + + private static void __Initialize_MyQueryHandler_Handler_tlXN2umVlBZGuyacqK1rqA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.MyQuery); + configuration.ResponseType = typeof(string); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 21, 1, 24, 2) + }; + } + + private static void __Initialize_MyEventHandler_Handler_Pt_RNA6jcbE13LqPyyPFJQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.MyEvent); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 28, 1, 31, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_DefaultAssemblyName_PrefixesWithLastSegment.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_DefaultAssemblyName_PrefixesWithLastSegment.md index 56c885287d7..730bc00e20a 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_DefaultAssemblyName_PrefixesWithLastSegment.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_DefaultAssemblyName_PrefixesWithLastSegment.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,19 +25,33 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetItemHandler), - MessageType = typeof(global::TestApp.GetItemQuery), - ResponseType = typeof(string), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.GetItemHandler), + typeof(global::TestApp.GetItemHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_GetItemHandler_Handler_dxBRJkHIWR80ax4m0LvrRQ)); return builder; } + + private static void __Initialize_GetItemHandler_Handler_dxBRJkHIWR80ax4m0LvrRQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.GetItemQuery); + configuration.ResponseType = typeof(string); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_DottedAssemblyName_UsesLastSegment.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_DottedAssemblyName_UsesLastSegment.md index 68e10a15ecf..5f1b1cd475c 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_DottedAssemblyName_UsesLastSegment.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_DottedAssemblyName_UsesLastSegment.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class OrderingMediatorBuilderExtensions + public static partial class OrderingMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.PingHandler), - MessageType = typeof(global::TestApp.PingCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.PingHandler), + typeof(global::TestApp.PingHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_PingHandler_Handler_gWK4t2j93n0_Kmv_8Z_noQ)); return builder; } + + private static void __Initialize_PingHandler_Handler_gWK4t2j93n0_Kmv_8Z_noQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.PingCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "MyCompany.Services.Ordering", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_ModuleFile_ContainsHandlerRegistrations.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_ModuleFile_ContainsHandlerRegistrations.md index 96b670f95dc..ee86e046632 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_ModuleFile_ContainsHandlerRegistrations.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MediatorModuleTests.Generate_ModuleFile_ContainsHandlerRegistrations.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,19 +25,33 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.CreateInvoiceHandler), - MessageType = typeof(global::TestApp.CreateInvoiceCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateInvoiceHandler), + typeof(global::TestApp.CreateInvoiceHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateInvoiceHandler_Handler_WpI3FcFYR1i208ZhfT6U4w)); return builder; } + + private static void __Initialize_CreateInvoiceHandler_Handler_WpI3FcFYR1i208ZhfT6U4w(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateInvoiceCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingBatchHandlerGeneratorTests.Generate_BatchEventHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingBatchHandlerGeneratorTests.Generate_BatchEventHandler_MatchesSnapshot.md index ba5e349d505..fd4826e338d 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingBatchHandlerGeneratorTests.Generate_BatchEventHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingBatchHandlerGeneratorTests.Generate_BatchEventHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -21,16 +21,50 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.BulkOrderEvent", + typeof(global::TestApp.BulkOrderEvent), + __Initialize_BulkOrderEvent_MessageType_f1tuLIV_lQQ541pn_aa48g)); + // --- Batch Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.BulkOrderHandler), - Factory = global::Mocha.ConsumerFactory.Batch() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Batch()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.BulkOrderHandler", + typeof(global::TestApp.BulkOrderHandler), + __Initialize_BulkOrderHandler_Consumer_G0YlUXfu0YMkVL73f_tEgA)); return builder; } + + private static void __Initialize_BulkOrderEvent_MessageType_f1tuLIV_lQQ541pn_aa48g(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 46) + }; + } + + private static void __Initialize_BulkOrderHandler_Consumer_G0YlUXfu0YMkVL73f_tEgA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingConsumerGeneratorTests.Generate_SingleConsumer_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingConsumerGeneratorTests.Generate_SingleConsumer_MatchesSnapshot.md index 4c6a405fe59..d7564f240a3 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingConsumerGeneratorTests.Generate_SingleConsumer_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingConsumerGeneratorTests.Generate_SingleConsumer_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -21,16 +21,50 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.AuditLogMessage", + typeof(global::TestApp.AuditLogMessage), + __Initialize_AuditLogMessage_MessageType_xuXXLUpy_YwkyEmFk8e0Tg)); + // --- Consumers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.AuditLogConsumer), - Factory = global::Mocha.ConsumerFactory.Consume() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Consume()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.AuditLogConsumer", + typeof(global::TestApp.AuditLogConsumer), + __Initialize_AuditLogConsumer_Consumer_pJ88E16EWVqXFDmkDlAtcg)); return builder; } + + private static void __Initialize_AuditLogMessage_MessageType_xuXXLUpy_YwkyEmFk8e0Tg(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 46) + }; + } + + private static void __Initialize_AuditLogConsumer_Consumer_pJ88E16EWVqXFDmkDlAtcg(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingDiagnosticTests.MO0011_DuplicateRequestHandler_ReportsError.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingDiagnosticTests.MO0011_DuplicateRequestHandler_ReportsError.md index 45850dd67d8..014b3dae1d2 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingDiagnosticTests.MO0011_DuplicateRequestHandler_ReportsError.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingDiagnosticTests.MO0011_DuplicateRequestHandler_ReportsError.md @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -24,22 +24,71 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.GetOrderRequest", + typeof(global::TestApp.GetOrderRequest), + __Initialize_GetOrderRequest_MessageType_ZNHJRlMjl9m2ifYWBgH2uQ)); + // --- Request Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetOrderHandlerA), - Factory = global::Mocha.ConsumerFactory.Request() - }); - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetOrderHandlerB), - Factory = global::Mocha.ConsumerFactory.Request() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Request()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.GetOrderHandlerA", + typeof(global::TestApp.GetOrderHandlerA), + __Initialize_GetOrderHandlerA_Consumer_DRJRT4ZnmdyT7oHGcXw0uw)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Request()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.GetOrderHandlerB", + typeof(global::TestApp.GetOrderHandlerB), + __Initialize_GetOrderHandlerB_Consumer_gkwImC2mvYUHAbBlKgEBqA)); return builder; } + + private static void __Initialize_GetOrderRequest_MessageType_ZNHJRlMjl9m2ifYWBgH2uQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 68) + }; + } + + private static void __Initialize_GetOrderHandlerA_Consumer_DRJRT4ZnmdyT7oHGcXw0uw(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } + + private static void __Initialize_GetOrderHandlerB_Consumer_gkwImC2mvYUHAbBlKgEBqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 13, 1, 17, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_MultipleEventHandlers_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_MultipleEventHandlers_MatchesSnapshot.md index 2d1c84c1ed0..647168105cd 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_MultipleEventHandlers_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_MultipleEventHandlers_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -22,22 +22,71 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + // --- Event Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderPlacedAuditHandler), - Factory = global::Mocha.ConsumerFactory.Subscribe() - }); - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderPlacedHandler), - Factory = global::Mocha.ConsumerFactory.Subscribe() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedAuditHandler", + typeof(global::TestApp.OrderPlacedAuditHandler), + __Initialize_OrderPlacedAuditHandler_Consumer_jC6zu80VulKEDqtk98ks8g)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); return builder; } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 45) + }; + } + + private static void __Initialize_OrderPlacedAuditHandler_Consumer_jC6zu80VulKEDqtk98ks8g(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 13, 1, 17, 2) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitAddMessageAndSource_When_HandlerMessageDocumentedWithoutJsonContext.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitAddMessageAndSource_When_HandlerMessageDocumentedWithoutJsonContext.md new file mode 100644 index 00000000000..50a1d5b19b9 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitAddMessageAndSource_When_HandlerMessageDocumentedWithoutJsonContext.md @@ -0,0 +1,78 @@ +# Generate_Should_EmitAddMessageAndSource_When_HandlerMessageDocumentedWithoutJsonContext + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedHandler), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTests( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + + // --- Event Handlers --- + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); + + return builder; + } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + XmlDocumentation = """ + + + Published after an order is placed. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("", 8, 1, 8, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 10, 1, 14, 2) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided.md new file mode 100644 index 00000000000..c4e68d0bb92 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided.md @@ -0,0 +1,100 @@ +# Generate_Should_EmitProjectRelativePath_When_ProjectDirProvided + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestAppMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedEvent), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedHandler), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTestApp( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- AOT Configuration --- + global::Mocha.MessageBusHostBuilderExtensions.AddJsonTypeInfoResolver( + builder, + global::TestApp.TestJsonContext.Default); + + // --- Message Type Serializers --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + + // --- Event Handlers --- + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); + + return builder; + } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.AddSerializer(new global::Mocha.JsonMessageSerializer( + global::TestApp.TestJsonContext.Default.GetTypeInfo(typeof(global::TestApp.OrderPlacedEvent))!)); + descriptor.Extend().Configuration.EnclosedTypes = + global::System.Collections.Immutable.ImmutableArray.Create( + typeof(global::TestApp.OrderPlacedEvent) + ); + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + XmlDocumentation = """ + + + Published after an order is placed. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("Handlers/OrderHandlers.cs", 11, 1, 11, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + XmlDocumentation = """ + + + Handles order placed events. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("Handlers/OrderHandlers.cs", 16, 1, 20, 2) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MessageAndHandlerHaveXmlDocumentation.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MessageAndHandlerHaveXmlDocumentation.md new file mode 100644 index 00000000000..e89449a3c2a --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MessageAndHandlerHaveXmlDocumentation.md @@ -0,0 +1,100 @@ +# Generate_Should_EmitTrackingMetadata_When_MessageAndHandlerHaveXmlDocumentation + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestAppMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedEvent), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedHandler), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTestApp( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- AOT Configuration --- + global::Mocha.MessageBusHostBuilderExtensions.AddJsonTypeInfoResolver( + builder, + global::TestApp.TestJsonContext.Default); + + // --- Message Type Serializers --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + + // --- Event Handlers --- + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); + + return builder; + } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.AddSerializer(new global::Mocha.JsonMessageSerializer( + global::TestApp.TestJsonContext.Default.GetTypeInfo(typeof(global::TestApp.OrderPlacedEvent))!)); + descriptor.Extend().Configuration.EnclosedTypes = + global::System.Collections.Immutable.ImmutableArray.Create( + typeof(global::TestApp.OrderPlacedEvent) + ); + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + XmlDocumentation = """ + + + Published after an order is placed. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("", 11, 1, 11, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + XmlDocumentation = """ + + + Handles order placed events. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("", 16, 1, 20, 2) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MessageOnlyComesFromJsonContext.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MessageOnlyComesFromJsonContext.md new file mode 100644 index 00000000000..9a1de97b2f5 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_EmitTrackingMetadata_When_MessageOnlyComesFromJsonContext.md @@ -0,0 +1,59 @@ +# Generate_Should_EmitTrackingMetadata_When_MessageOnlyComesFromJsonContext + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestAppMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.InventoryAdjustedEvent), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTestApp( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- AOT Configuration --- + global::Mocha.MessageBusHostBuilderExtensions.AddJsonTypeInfoResolver( + builder, + global::TestApp.TestJsonContext.Default); + + // --- Message Type Serializers --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.InventoryAdjustedEvent", + typeof(global::TestApp.InventoryAdjustedEvent), + __Initialize_InventoryAdjustedEvent_MessageType_InMXG4MP_oDDg_JbpxU8Eg)); + + return builder; + } + + private static void __Initialize_InventoryAdjustedEvent_MessageType_InMXG4MP_oDDg_JbpxU8Eg(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.AddSerializer(new global::Mocha.JsonMessageSerializer( + global::TestApp.TestJsonContext.Default.GetTypeInfo(typeof(global::TestApp.InventoryAdjustedEvent))!)); + descriptor.Extend().Configuration.EnclosedTypes = + global::System.Collections.Immutable.ImmutableArray.Create( + typeof(global::TestApp.InventoryAdjustedEvent) + ); + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 8, 1, 8, 53) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled.md new file mode 100644 index 00000000000..f40e36c74c9 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled.md @@ -0,0 +1,76 @@ +# Generate_Should_OmitSourceMetadata_When_EmitSourceMetadataDisabled + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestAppMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedEvent), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedHandler), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTestApp( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- AOT Configuration --- + global::Mocha.MessageBusHostBuilderExtensions.AddJsonTypeInfoResolver( + builder, + global::TestApp.TestJsonContext.Default); + + // --- Message Type Serializers --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + + // --- Event Handlers --- + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); + + return builder; + } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.AddSerializer(new global::Mocha.JsonMessageSerializer( + global::TestApp.TestJsonContext.Default.GetTypeInfo(typeof(global::TestApp.OrderPlacedEvent))!)); + descriptor.Extend().Configuration.EnclosedTypes = + global::System.Collections.Immutable.ImmutableArray.Create( + typeof(global::TestApp.OrderPlacedEvent) + ); + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_SingleEventHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_SingleEventHandler_MatchesSnapshot.md index 974bc318bad..c9dff7707b0 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_SingleEventHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingEventHandlerGeneratorTests.Generate_SingleEventHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -21,16 +21,50 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + // --- Event Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderPlacedHandler), - Factory = global::Mocha.ConsumerFactory.Subscribe() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); return builder; } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingIncrementalGeneratorTests.Generate_Should_RefreshMessageMetadata_When_OnlyMessageFileChanges.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingIncrementalGeneratorTests.Generate_Should_RefreshMessageMetadata_When_OnlyMessageFileChanges.md new file mode 100644 index 00000000000..4c59efc4e40 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingIncrementalGeneratorTests.Generate_Should_RefreshMessageMetadata_When_OnlyMessageFileChanges.md @@ -0,0 +1,103 @@ +# Generate_Should_RefreshMessageMetadata_When_OnlyMessageFileChanges + +## Incremental Steps (second run) + +```text +MochaMessagingSyntaxInfos: Cached, Modified +MochaMessagingCollectedInfos: Modified + +``` + +## TestAppMessageBusBuilderExtensions.kHkt5Slhw0EY-Xbr5K86dQ.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestAppMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedEvent), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedHandler), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTestApp( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- AOT Configuration --- + global::Mocha.MessageBusHostBuilderExtensions.AddJsonTypeInfoResolver( + builder, + global::TestApp.TestJsonContext.Default); + + // --- Message Type Serializers --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + + // --- Event Handlers --- + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); + + return builder; + } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.AddSerializer(new global::Mocha.JsonMessageSerializer( + global::TestApp.TestJsonContext.Default.GetTypeInfo(typeof(global::TestApp.OrderPlacedEvent))!)); + descriptor.Extend().Configuration.EnclosedTypes = + global::System.Collections.Immutable.ImmutableArray.Create( + typeof(global::TestApp.OrderPlacedEvent) + ); + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + XmlDocumentation = """ + + + New doc. + + + """, + DeclarationLocation = new global::Mocha.DeclarationLocation("", 8, 1, 8, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 8, 1, 11, 2) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingMixedHandlerTests.Generate_AllHandlerKinds_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingMixedHandlerTests.Generate_AllHandlerKinds_MatchesSnapshot.md index 2d3b9bc0d83..8fd3010f21f 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingMixedHandlerTests.Generate_AllHandlerKinds_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingMixedHandlerTests.Generate_AllHandlerKinds_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( SagaTypes = new global::System.Type[] @@ -28,49 +28,189 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.AuditLogMessage", + typeof(global::TestApp.AuditLogMessage), + __Initialize_AuditLogMessage_MessageType_xuXXLUpy_YwkyEmFk8e0Tg)); + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.BulkOrderEvent", + typeof(global::TestApp.BulkOrderEvent), + __Initialize_BulkOrderEvent_MessageType_f1tuLIV_lQQ541pn_aa48g)); + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.GetOrderStatusRequest", + typeof(global::TestApp.GetOrderStatusRequest), + __Initialize_GetOrderStatusRequest_MessageType_nwQbYwhghLC_BjOMRM6k2g)); + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + // --- Saga Configuration --- - global::Mocha.MessageBusHostBuilderExtensions.AddSagaConfiguration< - global::TestApp.OrderFulfillmentSaga>( + global::Mocha.MessageBusHostBuilderExtensions.AddSaga(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( builder, - new global::Mocha.MessagingSagaConfiguration - { - SagaType = typeof(global::TestApp.OrderFulfillmentSaga), - }); + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd>( + "Tests::TestApp.OrderFulfillmentSaga", + typeof(global::TestApp.OrderFulfillmentSaga), + __Initialize_OrderFulfillmentSaga_Saga_gqKOsgSD1keQ7yUDa_GkiA)); // --- Batch Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.BulkOrderHandler), - Factory = global::Mocha.ConsumerFactory.Batch() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Batch()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.BulkOrderHandler", + typeof(global::TestApp.BulkOrderHandler), + __Initialize_BulkOrderHandler_Consumer_G0YlUXfu0YMkVL73f_tEgA)); // --- Consumers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.AuditLogConsumer), - Factory = global::Mocha.ConsumerFactory.Consume() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Consume()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.AuditLogConsumer", + typeof(global::TestApp.AuditLogConsumer), + __Initialize_AuditLogConsumer_Consumer_pJ88E16EWVqXFDmkDlAtcg)); // --- Request Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetOrderStatusHandler), - Factory = global::Mocha.ConsumerFactory.Request() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Request()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.GetOrderStatusHandler", + typeof(global::TestApp.GetOrderStatusHandler), + __Initialize_GetOrderStatusHandler_Consumer_wAugHVU9pFU4GR_Q8AGIFw)); // --- Event Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderPlacedHandler), - Factory = global::Mocha.ConsumerFactory.Subscribe() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); return builder; } + + private static void __Initialize_AuditLogMessage_MessageType_xuXXLUpy_YwkyEmFk8e0Tg(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 23, 1, 23, 46) + }; + } + + private static void __Initialize_BulkOrderEvent_MessageType_f1tuLIV_lQQ541pn_aa48g(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 31, 1, 31, 46) + }; + } + + private static void __Initialize_GetOrderStatusRequest_MessageType_nwQbYwhghLC_BjOMRM6k2g(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 15, 1, 15, 74) + }; + } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 7, 45) + }; + } + + private static void __Initialize_OrderFulfillmentSaga_Saga_gqKOsgSD1keQ7yUDa_GkiA(global::Mocha.Sagas.ISagaDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 40, 1, 43, 2) + }; + } + + private static void __Initialize_BulkOrderHandler_Consumer_G0YlUXfu0YMkVL73f_tEgA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 32, 1, 36, 2) + }; + } + + private static void __Initialize_AuditLogConsumer_Consumer_pJ88E16EWVqXFDmkDlAtcg(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 24, 1, 28, 2) + }; + } + + private static void __Initialize_GetOrderStatusHandler_Consumer_wAugHVU9pFU4GR_Q8AGIFw(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 16, 1, 20, 2) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 8, 1, 12, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingModuleTests.Generate_DefaultModuleName_UsesAssemblyName_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingModuleTests.Generate_DefaultModuleName_UsesAssemblyName_MatchesSnapshot.md index 989bc735c9a..b841f8bad99 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingModuleTests.Generate_DefaultModuleName_UsesAssemblyName_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingModuleTests.Generate_DefaultModuleName_UsesAssemblyName_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class ApiMessageBusBuilderExtensions + public static partial class ApiMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -21,16 +21,50 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "My.OrderService.Api::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_dvhIfsYxAJnU0SPrLyZOEQ)); + // --- Event Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderPlacedHandler), - Factory = global::Mocha.ConsumerFactory.Subscribe() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "My.OrderService.Api::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_jup_xlxA4lU73SDscyj4Hw)); return builder; } + + private static void __Initialize_OrderPlacedEvent_MessageType_dvhIfsYxAJnU0SPrLyZOEQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "My.OrderService.Api", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_jup_xlxA4lU73SDscyj4Hw(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "My.OrderService.Api", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingModuleTests.Generate_ExplicitModuleName_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingModuleTests.Generate_ExplicitModuleName_MatchesSnapshot.md index 900f4b07f8f..6e7caae3ed5 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingModuleTests.Generate_ExplicitModuleName_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingModuleTests.Generate_ExplicitModuleName_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class OrderServiceMessageBusBuilderExtensions + public static partial class OrderServiceMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -21,16 +21,50 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + // --- Event Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderPlacedHandler), - Factory = global::Mocha.ConsumerFactory.Subscribe() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); return builder; } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 7, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 9, 1, 13, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingMultiInterfaceTests.Generate_HandlerWithBatchAndEvent_RegistersAsBatch_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingMultiInterfaceTests.Generate_HandlerWithBatchAndEvent_RegistersAsBatch_MatchesSnapshot.md index 7832a2d4859..8d593e921f8 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingMultiInterfaceTests.Generate_HandlerWithBatchAndEvent_RegistersAsBatch_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingMultiInterfaceTests.Generate_HandlerWithBatchAndEvent_RegistersAsBatch_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -21,16 +21,50 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderEvent", + typeof(global::TestApp.OrderEvent), + __Initialize_OrderEvent_MessageType_DtIReR3adn3FyUDrJr_tvw)); + // --- Batch Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderBatchAndEventHandler), - Factory = global::Mocha.ConsumerFactory.Batch() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Batch()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderBatchAndEventHandler", + typeof(global::TestApp.OrderBatchAndEventHandler), + __Initialize_OrderBatchAndEventHandler_Consumer_NyhESDLSj_C6xKn5vNLdzw)); return builder; } + + private static void __Initialize_OrderEvent_MessageType_DtIReR3adn3FyUDrJr_tvw(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 39) + }; + } + + private static void __Initialize_OrderBatchAndEventHandler_Consumer_NyhESDLSj_C6xKn5vNLdzw(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 9, 1, 16, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleConsumerInitializer_When_PartialEventHandlerRepeatsInterfaceOnBothParts.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleConsumerInitializer_When_PartialEventHandlerRepeatsInterfaceOnBothParts.md new file mode 100644 index 00000000000..8d801c8f831 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleConsumerInitializer_When_PartialEventHandlerRepeatsInterfaceOnBothParts.md @@ -0,0 +1,71 @@ +# Generate_Should_EmitSingleConsumerInitializer_When_PartialEventHandlerRepeatsInterfaceOnBothParts + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedHandler), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTests( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + + // --- Event Handlers --- + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); + + return builder; + } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 75) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleInitializer_When_PartialHandlerRestatesInterfaceOnBothParts.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleInitializer_When_PartialHandlerRestatesInterfaceOnBothParts.md new file mode 100644 index 00000000000..f2eb3221c03 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleInitializer_When_PartialHandlerRestatesInterfaceOnBothParts.md @@ -0,0 +1,71 @@ +# Generate_Should_EmitSingleInitializer_When_PartialHandlerRestatesInterfaceOnBothParts + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderPlacedHandler), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTests( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedEvent", + typeof(global::TestApp.OrderPlacedEvent), + __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ)); + + // --- Event Handlers --- + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Subscribe()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.OrderPlacedHandler", + typeof(global::TestApp.OrderPlacedHandler), + __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA)); + + return builder; + } + + private static void __Initialize_OrderPlacedEvent_MessageType_FCfNULgZa5VkyEzfdaDunQ(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 7, 45) + }; + } + + private static void __Initialize_OrderPlacedHandler_Consumer_K_qu6c9lx8dudkE9_QTUqA(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 75) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleSagaInitializer_When_PartialSagaRepeatsBaseTypeOnBothParts.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleSagaInitializer_When_PartialSagaRepeatsBaseTypeOnBothParts.md new file mode 100644 index 00000000000..9607c3bf891 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingPartialClassHandlerTests.Generate_Should_EmitSingleSagaInitializer_When_PartialSagaRepeatsBaseTypeOnBothParts.md @@ -0,0 +1,48 @@ +# Generate_Should_EmitSingleSagaInitializer_When_PartialSagaRepeatsBaseTypeOnBothParts + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMessageBusBuilderExtensions + { + [global::Mocha.MessagingModuleInfo( + SagaTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderFulfillmentSaga), + } + )] + public static global::Mocha.IMessageBusHostBuilder AddTests( + this global::Mocha.IMessageBusHostBuilder builder) + { + + // --- Saga Configuration --- + global::Mocha.MessageBusHostBuilderExtensions.AddSaga(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd>( + "Tests::TestApp.OrderFulfillmentSaga", + typeof(global::TestApp.OrderFulfillmentSaga), + __Initialize_OrderFulfillmentSaga_Saga_gqKOsgSD1keQ7yUDa_GkiA)); + + return builder; + } + + private static void __Initialize_OrderFulfillmentSaga_Saga_gqKOsgSD1keQ7yUDa_GkiA(global::Mocha.Sagas.ISagaDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 62) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingRequestHandlerGeneratorTests.Generate_RequestResponseHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingRequestHandlerGeneratorTests.Generate_RequestResponseHandler_MatchesSnapshot.md index b6668ab7663..060f8dc387d 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingRequestHandlerGeneratorTests.Generate_RequestResponseHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingRequestHandlerGeneratorTests.Generate_RequestResponseHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -21,16 +21,50 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.GetOrderStatusRequest", + typeof(global::TestApp.GetOrderStatusRequest), + __Initialize_GetOrderStatusRequest_MessageType_nwQbYwhghLC_BjOMRM6k2g)); + // --- Request Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetOrderStatusHandler), - Factory = global::Mocha.ConsumerFactory.Request() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Request()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.GetOrderStatusHandler", + typeof(global::TestApp.GetOrderStatusHandler), + __Initialize_GetOrderStatusHandler_Consumer_wAugHVU9pFU4GR_Q8AGIFw)); return builder; } + + private static void __Initialize_GetOrderStatusRequest_MessageType_nwQbYwhghLC_BjOMRM6k2g(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 74) + }; + } + + private static void __Initialize_GetOrderStatusHandler_Consumer_wAugHVU9pFU4GR_Q8AGIFw(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingRequestHandlerGeneratorTests.Generate_SendHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingRequestHandlerGeneratorTests.Generate_SendHandler_MatchesSnapshot.md index 76c7f18cf41..a85d0565148 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingRequestHandlerGeneratorTests.Generate_SendHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingRequestHandlerGeneratorTests.Generate_SendHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( HandlerTypes = new global::System.Type[] @@ -21,16 +21,50 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.IMessageBusHostBuilder builder) { + // --- Message Types --- + global::Mocha.MessageBusHostBuilderExtensions.AddMessage(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.ProcessOrderRequest", + typeof(global::TestApp.ProcessOrderRequest), + __Initialize_ProcessOrderRequest_MessageType__80IS74_WRtkxh16rF63L3Q)); + // --- Request Handlers --- - global::Mocha.MessageBusHostBuilderExtensions.AddHandlerConfiguration(builder, - new global::Mocha.MessagingHandlerConfiguration - { - HandlerType = typeof(global::TestApp.ProcessOrderHandler), - Factory = global::Mocha.ConsumerFactory.Send() - }); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddScoped( + builder.Services); + global::Mocha.MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => global::Mocha.ConsumerFactory.Send()); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( + builder, + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd( + "Tests::TestApp.ProcessOrderHandler", + typeof(global::TestApp.ProcessOrderHandler), + __Initialize_ProcessOrderHandler_Consumer__6lyGOwt3bVovBGGhaQRQ_g)); return builder; } + + private static void __Initialize_ProcessOrderRequest_MessageType__80IS74_WRtkxh16rF63L3Q(global::Mocha.IMessageTypeDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 48) + }; + } + + private static void __Initialize_ProcessOrderHandler_Consumer__6lyGOwt3bVovBGGhaQRQ_g(global::Mocha.IConsumerDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingSagaGeneratorTests.Generate_SimpleSaga_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingSagaGeneratorTests.Generate_SimpleSaga_MatchesSnapshot.md index 708f31290ed..d9ee93ad74e 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingSagaGeneratorTests.Generate_SimpleSaga_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MessagingSagaGeneratorTests.Generate_SimpleSaga_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMessageBusBuilderExtensions + public static partial class TestsMessageBusBuilderExtensions { [global::Mocha.MessagingModuleInfo( SagaTypes = new global::System.Type[] @@ -22,16 +22,26 @@ namespace Microsoft.Extensions.DependencyInjection { // --- Saga Configuration --- - global::Mocha.MessageBusHostBuilderExtensions.AddSagaConfiguration< - global::TestApp.OrderFulfillmentSaga>( + global::Mocha.MessageBusHostBuilderExtensions.AddSaga(builder); + global::Mocha.MessageBusHostBuilderExtensions.ConfigureDescriptorContext( builder, - new global::Mocha.MessagingSagaConfiguration - { - SagaType = typeof(global::TestApp.OrderFulfillmentSaga), - }); + static ctx => global::Mocha.Features.FeatureCollectionExtensions.GetRequired(ctx.Features) + .Configurations.TryAdd>( + "Tests::TestApp.OrderFulfillmentSaga", + typeof(global::TestApp.OrderFulfillmentSaga), + __Initialize_OrderFulfillmentSaga_Saga_gqKOsgSD1keQ7yUDa_GkiA)); return builder; } + + private static void __Initialize_OrderFulfillmentSaga_Saga_gqKOsgSD1keQ7yUDa_GkiA(global::Mocha.Sagas.ISagaDescriptor descriptor) + { + descriptor.Extend().Configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 12, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MixedHandlerGeneratorTests.Generate_AllHandlerTypes_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MixedHandlerGeneratorTests.Generate_AllHandlerTypes_MatchesSnapshot.md index cbf38d5a9dd..9610b1b781c 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MixedHandlerGeneratorTests.Generate_AllHandlerTypes_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MixedHandlerGeneratorTests.Generate_AllHandlerTypes_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -32,52 +32,122 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteOrderHandler), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.CreateOrderHandler), - MessageType = typeof(global::TestApp.CreateOrderCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetUserHandler), - MessageType = typeof(global::TestApp.GetUserQuery), - ResponseType = typeof(global::TestApp.UserDto), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderCreatedEmailHandler), - MessageType = typeof(global::TestApp.OrderCreated), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderCreatedStatsHandler), - MessageType = typeof(global::TestApp.OrderCreated), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteOrderHandler), + typeof(global::TestApp.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandler), + typeof(global::TestApp.CreateOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.GetUserHandler), + typeof(global::TestApp.GetUserHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_GetUserHandler_Handler_WY8BBSWjPahujA2CwnpZTA)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.OrderCreatedEmailHandler), + typeof(global::TestApp.OrderCreatedEmailHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_OrderCreatedEmailHandler_Handler_MJ_sF3C__AzDnVve_iwrTA)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.OrderCreatedStatsHandler), + typeof(global::TestApp.OrderCreatedStatsHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_OrderCreatedStatsHandler_Handler_ZGWJNX8usPNaQPmP24_0Vw)); return builder; } + + private static void __Initialize_DeleteOrderHandler_Handler_aWDuDwKebXZopBjbFLUIgA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 23, 1, 27, 2) + }; + } + + private static void __Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 17, 1, 21, 2) + }; + } + + private static void __Initialize_GetUserHandler_Handler_WY8BBSWjPahujA2CwnpZTA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.GetUserQuery); + configuration.ResponseType = typeof(global::TestApp.UserDto); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 29, 1, 33, 2) + }; + } + + private static void __Initialize_OrderCreatedEmailHandler_Handler_MJ_sF3C__AzDnVve_iwrTA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.OrderCreated); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 35, 1, 39, 2) + }; + } + + private static void __Initialize_OrderCreatedStatsHandler_Handler_ZGWJNX8usPNaQPmP24_0Vw(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.OrderCreated); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 41, 1, 45, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MixedHandlerGeneratorTests.Generate_HandlersInDifferentNamespaces_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MixedHandlerGeneratorTests.Generate_HandlersInDifferentNamespaces_MatchesSnapshot.md index c6cff9d1c14..167a1c85935 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MixedHandlerGeneratorTests.Generate_HandlersInDifferentNamespaces_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/MixedHandlerGeneratorTests.Generate_HandlersInDifferentNamespaces_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -27,28 +27,56 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.Orders.CreateOrderHandler), - MessageType = typeof(global::TestApp.Orders.CreateOrderCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.Users.GetUserHandler), - MessageType = typeof(global::TestApp.Users.GetUserQuery), - ResponseType = typeof(global::TestApp.Users.UserDto), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.Orders.CreateOrderHandler), + typeof(global::TestApp.Orders.CreateOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandler_Handler_ppFo_iJIpo1f3K0xKS8gXQ)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.Users.GetUserHandler), + typeof(global::TestApp.Users.GetUserHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_GetUserHandler_Handler_vW_sqOrzLgp1Dl4jyNfv_g)); return builder; } + + private static void __Initialize_CreateOrderHandler_Handler_ppFo_iJIpo1f3K0xKS8gXQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.Orders.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } + + private static void __Initialize_GetUserHandler_Handler_vW_sqOrzLgp1Dl4jyNfv_g(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.Users.GetUserQuery); + configuration.ResponseType = typeof(global::TestApp.Users.UserDto); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 8, 1, 12, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ModuleNameHelperTests.Generate_AssemblyNameWithHyphen_UsesLastSegmentSanitized_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ModuleNameHelperTests.Generate_AssemblyNameWithHyphen_UsesLastSegmentSanitized_MatchesSnapshot.md index 3b7b27831b7..b7927449f47 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ModuleNameHelperTests.Generate_AssemblyNameWithHyphen_UsesLastSegmentSanitized_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ModuleNameHelperTests.Generate_AssemblyNameWithHyphen_UsesLastSegmentSanitized_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class Order_ProcessingMediatorBuilderExtensions + public static partial class Order_ProcessingMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.PingHandler), - MessageType = typeof(global::TestApp.PingCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.PingHandler), + typeof(global::TestApp.PingHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_PingHandler_Handler_dy2aTkh1t05VBX7wCGjeUw)); return builder; } + + private static void __Initialize_PingHandler_Handler_dy2aTkh1t05VBX7wCGjeUw(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.PingCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "My-Company.Services.Order-Processing", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ModuleNameHelperTests.Generate_NullAssemblyName_UsesAssemblyDefault_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ModuleNameHelperTests.Generate_NullAssemblyName_UsesAssemblyDefault_MatchesSnapshot.md index 1103d90680e..8577534a7cc 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ModuleNameHelperTests.Generate_NullAssemblyName_UsesAssemblyDefault_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/ModuleNameHelperTests.Generate_NullAssemblyName_UsesAssemblyDefault_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class UnknownMediatorBuilderExtensions + public static partial class UnknownMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.PingHandler), - MessageType = typeof(global::TestApp.PingCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.PingHandler), + typeof(global::TestApp.PingHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_PingHandler_Handler_VW8RW4fIZL9gExGjRWZPKA)); return builder; } + + private static void __Initialize_PingHandler_Handler_VW8RW4fIZL9gExGjRWZPKA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.PingCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Unknown", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NestedHandlerTests.Generate_NestedClassHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NestedHandlerTests.Generate_NestedClassHandler_MatchesSnapshot.md index 326ce16300c..7f49dd7f7f0 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NestedHandlerTests.Generate_NestedClassHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NestedHandlerTests.Generate_NestedClassHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.Outer.DeleteOrderHandler), - MessageType = typeof(global::TestApp.DeleteOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.Outer.DeleteOrderHandler), + typeof(global::TestApp.Outer.DeleteOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteOrderHandler_Handler_l_svUTq_Q3LPN_yL_m1lew)); return builder; } + + private static void __Initialize_DeleteOrderHandler_Handler_l_svUTq_Q3LPN_yL_m1lew(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 9, 5, 13, 6) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NotificationHandlerGeneratorTests.Generate_MultipleHandlersForSameNotification_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NotificationHandlerGeneratorTests.Generate_MultipleHandlersForSameNotification_MatchesSnapshot.md index 9e81d700556..ce38067bc20 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NotificationHandlerGeneratorTests.Generate_MultipleHandlersForSameNotification_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NotificationHandlerGeneratorTests.Generate_MultipleHandlersForSameNotification_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -26,26 +26,54 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.SendEmailHandler), - MessageType = typeof(global::TestApp.OrderCreated), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.UpdateStatsHandler), - MessageType = typeof(global::TestApp.OrderCreated), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.SendEmailHandler), + typeof(global::TestApp.SendEmailHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_SendEmailHandler_Handler_NlKYo7B5BFgyNG29O1EOyg)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.UpdateStatsHandler), + typeof(global::TestApp.UpdateStatsHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_UpdateStatsHandler_Handler__3GMSFki9dxDUYZBOgUe5Cw)); return builder; } + + private static void __Initialize_SendEmailHandler_Handler_NlKYo7B5BFgyNG29O1EOyg(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.OrderCreated); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } + + private static void __Initialize_UpdateStatsHandler_Handler__3GMSFki9dxDUYZBOgUe5Cw(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.OrderCreated); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 13, 1, 17, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NotificationHandlerGeneratorTests.Generate_SingleNotificationHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NotificationHandlerGeneratorTests.Generate_SingleNotificationHandler_MatchesSnapshot.md index c7380066636..4b6fc633809 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NotificationHandlerGeneratorTests.Generate_SingleNotificationHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/NotificationHandlerGeneratorTests.Generate_SingleNotificationHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.OrderCreatedEmailHandler), - MessageType = typeof(global::TestApp.OrderCreated), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.OrderCreatedEmailHandler), + typeof(global::TestApp.OrderCreatedEmailHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_OrderCreatedEmailHandler_Handler_MJ_sF3C__AzDnVve_iwrTA)); return builder; } + + private static void __Initialize_OrderCreatedEmailHandler_Handler_MJ_sF3C__AzDnVve_iwrTA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.OrderCreated); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialClassHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialClassHandler_MatchesSnapshot.md index e0e9b6d649a..f761359a407 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialClassHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialClassHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,19 +25,33 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.CreateOrderHandler), - MessageType = typeof(global::TestApp.CreateOrderCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandler), + typeof(global::TestApp.CreateOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ)); return builder; } + + private static void __Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 9, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialQueryHandler_AcrossFiles_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialQueryHandler_AcrossFiles_MatchesSnapshot.md index fd9456b352f..eb996a7e6e7 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialQueryHandler_AcrossFiles_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialQueryHandler_AcrossFiles_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,19 +25,33 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetOrderQueryHandler), - MessageType = typeof(global::TestApp.GetOrderQuery), - ResponseType = typeof(string), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.GetOrderQueryHandler), + typeof(global::TestApp.GetOrderQueryHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_GetOrderQueryHandler_Handler_c6UMxqiCwCnGuq_fwK3YhA)); return builder; } + + private static void __Initialize_GetOrderQueryHandler_Handler_c6UMxqiCwCnGuq_fwK3YhA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.GetOrderQuery); + configuration.ResponseType = typeof(string); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 11, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialVoidCommandHandler_AcrossFiles_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialVoidCommandHandler_AcrossFiles_MatchesSnapshot.md index 8aa2d252d7a..a89910fd5d7 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialVoidCommandHandler_AcrossFiles_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_PartialVoidCommandHandler_AcrossFiles_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,18 +25,32 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.ProcessOrderHandler), - MessageType = typeof(global::TestApp.ProcessOrderCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.ProcessOrderHandler), + typeof(global::TestApp.ProcessOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_ProcessOrderHandler_Handler___AaWDGDi8yPAYFsHd3nILA)); return builder; } + + private static void __Initialize_ProcessOrderHandler_Handler___AaWDGDi8yPAYFsHd3nILA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.ProcessOrderCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 7, 1, 14, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_EmitSingleHandlerInitializer_When_PartialHandlerRepeatsInterfaceOnBothParts.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_EmitSingleHandlerInitializer_When_PartialHandlerRepeatsInterfaceOnBothParts.md new file mode 100644 index 00000000000..ab8eaf6a935 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_EmitSingleHandlerInitializer_When_PartialHandlerRepeatsInterfaceOnBothParts.md @@ -0,0 +1,58 @@ +# Generate_Should_EmitSingleHandlerInitializer_When_PartialHandlerRepeatsInterfaceOnBothParts + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMediatorBuilderExtensions + { + [global::Mocha.Mediator.MediatorModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.CreateOrderCommand), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.CreateOrderHandler), + } + )] + public static global::Mocha.Mediator.IMediatorHostBuilder AddTests( + this global::Mocha.Mediator.IMediatorHostBuilder builder) + { + + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandler), + typeof(global::TestApp.CreateOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ)); + + return builder; + } + + private static void __Initialize_CreateOrderHandler_Handler_ZT_Kb55ulioCoTTi5x0bAQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 84) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_EmitSingleNotificationInitializer_When_PartialNotificationHandlerRepeatsInterfaceOnBothParts.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_EmitSingleNotificationInitializer_When_PartialNotificationHandlerRepeatsInterfaceOnBothParts.md new file mode 100644 index 00000000000..f06481e5f85 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_EmitSingleNotificationInitializer_When_PartialNotificationHandlerRepeatsInterfaceOnBothParts.md @@ -0,0 +1,57 @@ +# Generate_Should_EmitSingleNotificationInitializer_When_PartialNotificationHandlerRepeatsInterfaceOnBothParts + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMediatorBuilderExtensions + { + [global::Mocha.Mediator.MediatorModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderCreated), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.OrderCreatedEmailHandler), + } + )] + public static global::Mocha.Mediator.IMediatorHostBuilder AddTests( + this global::Mocha.Mediator.IMediatorHostBuilder builder) + { + + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.OrderCreatedEmailHandler), + typeof(global::TestApp.OrderCreatedEmailHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_OrderCreatedEmailHandler_Handler_MJ_sF3C__AzDnVve_iwrTA)); + + return builder; + } + + private static void __Initialize_OrderCreatedEmailHandler_Handler_MJ_sF3C__AzDnVve_iwrTA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.OrderCreated); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 84) + }; + } + } +} + +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_ReportDuplicateHandler_When_TwoDifferentHandlerTypesImplementSameCommand.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_ReportDuplicateHandler_When_TwoDifferentHandlerTypesImplementSameCommand.md new file mode 100644 index 00000000000..cff131695b9 --- /dev/null +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/PartialClassHandlerTests.Generate_Should_ReportDuplicateHandler_When_TwoDifferentHandlerTypesImplementSameCommand.md @@ -0,0 +1,102 @@ +# Generate_Should_ReportDuplicateHandler_When_TwoDifferentHandlerTypesImplementSameCommand + +## TestsMediatorBuilderExtensions.kHkt5Slhw0EY-Xbr5K86dQ.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +namespace Microsoft.Extensions.DependencyInjection +{ + [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] + public static partial class TestsMediatorBuilderExtensions + { + [global::Mocha.Mediator.MediatorModuleInfo( + MessageTypes = new global::System.Type[] + { + typeof(global::TestApp.CreateOrderCommand), + }, + HandlerTypes = new global::System.Type[] + { + typeof(global::TestApp.CreateOrderHandlerA), + typeof(global::TestApp.CreateOrderHandlerB), + } + )] + public static global::Mocha.Mediator.IMediatorHostBuilder AddTests( + this global::Mocha.Mediator.IMediatorHostBuilder builder) + { + + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandlerA), + typeof(global::TestApp.CreateOrderHandlerA), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandlerA_Handler_uTMg2w9cGonlRZoD_13ykQ)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateOrderHandlerB), + typeof(global::TestApp.CreateOrderHandlerB), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateOrderHandlerB_Handler__2n_De_4zEJ1yBcBEgUJuMw)); + + return builder; + } + + private static void __Initialize_CreateOrderHandlerA_Handler_uTMg2w9cGonlRZoD_13ykQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 5, 85) + }; + } + + private static void __Initialize_CreateOrderHandlerB_Handler__2n_De_4zEJ1yBcBEgUJuMw(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateOrderCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 5, 1, 9, 2) + }; + } + } +} + +``` + +## Generator Diagnostics + +```json +[ + { + "Id": "MO0002", + "Title": "Duplicate handler for message type", + "Severity": "Error", + "WarningLevel": 0, + "Location": ": (4,14)-(4,32)", + "MessageFormat": "Message type '{0}' has multiple handlers: {1}", + "Message": "Message type 'global::TestApp.CreateOrderCommand' has multiple handlers: global::TestApp.CreateOrderHandlerA, global::TestApp.CreateOrderHandlerB", + "Category": "Mediator", + "CustomTags": [] + } +] +``` diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/QueryHandlerGeneratorTests.Generate_MultipleQueryHandlers_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/QueryHandlerGeneratorTests.Generate_MultipleQueryHandlers_MatchesSnapshot.md index 8bd87bae89d..fd9ef421ee0 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/QueryHandlerGeneratorTests.Generate_MultipleQueryHandlers_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/QueryHandlerGeneratorTests.Generate_MultipleQueryHandlers_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -27,28 +27,56 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetOrderHandler), - MessageType = typeof(global::TestApp.GetOrderQuery), - ResponseType = typeof(global::TestApp.OrderDto), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetUserHandler), - MessageType = typeof(global::TestApp.GetUserQuery), - ResponseType = typeof(global::TestApp.UserDto), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.GetOrderHandler), + typeof(global::TestApp.GetOrderHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_GetOrderHandler_Handler_bUIkLgTbACrRnhG9ll8AsQ)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.GetUserHandler), + typeof(global::TestApp.GetUserHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_GetUserHandler_Handler_WY8BBSWjPahujA2CwnpZTA)); return builder; } + + private static void __Initialize_GetOrderHandler_Handler_bUIkLgTbACrRnhG9ll8AsQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.GetOrderQuery); + configuration.ResponseType = typeof(global::TestApp.OrderDto); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 8, 1, 12, 2) + }; + } + + private static void __Initialize_GetUserHandler_Handler_WY8BBSWjPahujA2CwnpZTA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.GetUserQuery); + configuration.ResponseType = typeof(global::TestApp.UserDto); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 8, 1, 12, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/QueryHandlerGeneratorTests.Generate_QueryHandler_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/QueryHandlerGeneratorTests.Generate_QueryHandler_MatchesSnapshot.md index b2f784147f4..bec1405e444 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/QueryHandlerGeneratorTests.Generate_QueryHandler_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/QueryHandlerGeneratorTests.Generate_QueryHandler_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -25,19 +25,33 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetUserHandler), - MessageType = typeof(global::TestApp.GetUserQuery), - ResponseType = typeof(global::TestApp.UserDto), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.GetUserHandler), + typeof(global::TestApp.GetUserHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_GetUserHandler_Handler_WY8BBSWjPahujA2CwnpZTA)); return builder; } + + private static void __Initialize_GetUserHandler_Handler_WY8BBSWjPahujA2CwnpZTA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.GetUserQuery); + configuration.ResponseType = typeof(global::TestApp.UserDto); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 9, 1, 13, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/WarmUpGeneratorTests.Generate_WarmUpMethod_WithAllHandlerTypes_MatchesSnapshot.md b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/WarmUpGeneratorTests.Generate_WarmUpMethod_WithAllHandlerTypes_MatchesSnapshot.md index 47dfc462278..1b255f24094 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/WarmUpGeneratorTests.Generate_WarmUpMethod_WithAllHandlerTypes_MatchesSnapshot.md +++ b/src/Mocha/test/Mocha.Analyzers.Tests/__snapshots__/WarmUpGeneratorTests.Generate_WarmUpMethod_WithAllHandlerTypes_MatchesSnapshot.md @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection { [global::System.CodeDom.Compiler.GeneratedCode("Mocha.Analyzers", "1.0.0")] - public static class TestsMediatorBuilderExtensions + public static partial class TestsMediatorBuilderExtensions { [global::Mocha.Mediator.MediatorModuleInfo( MessageTypes = new global::System.Type[] @@ -31,44 +31,100 @@ namespace Microsoft.Extensions.DependencyInjection this global::Mocha.Mediator.IMediatorHostBuilder builder) { - // Register handler configurations - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.DeleteItemHandler), - MessageType = typeof(global::TestApp.DeleteItemCommand), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Command, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.CreateItemHandler), - MessageType = typeof(global::TestApp.CreateItemCommand), - ResponseType = typeof(int), - Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.GetItemHandler), - MessageType = typeof(global::TestApp.GetItemQuery), - ResponseType = typeof(global::TestApp.ItemDto), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Query, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline() - }); - global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration(builder, - new global::Mocha.Mediator.MediatorHandlerConfiguration - { - HandlerType = typeof(global::TestApp.ItemCreatedHandler), - MessageType = typeof(global::TestApp.ItemCreated), - Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification, - Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline() - }); + // Register handlers + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.DeleteItemHandler), + typeof(global::TestApp.DeleteItemHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_DeleteItemHandler_Handler__4hReAtEP507b09A9H1D0vA)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.CreateItemHandler), + typeof(global::TestApp.CreateItemHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_CreateItemHandler_Handler__7UfHWbjWHil28HYz7dVOdQ)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.GetItemHandler), + typeof(global::TestApp.GetItemHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_GetItemHandler_Handler_dxBRJkHIWR80ax4m0LvrRQ)); + global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd( + builder.Services, + new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor( + typeof(global::TestApp.ItemCreatedHandler), + typeof(global::TestApp.ItemCreatedHandler), + builder.Options.ServiceLifetime)); + global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator( + builder, + static b => b.AddHandler(__Initialize_ItemCreatedHandler_Handler_Jyu_jpikZvhO_1gyRRPxrQ)); return builder; } + + private static void __Initialize_DeleteItemHandler_Handler__4hReAtEP507b09A9H1D0vA(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.DeleteItemCommand); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Command; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 19, 1, 23, 2) + }; + } + + private static void __Initialize_CreateItemHandler_Handler__7UfHWbjWHil28HYz7dVOdQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.CreateItemCommand); + configuration.ResponseType = typeof(int); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.CommandResponse; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildCommandResponsePipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 25, 1, 29, 2) + }; + } + + private static void __Initialize_GetItemHandler_Handler_dxBRJkHIWR80ax4m0LvrRQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.GetItemQuery); + configuration.ResponseType = typeof(global::TestApp.ItemDto); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Query; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildQueryPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 31, 1, 35, 2) + }; + } + + private static void __Initialize_ItemCreatedHandler_Handler_Jyu_jpikZvhO_1gyRRPxrQ(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor) + { + var configuration = descriptor.Extend().Configuration; + configuration.MessageType = typeof(global::TestApp.ItemCreated); + configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.Notification; + configuration.Delegate = global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline(); + configuration.Source = new global::Mocha.SourceMetadata + { + Assembly = "Tests", + DeclarationLocation = new global::Mocha.DeclarationLocation("", 37, 1, 41, 2) + }; + } } } diff --git a/src/Mocha/test/Mocha.Sagas.Tests/SagaInitializationTests.cs b/src/Mocha/test/Mocha.Sagas.Tests/SagaInitializationTests.cs index 64655569778..84e876b1bfa 100644 --- a/src/Mocha/test/Mocha.Sagas.Tests/SagaInitializationTests.cs +++ b/src/Mocha/test/Mocha.Sagas.Tests/SagaInitializationTests.cs @@ -1,3 +1,5 @@ +using System.Buffers; + namespace Mocha.Sagas.Tests; public class SagaInitializationTests @@ -291,6 +293,98 @@ public void Initially_Must_DefineStateFactory() Assert.Equal("When 'StartEvent' is triggered, no state factory is defined.", exception.Message); } + [Fact] + public void Initialize_Should_Succeed_When_ConfigurationFeatureIsMissing() + { + // Arrange + var context = new TestMessagingSetupContext(); + context.Features.Set(null); + + var saga = + Saga.Create(descriptor => + { + descriptor + .Initially() + .OnEvent() + .TransitionTo("Started") + .StateFactory(_ => new TestState(Guid.NewGuid(), "Started")); + + descriptor.Finally("Started"); + }); + + // Act + saga.Initialize(context); + + // Assert + Assert.Equal(2, saga.States.Count); + } + + [Fact] + public void Initialize_Should_KeepPresetStateSerializer_When_DescriptorHasNoSerializer() + { + // Arrange + var preset = new StubSagaStateSerializer(); + + var saga = + Saga.Create(descriptor => + { + descriptor + .Initially() + .OnEvent() + .TransitionTo("Started") + .StateFactory(_ => new TestState(Guid.NewGuid(), "Started")); + + descriptor.Finally("Started"); + }); + + saga.StateSerializer = preset; + + // Act + saga.Initialize(TestMessagingSetupContext.Instance); + + // Assert + Assert.Same(preset, saga.StateSerializer); + } + + [Fact] + public void Initialize_Should_UseDescriptorSerializer_When_PresetSerializerExists() + { + // Arrange + var fromDescriptor = new StubSagaStateSerializer(); + + var saga = + Saga.Create(descriptor => + { + descriptor + .Initially() + .OnEvent() + .TransitionTo("Started") + .StateFactory(_ => new TestState(Guid.NewGuid(), "Started")); + + descriptor.Finally("Started"); + descriptor.Serializer(_ => fromDescriptor); + }); + + saga.StateSerializer = new StubSagaStateSerializer(); + + // Act + saga.Initialize(TestMessagingSetupContext.Instance); + + // Assert + Assert.Same(fromDescriptor, saga.StateSerializer); + } + + private sealed class StubSagaStateSerializer : ISagaStateSerializer + { + public T? Deserialize(ReadOnlyMemory body) => default; + + public object? Deserialize(ReadOnlyMemory body) => null; + + public void Serialize(T message, IBufferWriter writer) { } + + public void Serialize(object message, IBufferWriter writer) { } + } + private class TestState(Guid id, string state) : SagaStateBase(id, state); private sealed class StartEvent; diff --git a/src/Mocha/test/Mocha.Tests/Consumers/ConsumerRegistrationTests.cs b/src/Mocha/test/Mocha.Tests/Consumers/ConsumerRegistrationTests.cs index c7d0b74eda3..88b6cbfa344 100644 --- a/src/Mocha/test/Mocha.Tests/Consumers/ConsumerRegistrationTests.cs +++ b/src/Mocha/test/Mocha.Tests/Consumers/ConsumerRegistrationTests.cs @@ -399,6 +399,31 @@ public void AddConsumer_Should_RegisterCorrectMessageType_When_ConsumerAdded() Assert.Equal(typeof(OrderCreated), route.MessageType!.RuntimeType); } + [Fact] + public void AddConsumer_Should_CreateFreshConsumer_When_ProviderBuiltTwiceFromSameServiceCollection() + { + // arrange + // a fresh transport is registered per build because InMemoryMessagingTransport.Initialize is + // one-shot; this keeps the gate focused on the consumer factory rather than the transport. + var services = new ServiceCollection(); + var builder = services.AddMessageBus(); + MessageBusHostBuilderExtensions.AddConsumer( + builder, + static () => ConsumerFactory.Subscribe()); + builder.ConfigureMessageBus(static b => b.AddTransport(new InMemoryMessagingTransport(static _ => { }))); + + // act + var runtime1 = (MessagingRuntime)services.BuildServiceProvider().GetRequiredService(); + var runtime2 = (MessagingRuntime)services.BuildServiceProvider().GetRequiredService(); + + // assert + var consumer1 = runtime1.Consumers.FirstOrDefault(c => c.Identity == typeof(OrderCreatedHandler)); + var consumer2 = runtime2.Consumers.FirstOrDefault(c => c.Identity == typeof(OrderCreatedHandler)); + Assert.NotNull(consumer1); + Assert.NotNull(consumer2); + Assert.NotSame(consumer1, consumer2); + } + public sealed class OrderCreated { public string OrderId { get; init; } = ""; diff --git a/src/Mocha/test/Mocha.Tests/Inbox/ConsumeInboxMiddlewareTests.cs b/src/Mocha/test/Mocha.Tests/Inbox/ConsumeInboxMiddlewareTests.cs index d9665f853c0..3b20a4fe7c7 100644 --- a/src/Mocha/test/Mocha.Tests/Inbox/ConsumeInboxMiddlewareTests.cs +++ b/src/Mocha/test/Mocha.Tests/Inbox/ConsumeInboxMiddlewareTests.cs @@ -217,11 +217,6 @@ private static ReceiveContext CreateConsumeContext( /// private sealed class TestConsumer : Consumer { - public TestConsumer() - { - SetIdentity(typeof(TestConsumer)); - } - protected override ValueTask ConsumeAsync(IConsumeContext context) => ValueTask.CompletedTask; } diff --git a/src/Mocha/test/Mocha.Tests/MessageTypes/AotMessageTypeTests.cs b/src/Mocha/test/Mocha.Tests/MessageTypes/AotMessageTypeTests.cs index 7537e5aa9c4..fe6abf4b2c4 100644 --- a/src/Mocha/test/Mocha.Tests/MessageTypes/AotMessageTypeTests.cs +++ b/src/Mocha/test/Mocha.Tests/MessageTypes/AotMessageTypeTests.cs @@ -1,7 +1,9 @@ using System.Buffers; +using System.Collections.Immutable; using System.Text.Json.Serialization; using Microsoft.Extensions.DependencyInjection; using Mocha.Events; +using Mocha.Features; using Mocha.Transport.InMemory; namespace Mocha.Tests; @@ -12,22 +14,14 @@ public class AotMessageTypeTests public void Complete_Should_ResolveFrameworkIdentityViaRuntimeNaming_When_AotConfigurationIncludesFrameworkBaseType() { // arrange - var runtime = CreateAotRuntime(builder => builder.ConfigureMessageBus(bus => + var runtime = CreateAotRuntime(builder => { - bus.AddMessageConfiguration(new MessagingMessageConfiguration - { - MessageType = typeof(GetOrderStatus), - Serializer = new StubMessageSerializer(), - EnclosedTypes = [typeof(GetOrderStatus), typeof(IEventRequest)] - }); - - bus.AddMessageConfiguration(new MessagingMessageConfiguration - { - MessageType = typeof(OrderStatusResponse), - Serializer = new StubMessageSerializer(), - EnclosedTypes = [typeof(OrderStatusResponse)] - }); - })); + AddAotMessage( + builder, + typeof(GetOrderStatus), + typeof(IEventRequest)); + AddAotMessage(builder, typeof(OrderStatusResponse)); + }); // act var messageType = runtime.Messages.GetMessageType(typeof(GetOrderStatus))!; @@ -49,22 +43,11 @@ public void Complete_Should_UseCustomNamingConvention_When_FrameworkBaseTypeReso // resolves IBusNamingConventions from application services. builder.Services.AddSingleton(); - builder.ConfigureMessageBus(bus => - { - bus.AddMessageConfiguration(new MessagingMessageConfiguration - { - MessageType = typeof(GetOrderStatus), - Serializer = new StubMessageSerializer(), - EnclosedTypes = [typeof(GetOrderStatus), typeof(IEventRequest)] - }); - - bus.AddMessageConfiguration(new MessagingMessageConfiguration - { - MessageType = typeof(OrderStatusResponse), - Serializer = new StubMessageSerializer(), - EnclosedTypes = [typeof(OrderStatusResponse)] - }); - }); + AddAotMessage( + builder, + typeof(GetOrderStatus), + typeof(IEventRequest)); + AddAotMessage(builder, typeof(OrderStatusResponse)); }); // act @@ -90,7 +73,7 @@ public void Complete_Should_Throw_When_AotModeWithoutEnclosedTypes() static void Act() { CreateAotRuntime( - builder => builder.ConfigureMessageBus(bus => bus.AddMessage(static _ => { }))); + builder => builder.ConfigureMessageBus(static bus => bus.AddMessage())); } } @@ -134,6 +117,30 @@ public void GetSerializer_Should_UseConfiguredJsonTypeInfoResolver_When_AotMode( Assert.Equal("42", deserialized?.OrderId); } + [Fact] + public void GetSerializer_Should_UseUserSerializer_When_UserConfigurationRunsAfterSourceGenerator() + { + // arrange + var runtime = CreateAotRuntime(builder => + { + AddAotMessage( + builder, + typeof(GetOrderStatus), + typeof(IEventRequest)); + + builder.AddMessage( + static descriptor => descriptor.AddSerializer(new OverrideMessageSerializer())); + }); + + var messageType = runtime.Messages.GetMessageType(typeof(GetOrderStatus))!; + + // act + var serializer = messageType.GetSerializer(MessageContentType.Json); + + // assert + Assert.IsType(serializer); + } + public sealed class GetOrderStatus : IEventRequest { public string OrderId { get; init; } = ""; @@ -144,6 +151,22 @@ public sealed class OrderStatusResponse public string Status { get; init; } = ""; } + private static void AddAotMessage( + IMessageBusHostBuilder builder, + params Type[] enclosedTypes) + where TMessage : class + { + builder.ConfigureMessageBus(static bus => bus.AddMessage()); + builder.ConfigureDescriptorContext(ctx => + ctx.Features.GetRequired() + .Configurations.Add(typeof(TMessage), descriptor => + { + descriptor.AddSerializer(new StubMessageSerializer()); + descriptor.Extend().Configuration.EnclosedTypes = + ImmutableArray.Create(enclosedTypes); + })); + } + private sealed class StubMessageSerializer : IMessageSerializer { public MessageContentType ContentType => MessageContentType.Json; @@ -161,6 +184,23 @@ public void Serialize(object message, IBufferWriter writer) } } + private sealed class OverrideMessageSerializer : IMessageSerializer + { + public MessageContentType ContentType => MessageContentType.Json; + + public T? Deserialize(ReadOnlyMemory body) => default; + + public object? Deserialize(ReadOnlyMemory body) => null; + + public void Serialize(T message, IBufferWriter writer) + { + } + + public void Serialize(object message, IBufferWriter writer) + { + } + } + /// /// Test double that prefixes every identity with "custom:" so we can observe whether /// the runtime resolves framework base type identities through the configured convention diff --git a/src/Mocha/test/Mocha.Tests/Runtime/MessagingRuntimeTests.cs b/src/Mocha/test/Mocha.Tests/Runtime/MessagingRuntimeTests.cs index be74ff1dc15..6998fa93d7c 100644 --- a/src/Mocha/test/Mocha.Tests/Runtime/MessagingRuntimeTests.cs +++ b/src/Mocha/test/Mocha.Tests/Runtime/MessagingRuntimeTests.cs @@ -59,6 +59,42 @@ public async Task DefaultMessageBus_Should_BeRegistered_When_BuiltWithAddMessage Assert.IsType(bus); } + [Fact] + public void Runtime_Should_CreateConsumersPerBuild_When_ServiceCollectionIsBuiltTwice() + { + // arrange + var services = new ServiceCollection(); + var builder = services.AddMessageBus(); + builder.AddConsumer(static () => ConsumerFactory.Subscribe()); + builder.ConfigureMessageBus(b => b.AddTransport(new InMemoryMessagingTransport(_ => { }))); + + // act + var runtime1 = (MessagingRuntime)services.BuildServiceProvider().GetRequiredService(); + var runtime2 = (MessagingRuntime)services.BuildServiceProvider().GetRequiredService(); + + // assert + var consumer1 = Assert.Single(runtime1.Consumers, c => c.Identity == typeof(TestEventHandler)); + var consumer2 = Assert.Single(runtime2.Consumers, c => c.Identity == typeof(TestEventHandler)); + Assert.NotSame(consumer1, consumer2); + } + + [Fact] + public void AddMessage_Should_RegisterOnce_When_CalledTwiceForSameType() + { + // arrange & act + // A generator-emitted AddMessage plus a user-written one (or two modules sharing a type) register the + // same message twice. The second registration must be a no-op instead of throwing on the message-type + // dictionary, and the type must resolve to a single MessageType. + var runtime = CreateRuntime(b => + { + b.AddMessage(); + b.AddMessage(); + }); + + // assert + Assert.Single(runtime.Messages.MessageTypes, m => m.RuntimeType == typeof(TestEvent)); + } + private static MessagingRuntime CreateRuntime(Action configure) { var services = new ServiceCollection(); diff --git a/src/Mocha/test/Mocha.Tests/Sagas/SagaRegistrationTests.cs b/src/Mocha/test/Mocha.Tests/Sagas/SagaBuilderTests.cs similarity index 74% rename from src/Mocha/test/Mocha.Tests/Sagas/SagaRegistrationTests.cs rename to src/Mocha/test/Mocha.Tests/Sagas/SagaBuilderTests.cs index f3579c01f47..242be394473 100644 --- a/src/Mocha/test/Mocha.Tests/Sagas/SagaRegistrationTests.cs +++ b/src/Mocha/test/Mocha.Tests/Sagas/SagaBuilderTests.cs @@ -1,10 +1,11 @@ using Microsoft.Extensions.DependencyInjection; +using Mocha.Features; using Mocha.Sagas; using Mocha.Transport.InMemory; namespace Mocha.Tests; -public class SagaRegistrationTests +public class SagaBuilderTests { private static MessagingRuntime CreateRuntime(Action configure) { @@ -98,6 +99,35 @@ public void SagaAndHandler_Should_Coexist_When_BothRegistered() Assert.NotNull(runtime.Consumers.FirstOrDefault(c => c.Name == "order-saga")); } + [Fact] + public void SagaConfiguration_Should_PreserveSourceMetadata_When_RuntimeRegistrationFollowsGeneratedRegistration() + { + // arrange + const string xmlDocumentation = ""; + var declarationLocation = new DeclarationLocation("SourceMetadataSaga.cs", 1, 2, 3, 4); + var sourceMetadata = new SourceMetadata + { + XmlDocumentation = xmlDocumentation, + DeclarationLocation = declarationLocation + }; + SourceMetadataSaga.LastConfiguration = null; + + // act + _ = CreateRuntime(b => + { + b.ConfigureMessageBus(h => h.AddSaga()); + b.ConfigureDescriptorContext(ctx => + ctx.Features.GetRequired() + .Configurations.TryAdd>( + "test::SourceMetadataSaga", + typeof(SourceMetadataSaga), + descriptor => descriptor.Extend().Configuration.Source = sourceMetadata)); + }); + + // assert + Assert.Equal(sourceMetadata, SourceMetadataSaga.LastConfiguration?.Source); + } + public sealed class OrderPlaced { public string OrderId { get; init; } = ""; @@ -176,6 +206,28 @@ protected override void Configure(ISagaDescriptor descriptor) } } + public sealed class SourceMetadataSaga : Saga + { + public static SagaConfiguration? LastConfiguration { get; set; } + + public override void Initialize(IMessagingSetupContext context) + { + base.Initialize(context); + LastConfiguration = Configuration; + } + + protected override void Configure(ISagaDescriptor descriptor) + { + descriptor + .Initially() + .OnEvent() + .StateFactory(e => new OrderSagaState { OrderId = e.OrderId, Total = e.Total }) + .TransitionTo("Completed"); + + descriptor.Finally("Completed"); + } + } + public sealed class StandaloneHandler : IEventHandler { public ValueTask HandleAsync(StandaloneEvent message, CancellationToken cancellationToken) => default; From facedd8fb4dad6275d67762858b56d4f6b222970 Mon Sep 17 00:00:00 2001 From: Pascal Senn Date: Mon, 6 Jul 2026 22:31:30 +0200 Subject: [PATCH 2/2] Rework Configuration Pattern and add Source Metadata --- src/Mocha/src/Mocha/Consumers/Consumer.cs | 2 +- src/Mocha/src/Mocha/MessageTypes/MessageType.cs | 2 +- src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs | 5 ++++- src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Mocha/src/Mocha/Consumers/Consumer.cs b/src/Mocha/src/Mocha/Consumers/Consumer.cs index 11862f2f01a..4c112e9c81f 100644 --- a/src/Mocha/src/Mocha/Consumers/Consumer.cs +++ b/src/Mocha/src/Mocha/Consumers/Consumer.cs @@ -24,7 +24,7 @@ protected Consumer() protected Consumer(Type identity) { - Identity = identity; + Identity = identity ?? throw new ArgumentNullException(nameof(identity)); } /// diff --git a/src/Mocha/src/Mocha/MessageTypes/MessageType.cs b/src/Mocha/src/Mocha/MessageTypes/MessageType.cs index a7d165e45ca..ecec1c5eb12 100644 --- a/src/Mocha/src/Mocha/MessageTypes/MessageType.cs +++ b/src/Mocha/src/Mocha/MessageTypes/MessageType.cs @@ -96,7 +96,7 @@ internal void Initialize(IMessagingConfigurationContext context) context.Conventions.Configure(context, configuration); - Identity = configuration.Identity ?? throw new InvalidOperationException("Message requires and identity"); + Identity = configuration.Identity ?? throw new InvalidOperationException("Message requires an identity"); RuntimeType = configuration.RuntimeType ?? RuntimeType ?? throw new InvalidOperationException("Message requires a runtime type"); IsInterface = RuntimeType.IsInterface; diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs index 02a83bce522..0c4ac25b6f4 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs +++ b/src/Mocha/test/Mocha.Analyzers.Tests/MessagingTestHelper.cs @@ -231,7 +231,10 @@ private static CSharpCompilation CreateCompilation( return CSharpCompilation.Create( assemblyName: assemblyName, - syntaxTrees: sourceTexts.Select((s, i) => CSharpSyntaxTree.ParseText(s, path: sourcePaths?[i] ?? "")), + syntaxTrees: sourceTexts.Select( + (s, i) => CSharpSyntaxTree.ParseText( + s, + path: sourcePaths is not null && i < sourcePaths.Length ? sourcePaths[i] : "")), references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } diff --git a/src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs b/src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs index addd21f6c39..7fc057266de 100644 --- a/src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs +++ b/src/Mocha/test/Mocha.Analyzers.Tests/TestHelper.cs @@ -52,7 +52,10 @@ public static Snapshot GetGeneratedSourceSnapshot( var compilation = CSharpCompilation.Create( assemblyName: assemblyName, - syntaxTrees: sourceTexts.Select((s, i) => CSharpSyntaxTree.ParseText(s, path: sourcePaths?[i] ?? "")), + syntaxTrees: sourceTexts.Select( + (s, i) => CSharpSyntaxTree.ParseText( + s, + path: sourcePaths is not null && i < sourcePaths.Length ? sourcePaths[i] : "")), references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));