From 1b28022eb2fef5ea6cfd81f27dba0d8fc52c80b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:34:29 +0000 Subject: [PATCH 1/7] Initial plan From d6fdb9801fab62e8b5bd762768dafcd7c4cc43ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:16:24 +0000 Subject: [PATCH 2/7] Add HandlerTemplate support to ScanForTypesAttribute Agent-Logs-Url: https://github.com/Dreamescaper/ServiceScan.SourceGenerator/sessions/03f9fca7-2aac-495b-94d4-354852cc473b Co-authored-by: Dreamescaper <17177729+Dreamescaper@users.noreply.github.com> --- .../CustomHandlerTests.cs | 175 ++++++++++++++++++ ...jectionGenerator.FindServicesToRegister.cs | 31 +++- ...encyInjectionGenerator.ParseMethodModel.cs | 10 +- .../DependencyInjectionGenerator.cs | 6 +- .../DiagnosticDescriptors.cs | 7 + .../GenerateAttributeInfo.cs | 13 ++ .../Model/AttributeModel.cs | 12 +- 7 files changed, 246 insertions(+), 8 deletions(-) diff --git a/ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs b/ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs index 85c7d57..8772f6b 100644 --- a/ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs +++ b/ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs @@ -1674,4 +1674,179 @@ public class MyService : IService { } Assert.Equal(results.Diagnostics.Single().Descriptor, DiagnosticDescriptors.MissingCustomHandlerOnGenerateServiceHandler); } + + [Fact] + public void ScanForTypesAttribute_HandlerTemplate_ReturnsCollection() + { + var source = """ + using ServiceScan.SourceGenerator; + + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + [ScanForTypes(AssignableTo = typeof(IService), HandlerTemplate = "new T(argument)")] + public static partial IService[] GetServiceInstances(string argument); + } + """; + + var services = + """ + namespace GeneratorTests; + + public interface IService { } + public class MyService1 : IService { public MyService1(string x) { } } + public class MyService2 : IService { public MyService2(string x) { } } + """; + + var compilation = CreateCompilation(source, services); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var expected = """ + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + public static partial global::GeneratorTests.IService[] GetServiceInstances( string argument) + { + return [ + new global::GeneratorTests.MyService1(argument), + new global::GeneratorTests.MyService2(argument) + ]; + } + } + """; + Assert.Equal(expected, results.GeneratedTrees[2].ToString()); + } + + [Fact] + public void ScanForTypesAttribute_HandlerTemplate_VoidMethod() + { + var source = """ + using ServiceScan.SourceGenerator; + + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + [ScanForTypes(AssignableTo = typeof(IService), HandlerTemplate = "registry.Add(new T(argument))")] + public static partial void RegisterServices(ServiceRegistry registry, string argument); + } + """; + + var services = + """ + namespace GeneratorTests; + + public interface IService { } + public class MyService1 : IService { public MyService1(string x) { } } + public class MyService2 : IService { public MyService2(string x) { } } + public class ServiceRegistry { public void Add(IService s) { } } + """; + + var compilation = CreateCompilation(source, services); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var expected = """ + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + public static partial void RegisterServices( global::GeneratorTests.ServiceRegistry registry, string argument) + { + registry.Add(new global::GeneratorTests.MyService1(argument)); + registry.Add(new global::GeneratorTests.MyService2(argument)); + } + } + """; + Assert.Equal(expected, results.GeneratedTrees[2].ToString()); + } + + [Fact] + public void ScanForTypesAttribute_HandlerTemplate_StatementWithSemicolon() + { + var source = """ + using ServiceScan.SourceGenerator; + + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + [ScanForTypes(AssignableTo = typeof(IService), HandlerTemplate = "registry.Add(new T(argument));")] + public static partial void RegisterServices(ServiceRegistry registry, string argument); + } + """; + + var services = + """ + namespace GeneratorTests; + + public interface IService { } + public class MyService1 : IService { public MyService1(string x) { } } + public class ServiceRegistry { public void Add(IService s) { } } + """; + + var compilation = CreateCompilation(source, services); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var expected = """ + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + public static partial void RegisterServices( global::GeneratorTests.ServiceRegistry registry, string argument) + { + registry.Add(new global::GeneratorTests.MyService1(argument)); + } + } + """; + Assert.Equal(expected, results.GeneratedTrees[2].ToString()); + } + + [Fact] + public void ScanForTypesAttribute_BothHandlerAndHandlerTemplate_ReportsDiagnostic() + { + var source = """ + using ServiceScan.SourceGenerator; + + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + [ScanForTypes(AssignableTo = typeof(IService), Handler = nameof(GetServiceInfo), HandlerTemplate = "new T()")] + public static partial IService[] GetServiceInstances(); + + private static IService GetServiceInfo() where T : IService, new() => new T(); + } + """; + + var services = + """ + namespace GeneratorTests; + + public interface IService { } + public class MyService : IService { } + """; + + var compilation = CreateCompilation(source, services); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + Assert.Equal(results.Diagnostics.Single().Descriptor, DiagnosticDescriptors.CantUseBothHandlerAndHandlerTemplate); + } } diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs index fbd50f6..4b4ff87 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using ServiceScan.SourceGenerator.Model; using static ServiceScan.SourceGenerator.DiagnosticDescriptors; @@ -40,6 +41,8 @@ private static DiagnosticModel FindServicesToRegister AddCollectionItems(implementationType, matchedTypes, attribute, method, collectionItems); else if (attribute.CustomHandler != null) AddCustomHandlerItems(implementationType, matchedTypes, attribute, customHandlers); + else if (attribute.HandlerTemplate != null) + AddTemplateStatementItem(implementationType, attribute, customHandlers); else { var implementationTypeName = implementationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); @@ -106,10 +109,14 @@ private static void AddCollectionItems( { var implementationTypeName = implementationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - if (attribute.CustomHandler == null) + if (attribute.CustomHandler == null && attribute.HandlerTemplate == null) { collectionItems.Add($"typeof({implementationTypeName})"); } + else if (attribute.HandlerTemplate != null) + { + collectionItems.Add(ExpandTemplate(attribute.HandlerTemplate, implementationTypeName)); + } else { var arguments = string.Join(", ", method.Parameters.Select(p => p.Name)); @@ -172,6 +179,28 @@ .. matchedType.TypeArguments.Select(a => a.ToDisplayString(SymbolDisplayFormat.F } } + private static void AddTemplateStatementItem( + INamedTypeSymbol implementationType, + AttributeModel attribute, + List customHandlers) + { + var implementationTypeName = implementationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var statement = ExpandTemplate(attribute.HandlerTemplate!, implementationTypeName); + if (!statement.TrimEnd().EndsWith(";")) + statement += ";"; + + customHandlers.Add(new CustomHandlerModel( + Model.CustomHandlerType.Template, + statement, + implementationTypeName, + [])); + } + + private static string ExpandTemplate(string template, string typeName) + { + return Regex.Replace(template, @"\bT\b", typeName); + } + private static IEnumerable GetSuitableInterfaces(ITypeSymbol type) { return type.AllInterfaces.Where(x => !ExcludedInterfaces.Contains(x.ToDisplayString())); diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs index 99738a3..6d13549 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs @@ -129,13 +129,17 @@ public partial class DependencyInjectionGenerator foreach (var attribute in attributeData) { - if (attribute.CustomHandler == null) + if (attribute.CustomHandler != null && attribute.HandlerTemplate != null) { - // Without a Handler, the method must return Type[] or IEnumerable + return Diagnostic.Create(CantUseBothHandlerAndHandlerTemplate, attribute.Location); + } + else if (attribute.CustomHandler == null && attribute.HandlerTemplate == null) + { + // Without a Handler or HandlerTemplate, the method must return Type[] or IEnumerable if (!isTypeCollection) return Diagnostic.Create(MissingCustomHandlerOnGenerateServiceHandler, attribute.Location); } - else + else if (attribute.CustomHandler != null) { var customHandlerMethod = method.ContainingType.GetMethod(attribute.CustomHandler, context.SemanticModel, position); diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs index 36bce65..f5be124 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs @@ -158,7 +158,11 @@ private static string GenerateCustomHandlingSource(MethodModel method, Equatable { var invocations = string.Join("\n", customHandlers.Select(h => { - if (h.CustomHandlerType == CustomHandlerType.Method) + if (h.CustomHandlerType == CustomHandlerType.Template) + { + return $" {h.HandlerMethodName}"; + } + else if (h.CustomHandlerType == CustomHandlerType.Method) { var genericArguments = string.Join(", ", h.TypeArguments); var arguments = string.Join(", ", method.Parameters.Select(p => p.Name)); diff --git a/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs b/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs index 69b00c8..dfe0a5f 100644 --- a/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs +++ b/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs @@ -94,4 +94,11 @@ public static class DiagnosticDescriptors "Usage", DiagnosticSeverity.Error, true); + + public static readonly DiagnosticDescriptor CantUseBothHandlerAndHandlerTemplate = new("DI0016", + "Cannot use both Handler and HandlerTemplate", + "It is not allowed to use both Handler and HandlerTemplate in the same attribute", + "Usage", + DiagnosticSeverity.Error, + true); } diff --git a/ServiceScan.SourceGenerator/GenerateAttributeInfo.cs b/ServiceScan.SourceGenerator/GenerateAttributeInfo.cs index 26f9b8b..da25145 100644 --- a/ServiceScan.SourceGenerator/GenerateAttributeInfo.cs +++ b/ServiceScan.SourceGenerator/GenerateAttributeInfo.cs @@ -125,9 +125,22 @@ internal class ScanForTypesAttribute : Attribute /// This property should point to one of the following: /// - Name of a generic method in the current type. /// - Static method name in found types. + /// This property is incompatible with . /// public string? Handler { get; set; } + /// + /// Sets an expression template to evaluate for each type found. + /// Use T as a placeholder for the full name of each found type. + /// For void methods (or methods returning their first argument), the template is used as a statement; + /// a semicolon is appended automatically if not present. + /// For methods returning a collection, the template is used as the expression for each collection element. + /// This property is incompatible with . + /// + /// new T(argument) + /// registry.Add(new T(argument)) + public string? HandlerTemplate { get; set; } + /// /// Sets the assembly containing the given type as the source of types to scan. /// If not specified, the assembly containing the method with this attribute will be used. diff --git a/ServiceScan.SourceGenerator/Model/AttributeModel.cs b/ServiceScan.SourceGenerator/Model/AttributeModel.cs index cf38995..6d51878 100644 --- a/ServiceScan.SourceGenerator/Model/AttributeModel.cs +++ b/ServiceScan.SourceGenerator/Model/AttributeModel.cs @@ -5,7 +5,7 @@ namespace ServiceScan.SourceGenerator.Model; enum KeySelectorType { Method, GenericMethod, TypeMember }; -enum CustomHandlerType { Method, TypeMethod }; +enum CustomHandlerType { Method, TypeMethod, Template }; record AttributeModel( string? AssignableToTypeName, @@ -28,7 +28,8 @@ record AttributeModel( bool AsImplementedInterfaces, bool AsSelf, Location Location, - bool HasErrors) + bool HasErrors, + string? HandlerTemplate) { public bool HasSearchCriteria => TypeNameFilter != null || AssignableToTypeName != null || AttributeFilterTypeName != null; @@ -49,6 +50,7 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho var keySelector = attribute.NamedArguments.FirstOrDefault(a => a.Key == "KeySelector").Value.Value as string; var customHandler = (attribute.NamedArguments.FirstOrDefault(a => a.Key == "Handler").Value.Value ?? attribute.NamedArguments.FirstOrDefault(a => a.Key == "CustomHandler").Value.Value) as string; + var handlerTemplate = attribute.NamedArguments.FirstOrDefault(a => a.Key == "HandlerTemplate").Value.Value as string; var assignableToTypeParametersCount = assignableTo?.TypeParameters.Length ?? 0; @@ -86,6 +88,9 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho if (string.IsNullOrWhiteSpace(assemblyNameFilter)) assemblyNameFilter = null; + if (string.IsNullOrWhiteSpace(handlerTemplate)) + handlerTemplate = null; + var attributeFilterTypeName = attributeFilterType?.ToFullMetadataName(); var excludeByAttributeTypeName = excludeByAttributeType?.ToFullMetadataName(); var assemblyOfTypeName = assemblyType?.ToFullMetadataName(); @@ -134,6 +139,7 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho asImplementedInterfaces, asSelf, location, - hasError); + hasError, + handlerTemplate); } } \ No newline at end of file From 5642584d6b4bd446fa84a240fddcc645c68676a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:19:16 +0000 Subject: [PATCH 3/7] Optimize HandlerTemplate: use compiled static regex and short-circuit EndsWith check Agent-Logs-Url: https://github.com/Dreamescaper/ServiceScan.SourceGenerator/sessions/03f9fca7-2aac-495b-94d4-354852cc473b Co-authored-by: Dreamescaper <17177729+Dreamescaper@users.noreply.github.com> --- .../DependencyInjectionGenerator.FindServicesToRegister.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs index 4b4ff87..c866dee 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs @@ -14,6 +14,8 @@ public partial class DependencyInjectionGenerator "System.IAsyncDisposable" ]; + private static readonly Regex TypePlaceholderRegex = new(@"\bT\b", RegexOptions.Compiled); + private static DiagnosticModel FindServicesToRegister((DiagnosticModel, Compilation) context) { var (diagnosticModel, compilation) = context; @@ -186,7 +188,7 @@ private static void AddTemplateStatementItem( { var implementationTypeName = implementationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var statement = ExpandTemplate(attribute.HandlerTemplate!, implementationTypeName); - if (!statement.TrimEnd().EndsWith(";")) + if (!statement.EndsWith(";") && !statement.TrimEnd().EndsWith(";")) statement += ";"; customHandlers.Add(new CustomHandlerModel( @@ -198,7 +200,7 @@ private static void AddTemplateStatementItem( private static string ExpandTemplate(string template, string typeName) { - return Regex.Replace(template, @"\bT\b", typeName); + return TypePlaceholderRegex.Replace(template, typeName); } private static IEnumerable GetSuitableInterfaces(ITypeSymbol type) From ea6369c527bbd5992f9f4d235ba03cbb610f0677 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 15:50:21 +0000 Subject: [PATCH 4/7] Update README.md to document HandlerTemplate property Agent-Logs-Url: https://github.com/Dreamescaper/ServiceScan.SourceGenerator/sessions/45210ec8-80ca-48d7-9e03-aeaaf4255cf7 Co-authored-by: Dreamescaper <17177729+Dreamescaper@users.noreply.github.com> --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index e0e6dd6..3e24618 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,27 @@ public static partial class TypeDiscovery } ``` +### Create instances using constructors with parameters + +When the types you want to instantiate require constructor arguments, use `HandlerTemplate` instead of `Handler`. +The placeholder `T` is replaced with the fully-qualified name of each matched type: +```csharp +public static partial class Factory +{ + [ScanForTypes(AssignableTo = typeof(IPlugin), HandlerTemplate = "new T(options)")] + public static partial IPlugin[] CreatePlugins(PluginOptions options); +} +``` + +`HandlerTemplate` also works with void methods (and methods that return their first parameter), where each expanded expression becomes a statement: +```csharp +public static partial class ServiceCollectionExtensions +{ + [ScanForTypes(AssignableTo = typeof(IPlugin), HandlerTemplate = "registry.Add(new T(options))")] + public static partial void RegisterPlugins(PluginRegistry registry, PluginOptions options); +} +``` + ## Parameters @@ -195,6 +216,7 @@ public static partial class TypeDiscovery | Property | Description | | --- | --- | | **Handler** | Sets this property to invoke a custom method for each type found. This property should point to one of the following:
- Name of a generic method in the current type.
- Static method name in found types.
**Note:** Types are automatically filtered by the generic constraints defined on the method's type parameters (e.g., `class`, `struct`, `new()`, interface constraints). | +| **HandlerTemplate** | Sets an expression template to evaluate for each type found. Use `T` as a placeholder for the fully-qualified name of each matched type. For collection-returning methods the template is the element expression; for `void` methods it becomes a statement (a `;` is appended automatically if absent). Incompatible with `Handler`. | | **FromAssemblyOf** | Sets the assembly containing the given type as the source of types to scan. If not specified, the assembly containing the method with this attribute will be used. | | **AssemblyNameFilter** | Sets this value to filter scanned assemblies by assembly name. This option is incompatible with `FromAssemblyOf`. You can use '*' wildcards. You can also use ',' to separate multiple filters. | | **AssignableTo** | Sets the type that the scanned types must be assignable to. | From 6af0890c721814cda87f0ef9d99536c69f8ed2a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 17:45:28 +0000 Subject: [PATCH 5/7] Broaden HandlerTemplate README section to cover non-standard cases beyond constructors Agent-Logs-Url: https://github.com/Dreamescaper/ServiceScan.SourceGenerator/sessions/fc89dc29-473c-48ba-9b29-64a15376f02e Co-authored-by: Dreamescaper <17177729+Dreamescaper@users.noreply.github.com> --- README.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3e24618..6d7dc2d 100644 --- a/README.md +++ b/README.md @@ -170,10 +170,12 @@ public static partial class TypeDiscovery } ``` -### Create instances using constructors with parameters +### Use a template expression for each matched type -When the types you want to instantiate require constructor arguments, use `HandlerTemplate` instead of `Handler`. -The placeholder `T` is replaced with the fully-qualified name of each matched type: +When `Handler` is not flexible enough — for example when you need to pass extra arguments, call static methods with multiple parameters, or perform `typeof` manipulations — use `HandlerTemplate`. +The placeholder `T` is replaced with the fully-qualified name of each matched type at code-generation time. + +Instantiate types with constructor arguments: ```csharp public static partial class Factory { @@ -182,7 +184,25 @@ public static partial class Factory } ``` -`HandlerTemplate` also works with void methods (and methods that return their first parameter), where each expanded expression becomes a statement: +Call a static factory method that takes multiple parameters: +```csharp +public static partial class PipelineBuilder +{ + [ScanForTypes(AssignableTo = typeof(IPipelineStep), HandlerTemplate = "T.Create(context, logger)")] + public static partial IPipelineStep[] BuildSteps(PipelineContext context, ILogger logger); +} +``` + +Perform `typeof` manipulations (e.g. register open-generic metadata): +```csharp +public static partial class ServiceCollectionExtensions +{ + [ScanForTypes(AssignableTo = typeof(ICommandHandler<>), HandlerTemplate = "services.AddTransient(typeof(ICommandHandler<>), typeof(T))")] + public static partial void RegisterHandlers(IServiceCollection services); +} +``` + +`HandlerTemplate` works equally well with void methods, where each expanded expression becomes a statement: ```csharp public static partial class ServiceCollectionExtensions { From bee2278805a6b1f37e83ef0295f9a4c14039434d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 17:50:04 +0000 Subject: [PATCH 6/7] Replace DI-registration typeof example with a HandlerDescriptor typeof example Agent-Logs-Url: https://github.com/Dreamescaper/ServiceScan.SourceGenerator/sessions/039c87db-f442-450d-94d7-fb871c81d9f8 Co-authored-by: Dreamescaper <17177729+Dreamescaper@users.noreply.github.com> --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6d7dc2d..2e14f55 100644 --- a/README.md +++ b/README.md @@ -193,12 +193,12 @@ public static partial class PipelineBuilder } ``` -Perform `typeof` manipulations (e.g. register open-generic metadata): +Build descriptor objects using `typeof(T)` and additional context: ```csharp -public static partial class ServiceCollectionExtensions +public static partial class HandlerRegistry { - [ScanForTypes(AssignableTo = typeof(ICommandHandler<>), HandlerTemplate = "services.AddTransient(typeof(ICommandHandler<>), typeof(T))")] - public static partial void RegisterHandlers(IServiceCollection services); + [ScanForTypes(AssignableTo = typeof(ICommandHandler), HandlerTemplate = "new HandlerDescriptor(typeof(T), category)")] + public static partial HandlerDescriptor[] GetDescriptors(string category); } ``` From 5f43d73943f0df979e9749dacfe09c4b86f6f6db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 17:53:13 +0000 Subject: [PATCH 7/7] Fix misleading class name ServiceCollectionExtensions in README void method example Agent-Logs-Url: https://github.com/Dreamescaper/ServiceScan.SourceGenerator/sessions/f3293937-d4cf-4fcf-aa3c-c1c153bf5421 Co-authored-by: Dreamescaper <17177729+Dreamescaper@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e14f55..ddc105e 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ public static partial class HandlerRegistry `HandlerTemplate` works equally well with void methods, where each expanded expression becomes a statement: ```csharp -public static partial class ServiceCollectionExtensions +public static partial class PluginLoader { [ScanForTypes(AssignableTo = typeof(IPlugin), HandlerTemplate = "registry.Add(new T(options))")] public static partial void RegisterPlugins(PluginRegistry registry, PluginOptions options);