diff --git a/ConfigurationProcessor.DependencyInjection.sln b/ConfigurationProcessor.DependencyInjection.sln
index 6bbcc18..b3f8662 100644
--- a/ConfigurationProcessor.DependencyInjection.sln
+++ b/ConfigurationProcessor.DependencyInjection.sln
@@ -37,13 +37,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleOutboxApi", "sample\S
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Components", "sample\Sample.Components\Sample.Components.csproj", "{53B667D5-FB51-4C83-A040-31724EC96F30}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationProcessor.DependencyInjection.Generator", "src\ConfigurationProcessor.DependencyInjection.Generator\ConfigurationProcessor.DependencyInjection.Generator.csproj", "{AD8581E7-B99F-42D8-BBA9-39D631F1F496}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApiGenerator", "sample\TestWebApiGenerator\TestWebApiGenerator.csproj", "{0945131E-BEAB-4EE0-86FE-A2C44300CCA1}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationProcessor.DependencyInjection.SourceGeneration", "src\ConfigurationProcessor.DependencyInjection.SourceGeneration\ConfigurationProcessor.DependencyInjection.SourceGeneration.csproj", "{06738AF5-221D-44C4-AD3D-3377CA4C1BEC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationProcessor.SourceGeneration.UnitTests", "tests\ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests\ConfigurationProcessor.SourceGeneration.UnitTests.csproj", "{1FA1DC9D-FF01-4B92-9EB0-5551F4016553}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationProcessor.SourceGeneration", "src\ConfigurationProcessor.SourceGeneration\ConfigurationProcessor.SourceGeneration.csproj", "{D3FF19EC-056F-4A54-81C2-6399877A6258}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests", "tests\ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests\ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests.csproj", "{1FA1DC9D-FF01-4B92-9EB0-5551F4016553}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationProcessor.Generator", "src\ConfigurationProcessor.Generator\ConfigurationProcessor.Generator.csproj", "{BC10F1A0-C2BE-4D0D-B874-6A79B709CCDF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -87,22 +87,22 @@ Global
{53B667D5-FB51-4C83-A040-31724EC96F30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53B667D5-FB51-4C83-A040-31724EC96F30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53B667D5-FB51-4C83-A040-31724EC96F30}.Release|Any CPU.Build.0 = Release|Any CPU
- {AD8581E7-B99F-42D8-BBA9-39D631F1F496}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AD8581E7-B99F-42D8-BBA9-39D631F1F496}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AD8581E7-B99F-42D8-BBA9-39D631F1F496}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AD8581E7-B99F-42D8-BBA9-39D631F1F496}.Release|Any CPU.Build.0 = Release|Any CPU
{0945131E-BEAB-4EE0-86FE-A2C44300CCA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0945131E-BEAB-4EE0-86FE-A2C44300CCA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0945131E-BEAB-4EE0-86FE-A2C44300CCA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0945131E-BEAB-4EE0-86FE-A2C44300CCA1}.Release|Any CPU.Build.0 = Release|Any CPU
- {06738AF5-221D-44C4-AD3D-3377CA4C1BEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {06738AF5-221D-44C4-AD3D-3377CA4C1BEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {06738AF5-221D-44C4-AD3D-3377CA4C1BEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {06738AF5-221D-44C4-AD3D-3377CA4C1BEC}.Release|Any CPU.Build.0 = Release|Any CPU
{1FA1DC9D-FF01-4B92-9EB0-5551F4016553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FA1DC9D-FF01-4B92-9EB0-5551F4016553}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FA1DC9D-FF01-4B92-9EB0-5551F4016553}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FA1DC9D-FF01-4B92-9EB0-5551F4016553}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D3FF19EC-056F-4A54-81C2-6399877A6258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D3FF19EC-056F-4A54-81C2-6399877A6258}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D3FF19EC-056F-4A54-81C2-6399877A6258}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D3FF19EC-056F-4A54-81C2-6399877A6258}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BC10F1A0-C2BE-4D0D-B874-6A79B709CCDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BC10F1A0-C2BE-4D0D-B874-6A79B709CCDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BC10F1A0-C2BE-4D0D-B874-6A79B709CCDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BC10F1A0-C2BE-4D0D-B874-6A79B709CCDF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -117,10 +117,10 @@ Global
{81216638-2D84-40AB-8AAA-5CA8065079F7} = {14052F2C-4DA2-45FC-8F05-33B7AC728494}
{E6D3E9E4-983D-4193-90FC-98E3F7C8B530} = {645ABE25-B876-4372-8712-C66AA34020FC}
{53B667D5-FB51-4C83-A040-31724EC96F30} = {645ABE25-B876-4372-8712-C66AA34020FC}
- {AD8581E7-B99F-42D8-BBA9-39D631F1F496} = {60E2AA27-F390-4152-9039-A05388E2D3D8}
{0945131E-BEAB-4EE0-86FE-A2C44300CCA1} = {645ABE25-B876-4372-8712-C66AA34020FC}
- {06738AF5-221D-44C4-AD3D-3377CA4C1BEC} = {60E2AA27-F390-4152-9039-A05388E2D3D8}
{1FA1DC9D-FF01-4B92-9EB0-5551F4016553} = {14052F2C-4DA2-45FC-8F05-33B7AC728494}
+ {D3FF19EC-056F-4A54-81C2-6399877A6258} = {60E2AA27-F390-4152-9039-A05388E2D3D8}
+ {BC10F1A0-C2BE-4D0D-B874-6A79B709CCDF} = {60E2AA27-F390-4152-9039-A05388E2D3D8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2358CB49-45E6-4E83-85C1-F995C676A43F}
diff --git a/sample/TestWebApiGenerator/ServiceRegistrationExtensions.cs b/sample/TestWebApiGenerator/ServiceRegistrationExtensions.cs
index 3262390..f85d5f5 100644
--- a/sample/TestWebApiGenerator/ServiceRegistrationExtensions.cs
+++ b/sample/TestWebApiGenerator/ServiceRegistrationExtensions.cs
@@ -1,11 +1,11 @@
-using ConfigurationProcessor.DependencyInjection;
+using ConfigurationProcessor;
using OpenTelemetry.Trace;
namespace TestWebApiGenerator;
internal static partial class ServiceRegistrationExtensions
{
- [GenerateServiceRegistration("Services", ExcludedSections = new[] { "Hsts" })]
+ [GenerateServiceRegistration("Services", ExcludedSections = new[] { "Hsts" }, ImplicitSuffixes = new[] { "Instrumentation", "Exporter" })]
public static partial void AddServicesFromConfiguration(this WebApplicationBuilder builder);
// [GenerateServiceRegistration("Services")]
diff --git a/sample/TestWebApiGenerator/TestWebApiGenerator.csproj b/sample/TestWebApiGenerator/TestWebApiGenerator.csproj
index d6a2ec0..b1422fd 100644
--- a/sample/TestWebApiGenerator/TestWebApiGenerator.csproj
+++ b/sample/TestWebApiGenerator/TestWebApiGenerator.csproj
@@ -18,8 +18,8 @@
-
-
+
+
diff --git a/sample/TestWebApiGenerator/appsettings.json b/sample/TestWebApiGenerator/appsettings.json
index d9ddfd1..756dee3 100644
--- a/sample/TestWebApiGenerator/appsettings.json
+++ b/sample/TestWebApiGenerator/appsettings.json
@@ -24,10 +24,10 @@
"EndpointsApiExplorer": true,
"SwaggerGen": true,
"OpenTelemetryTracing": {
- "AspNetCoreInstrumentation": true,
- "HttpClientInstrumentation": true,
- "SqlClientInstrumentation": true,
- "JaegerExporter": true
+ "AspNetCore": true,
+ "HttpClient": true,
+ "SqlClient": true,
+ "Jaeger": true
}
}
}
diff --git a/src/ConfigurationProcessor.Core/Implementation/CommonExtensions.cs b/src/ConfigurationProcessor.Core/Implementation/CommonExtensions.cs
index 415bbe7..0f23e2e 100644
--- a/src/ConfigurationProcessor.Core/Implementation/CommonExtensions.cs
+++ b/src/ConfigurationProcessor.Core/Implementation/CommonExtensions.cs
@@ -114,6 +114,22 @@ public static List FindConfigurationExtensionMethods(
IEnumerable? candidateNames,
MethodFilter? filter)
{
+ static bool IsDefined(MethodInfo method, Type attributeType)
+ {
+#if Generator
+ try
+ {
+ return method.CustomAttributes.Any(x => x.AttributeType.FullName == attributeType.FullName);
+ }
+ catch (InvalidOperationException)
+ {
+ return false;
+ }
+#else
+ return method.IsDefined(attributeType, false);
+#endif
+ }
+
IReadOnlyCollection configurationAssemblies = resolutionContext.ConfigurationAssemblies;
var interfaces = configType.GetInterfaces();
var scannedTypes = configurationAssemblies
@@ -125,11 +141,13 @@ public static List FindConfigurationExtensionMethods(
.Concat(interfaces.Select(t => t.GetTypeInfo()))
.SelectMany(t => candidateNames != null ? candidateNames.SelectMany(n => t.GetDeclaredMethods(n)) : t.DeclaredMethods)
.Where(m => filter == null || filter(m, key))
-#if Generator
- .Where(m => m.IsPublic && (m.IsStatic || IsTypeCompatible(configType, m.DeclaringType) || interfaces.Contains(m.DeclaringType)))
-#else
- .Where(m => !m.IsDefined(typeof(CompilerGeneratedAttribute), false) && m.IsPublic && ((m.IsStatic && m.IsDefined(typeof(ExtensionAttribute), false)) || IsTypeCompatible(configType, m.DeclaringType) || interfaces.Contains(m.DeclaringType)))
-#endif
+ .Where(m =>
+ !IsDefined(m, typeof(CompilerGeneratedAttribute)) && // method must not be compiler generated and
+ m.IsPublic && // method must be public and
+ (
+ (m.IsStatic && IsDefined(m, typeof(ExtensionAttribute))) || // method is an extension method
+ IsTypeCompatible(configType, m.DeclaringType) || // method is declared in the target object type
+ interfaces.Contains(m.DeclaringType))) // the method is declared in one of the implemented interfaces
.Where(m => !m.IsStatic || configType.IsTypeCompatible(SafeGetParameters(m).ElementAtOrDefault(0)?.ParameterType)) // If static method, checks that the first parameter is same as the extension type
.ToList();
diff --git a/src/ConfigurationProcessor.Core/Implementation/StringArgumentValue.cs b/src/ConfigurationProcessor.Core/Implementation/StringArgumentValue.cs
index 89d9aed..40c1158 100644
--- a/src/ConfigurationProcessor.Core/Implementation/StringArgumentValue.cs
+++ b/src/ConfigurationProcessor.Core/Implementation/StringArgumentValue.cs
@@ -166,7 +166,7 @@ public StringArgumentValue(IConfigurationSection section, string providedValue,
if (methodCandidate != null)
{
- if (typeof(MethodInfo) == toType)
+ if (typeof(MethodInfo) == toType || typeof(Delegate) == toType)
{
return methodCandidate;
}
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/ServiceRegistrationClass.cs b/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/ServiceRegistrationClass.cs
deleted file mode 100644
index ec1837c..0000000
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/ServiceRegistrationClass.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
-
-internal sealed class ServiceRegistrationClass
-{
- public List Methods { get; } = new();
-
- public string Keyword { get; init; } = string.Empty;
-
- public string Namespace { get; init; } = string.Empty;
-
- public string Name { get; init; } = string.Empty;
-
- public ServiceRegistrationClass? ParentClass { get; set; }
-}
\ No newline at end of file
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/ServiceRegistrationMethod.cs b/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/ServiceRegistrationMethod.cs
deleted file mode 100644
index 743a045..0000000
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/ServiceRegistrationMethod.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Microsoft.CodeAnalysis;
-
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
-
-internal sealed record class ServiceRegistrationMethod(string Name, string Arguments, string Modifiers, IEnumerable> ConfigurationValues, string ConfigurationSectionName)
-{
- public string UniqueName { get; set; } = string.Empty;
-
- public string? ServiceCollectionField { get; set; }
-
- public string? ConfigurationField { get; set; }
-}
\ No newline at end of file
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/SymbolVisibility.cs b/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/SymbolVisibility.cs
deleted file mode 100644
index 34fc9f3..0000000
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/SymbolVisibility.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
-
-internal enum SymbolVisibility
-{
- Public,
- Private,
- Internal,
-}
\ No newline at end of file
diff --git a/src/ConfigurationProcessor.DependencyInjection.Generator/ConfigurationProcessor.DependencyInjection.Generator.csproj b/src/ConfigurationProcessor.Generator/ConfigurationProcessor.Generator.csproj
similarity index 90%
rename from src/ConfigurationProcessor.DependencyInjection.Generator/ConfigurationProcessor.DependencyInjection.Generator.csproj
rename to src/ConfigurationProcessor.Generator/ConfigurationProcessor.Generator.csproj
index 05520d1..e7c4786 100644
--- a/src/ConfigurationProcessor.DependencyInjection.Generator/ConfigurationProcessor.DependencyInjection.Generator.csproj
+++ b/src/ConfigurationProcessor.Generator/ConfigurationProcessor.Generator.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/ConfigurationProcessor.DependencyInjection.Generator/GenerateServiceRegistrationAttribute.cs b/src/ConfigurationProcessor.Generator/GenerateServiceRegistrationAttribute.cs
similarity index 74%
rename from src/ConfigurationProcessor.DependencyInjection.Generator/GenerateServiceRegistrationAttribute.cs
rename to src/ConfigurationProcessor.Generator/GenerateServiceRegistrationAttribute.cs
index 006f5d3..dcba033 100644
--- a/src/ConfigurationProcessor.DependencyInjection.Generator/GenerateServiceRegistrationAttribute.cs
+++ b/src/ConfigurationProcessor.Generator/GenerateServiceRegistrationAttribute.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace ConfigurationProcessor.DependencyInjection;
+namespace ConfigurationProcessor;
///
/// Attribute for generating service registration.
@@ -33,6 +29,16 @@ public GenerateServiceRegistrationAttribute(string configurationSection)
///
public string[] ExcludedSections { get; set; } = Array.Empty();
+ ///
+ /// Subsections that are treated separately.
+ ///
+ public string[] ExpandableSections { get; set; } = Array.Empty();
+
+ ///
+ /// Suffixes that can be ommitted.
+ ///
+ public string[] ImplicitSuffixes { get; set; } = Array.Empty();
+
///
/// Gets the configuration section.
///
diff --git a/src/ConfigurationProcessor.DependencyInjection.Generator/README.md b/src/ConfigurationProcessor.Generator/README.md
similarity index 100%
rename from src/ConfigurationProcessor.DependencyInjection.Generator/README.md
rename to src/ConfigurationProcessor.Generator/README.md
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/ConfigurationProcessor.DependencyInjection.SourceGeneration.csproj b/src/ConfigurationProcessor.SourceGeneration/ConfigurationProcessor.SourceGeneration.csproj
similarity index 96%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/ConfigurationProcessor.DependencyInjection.SourceGeneration.csproj
rename to src/ConfigurationProcessor.SourceGeneration/ConfigurationProcessor.SourceGeneration.csproj
index eb1bb27..61cd371 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/ConfigurationProcessor.DependencyInjection.SourceGeneration.csproj
+++ b/src/ConfigurationProcessor.SourceGeneration/ConfigurationProcessor.SourceGeneration.csproj
@@ -27,7 +27,6 @@
-
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Core/CoreCompatExtensions.cs b/src/ConfigurationProcessor.SourceGeneration/Core/CoreCompatExtensions.cs
similarity index 78%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Core/CoreCompatExtensions.cs
rename to src/ConfigurationProcessor.SourceGeneration/Core/CoreCompatExtensions.cs
index 573812f..e9813ee 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Core/CoreCompatExtensions.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Core/CoreCompatExtensions.cs
@@ -5,8 +5,21 @@ namespace ConfigurationProcessor.Core.Implementation;
internal static class CoreCompatExtensions
{
- public static IConfigurationArgumentValue GetArgumentValue(this IConfigurationSection value, ResolutionContext resolutionContext)
- => BlankConfigurationArgValue.Instance;
+ public static IConfigurationArgumentValue GetArgumentValue(this IConfigurationSection argumentSection, ResolutionContext resolutionContext)
+ {
+ IConfigurationArgumentValue argumentValue;
+
+ if (argumentSection.Value != null)
+ {
+ argumentValue = new StringArgumentValue(argumentSection, argumentSection.Value, argumentSection.Key);
+ }
+ else
+ {
+ argumentValue = new ObjectArgumentValue(argumentSection);
+ }
+
+ return argumentValue;
+ }
internal static Dictionary Blank(this IConfigurationSection section)
=> new()
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Core/ResolutionContext.cs b/src/ConfigurationProcessor.SourceGeneration/Core/ResolutionContext.cs
similarity index 96%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Core/ResolutionContext.cs
rename to src/ConfigurationProcessor.SourceGeneration/Core/ResolutionContext.cs
index 0b46937..cb86fa1 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Core/ResolutionContext.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Core/ResolutionContext.cs
@@ -1,5 +1,5 @@
using System.Reflection;
-using ConfigurationProcessor.DependencyInjection.SourceGeneration.Utility;
+using ConfigurationProcessor.SourceGeneration.Utility;
using Microsoft.Extensions.Configuration;
namespace ConfigurationProcessor.Core.Implementation;
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Emitter.cs b/src/ConfigurationProcessor.SourceGeneration/Emitter.cs
similarity index 68%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Emitter.cs
rename to src/ConfigurationProcessor.SourceGeneration/Emitter.cs
index 1f598d9..63d4684 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Emitter.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Emitter.cs
@@ -1,15 +1,28 @@
using System.Reflection;
-using ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
-using ConfigurationProcessor.DependencyInjection.SourceGeneration.Utility;
+using ConfigurationProcessor.SourceGeneration.Parsing;
+using ConfigurationProcessor.SourceGeneration.Utility;
using Microsoft.Extensions.Configuration;
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration;
+namespace ConfigurationProcessor.SourceGeneration;
-internal class Emitter
+///
+/// Generates code.
+///
+public static class Emitter
{
+ ///
+ /// The version string.
+ ///
public static readonly string VersionString = typeof(Emitter).Assembly.GetCustomAttribute().InformationalVersion;
- public string Emit(IReadOnlyList generateConfigurationClasses, List references, CancellationToken cancellationToken)
+ ///
+ /// Generates code from service class registrations.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string Emit(IReadOnlyList generateConfigurationClasses, List references, CancellationToken cancellationToken)
{
var emitContext = new EmitContext(generateConfigurationClasses.First().Namespace, references);
@@ -31,12 +44,13 @@ static partial class {{configClass.Name}}
emitContext.IncreaseIndent();
foreach (var configMethod in configClass.Methods)
{
+ emitContext.ImplicitSuffixes = configMethod.ImplicitSuffixes;
var sectionName = configMethod.ConfigurationSectionName;
string configSectionVariableName = "servicesSection";
emitContext.Write($$"""
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("ConfigurationProcessor.DependencyInjection.Generator", "{{VersionString}}")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("ConfigurationProcessor.Generator", "{{VersionString}}")]
{{configMethod.Modifiers}} void {{configMethod.Name}}({{configMethod.Arguments}})
{
var {{configSectionVariableName}} = {{configMethod.ConfigurationField}}.GetSection("{{sectionName}}");
@@ -47,7 +61,7 @@ static partial class {{configClass.Name}}
""");
emitContext.IncreaseIndent();
- BuildMethods(emitContext, configMethod.ConfigurationValues, sectionName, configMethod.ServiceCollectionField!, configSectionVariableName);
+ BuildMethods(emitContext, configMethod.ConfigurationValues, sectionName, configMethod.TargetField!, configSectionVariableName);
emitContext.DecreaseIndent();
emitContext.Write("}");
}
@@ -63,7 +77,7 @@ static partial class {{configClass.Name}}
return emitContext.ToString();
}
- private void BuildMethods(EmitContext emitContext, IEnumerable> configurationValues, string sectionName, string targetExpression, string configSectionVariableName)
+ private static void BuildMethods(EmitContext emitContext, IEnumerable> configurationValues, string sectionName, string targetExpression, string configSectionVariableName)
{
var configBuilder = new ConfigurationBuilder();
configBuilder.AddInMemoryCollection(configurationValues);
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/RegistrationGenerator.cs b/src/ConfigurationProcessor.SourceGeneration/Generator.cs
similarity index 89%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/RegistrationGenerator.cs
rename to src/ConfigurationProcessor.SourceGeneration/Generator.cs
index b33f2fc..8282aa4 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/RegistrationGenerator.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Generator.cs
@@ -4,8 +4,8 @@
using System.Reflection;
using System.Text;
-using ConfigurationProcessor.DependencyInjection.SourceGeneration;
-using ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
+using ConfigurationProcessor.SourceGeneration;
+using ConfigurationProcessor.SourceGeneration.Parsing;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
@@ -16,7 +16,7 @@ namespace ConfigurationProcessor;
/// Generates method for registration based on an appsetting.json file.
///
[Generator]
-public class RegistrationGenerator : ISourceGenerator
+public class Generator : ISourceGenerator
{
///
public void Initialize(GeneratorInitializationContext context)
@@ -33,6 +33,10 @@ public void Execute(GeneratorExecutionContext context)
return;
}
+#if DEBUG
+ System.Diagnostics.Debugger.Launch();
+#endif
+
var p = new Parser(context, context.ReportDiagnostic, context.CancellationToken);
IReadOnlyList registrationClasses = p.GetServiceRegistrationClasses(receiver.ClassDeclarations);
if (registrationClasses.Count > 0)
@@ -42,10 +46,9 @@ public void Execute(GeneratorExecutionContext context)
var mlc = new MetadataLoadContext(resolver);
var references = context.Compilation.ExternalReferences.Select(x => mlc.LoadFromAssemblyPath(x.Display!)).ToList();
- var e = new Emitter();
- string result = e.Emit(registrationClasses, references, context.CancellationToken);
+ string result = Emitter.Emit(registrationClasses, references, context.CancellationToken);
- context.AddSource("RegisterServices.g.cs", SourceText.From(result, Encoding.UTF8));
+ context.AddSource($"{registrationClasses.First().Name}.g.cs", SourceText.From(result, Encoding.UTF8));
}
}
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parser.cs b/src/ConfigurationProcessor.SourceGeneration/Parser.cs
similarity index 91%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parser.cs
rename to src/ConfigurationProcessor.SourceGeneration/Parser.cs
index 129d121..7792b21 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parser.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Parser.cs
@@ -1,19 +1,20 @@
using System.Collections.Immutable;
using System.Data;
using System.Diagnostics;
-using ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
-using ConfigurationProcessor.DependencyInjection.SourceGeneration.Utility;
+using ConfigurationProcessor.SourceGeneration.Parsing;
+using ConfigurationProcessor.SourceGeneration.Utility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration;
+namespace ConfigurationProcessor.SourceGeneration;
internal class Parser
{
internal const string DefaultConfigurationFile = "appsettings.json";
- internal const string GenerateServiceRegistrationAttribute = "ConfigurationProcessor.DependencyInjection.GenerateServiceRegistrationAttribute";
+ internal const string GenerateServiceRegistrationAttribute = "ConfigurationProcessor.GenerateServiceRegistrationAttribute";
internal const string ServiceCollectionTypeName = "Microsoft.Extensions.DependencyInjection.IServiceCollection";
+ internal const string WebApplicationBuilderTypeName = "Microsoft.AspNetCore.Builder.WebApplicationBuilder";
private readonly GeneratorExecutionContext context;
private readonly Action reportDiagnostic;
private readonly CancellationToken cancellationToken;
@@ -35,11 +36,7 @@ internal IReadOnlyList GetServiceRegistrationClasses(I
}
INamedTypeSymbol? serviceCollectionSymbol = context.Compilation.GetBestTypeByMetadataName(ServiceCollectionTypeName);
- if (serviceCollectionSymbol == null)
- {
- // nothing to do if this type isn't available
- return Array.Empty();
- }
+ INamedTypeSymbol? webApplicationBuilderSymbol = context.Compilation.GetBestTypeByMetadataName(WebApplicationBuilderTypeName);
INamedTypeSymbol? configurationSymbol = context.Compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Configuration.IConfiguration");
if (configurationSymbol == null)
@@ -85,6 +82,7 @@ internal IReadOnlyList GetServiceRegistrationClasses(I
Debug.Assert(configurationMethodSymbol != null, "configuration method is present.");
(string configurationSection, string? configurationFile) = (string.Empty, null);
string[] excluded = Array.Empty();
+ string[] suffixes = Array.Empty();
foreach (AttributeListSyntax mal in method.AttributeLists)
{
foreach (AttributeSyntax ma in mal.Attributes)
@@ -163,6 +161,10 @@ internal IReadOnlyList GetServiceRegistrationClasses(I
var values = (ImmutableArray)GetItem(value)!;
excluded = values.Select(x => $"{configurationSection}:{x.Value}").ToArray();
break;
+ case "ImplicitSuffixes":
+ values = (ImmutableArray)GetItem(value)!;
+ suffixes = values.Select(x => x.Value?.ToString()).Where(x => !string.IsNullOrEmpty(x)).ToArray()!;
+ break;
}
}
}
@@ -210,7 +212,7 @@ internal IReadOnlyList GetServiceRegistrationClasses(I
}
var lm = new ServiceRegistrationMethod(configurationMethodSymbol.Name, methodSignature, method.Modifiers.ToString(), configurationValues, configurationSection);
-
+ lm.ImplicitSuffixes = suffixes;
static string ToDisplay(IParameterSymbol parameter)
{
return $"global::{parameter.Type} {parameter.Name}";
@@ -267,7 +269,7 @@ static string ToDisplay(IParameterSymbol parameter)
keepMethod = false;
}
- bool foundServiceCollection = false;
+ bool foundTarget = false;
bool foundConfiguration = false;
foreach (IParameterSymbol paramSymbol in configurationMethodSymbol.Parameters)
{
@@ -295,18 +297,18 @@ static string ToDisplay(IParameterSymbol parameter)
Diag(DiagnosticDescriptors.InvalidGenerateConfigurationMethodParameterName, paramSymbol.Locations[0]);
}
- var matchesServiceCollection = IsBaseOrIdentity(paramTypeSymbol, serviceCollectionSymbol);
+ var notMatchesConfiguration = !IsBaseOrIdentity(paramTypeSymbol, configurationSymbol);
var matchesConfiguration = IsBaseOrIdentity(paramTypeSymbol, configurationSymbol);
- if (foundServiceCollection && matchesServiceCollection)
+ if (foundTarget && notMatchesConfiguration)
{
keepMethod = false;
Diag(DiagnosticDescriptors.MultipleServiceCollectionParameter, paramSymbol.Locations[0]);
break;
}
- else if (matchesServiceCollection)
+ else if (notMatchesConfiguration)
{
- foundServiceCollection = matchesServiceCollection;
- lm.ServiceCollectionField = paramName;
+ foundTarget = notMatchesConfiguration;
+ lm.TargetField = paramName;
}
if (foundConfiguration && matchesConfiguration)
@@ -322,7 +324,7 @@ static string ToDisplay(IParameterSymbol parameter)
}
}
- if (keepMethod && !foundServiceCollection && !foundConfiguration && configurationMethodSymbol.Parameters.Length == 1)
+ if (keepMethod && !foundConfiguration && configurationMethodSymbol.Parameters.Length == 1)
{
// we check if the single parameter has public properties that are assignable to serviceCollection and configuration
var paramSymbol = configurationMethodSymbol.Parameters[0];
@@ -349,12 +351,18 @@ static string ToDisplay(IParameterSymbol parameter)
}
else
{
+ bool isWebAppBuilder = webApplicationBuilderSymbol != null && IsBaseOrIdentity(paramTypeSymbol, webApplicationBuilderSymbol);
+ if (isWebAppBuilder)
+ {
+ foundTarget = false;
+ }
+
var properties = paramTypeSymbol.GetMembers().OfType().ToArray();
foreach (var property in properties)
{
- var matchesServiceCollection = IsBaseOrIdentity(property.Type, serviceCollectionSymbol);
+ var matchesServiceCollection = isWebAppBuilder && serviceCollectionSymbol != null && IsBaseOrIdentity(property.Type, serviceCollectionSymbol);
var matchesConfiguration = IsBaseOrIdentity(property.Type, configurationSymbol);
- if (foundServiceCollection && matchesServiceCollection)
+ if (foundTarget && matchesServiceCollection)
{
keepMethod = false;
Diag(DiagnosticDescriptors.MultipleServiceCollectionParameter, paramSymbol.Locations[0]);
@@ -362,8 +370,8 @@ static string ToDisplay(IParameterSymbol parameter)
}
else if (matchesServiceCollection)
{
- foundServiceCollection = matchesServiceCollection;
- lm.ServiceCollectionField = $"{paramName}.{property.Name}";
+ foundTarget = matchesServiceCollection;
+ lm.TargetField = $"{paramName}.{property.Name}";
}
if (foundConfiguration && matchesConfiguration)
@@ -384,18 +392,18 @@ static string ToDisplay(IParameterSymbol parameter)
if (keepMethod)
{
- if (isStatic && !foundServiceCollection)
+ if (isStatic && !foundTarget)
{
Diag(DiagnosticDescriptors.MissingGenerateConfigurationArgument, method.GetLocation(), lm.Name);
keepMethod = false;
}
- else if (!isStatic && foundServiceCollection)
+ else if (!isStatic && foundTarget)
{
Diag(DiagnosticDescriptors.GenerateConfigurationMethodShouldBeStatic, method.GetLocation());
}
- else if (!isStatic && !foundServiceCollection)
+ else if (!isStatic && !foundTarget)
{
- if (serviceCollectionField == null)
+ if (serviceCollectionField == null && serviceCollectionSymbol != null)
{
(serviceCollectionField, multipleServiceCollectionFields) = FindServiceCollectionField(sm, classDec, serviceCollectionSymbol);
}
@@ -412,7 +420,7 @@ static string ToDisplay(IParameterSymbol parameter)
}
else
{
- lm.ServiceCollectionField = serviceCollectionField;
+ lm.TargetField = serviceCollectionField;
}
}
else if (!foundConfiguration)
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/JsonConfigurationFileParser.cs b/src/ConfigurationProcessor.SourceGeneration/Parsing/JsonConfigurationFileParser.cs
similarity index 93%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/JsonConfigurationFileParser.cs
rename to src/ConfigurationProcessor.SourceGeneration/Parsing/JsonConfigurationFileParser.cs
index 371af14..7f9a72a 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Parsing/JsonConfigurationFileParser.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Parsing/JsonConfigurationFileParser.cs
@@ -2,12 +2,12 @@
using System.Text.Json;
using Microsoft.Extensions.Configuration;
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
+namespace ConfigurationProcessor.SourceGeneration.Parsing;
///
/// This implementation is copied from Microsoft.Extensions.Configuration.Json
///
-internal sealed class JsonConfigurationFileParser
+public sealed class JsonConfigurationFileParser
{
private readonly Dictionary data = new Dictionary(StringComparer.OrdinalIgnoreCase);
private readonly Stack paths = new Stack();
@@ -16,6 +16,11 @@ private JsonConfigurationFileParser()
{
}
+ ///
+ /// Parses a json input stream into key value pairs.
+ ///
+ ///
+ ///
public static IDictionary Parse(Stream input)
=> new JsonConfigurationFileParser().ParseStream(input);
diff --git a/src/ConfigurationProcessor.SourceGeneration/Parsing/ServiceRegistrationClass.cs b/src/ConfigurationProcessor.SourceGeneration/Parsing/ServiceRegistrationClass.cs
new file mode 100644
index 0000000..dba714b
--- /dev/null
+++ b/src/ConfigurationProcessor.SourceGeneration/Parsing/ServiceRegistrationClass.cs
@@ -0,0 +1,32 @@
+namespace ConfigurationProcessor.SourceGeneration.Parsing;
+
+///
+/// Represents a class that contains one or more methods to be code generated.
+///
+public sealed class ServiceRegistrationClass
+{
+ ///
+ /// List of methods to be code generated.
+ ///
+ public List Methods { get; } = new();
+
+ ///
+ /// The csharp keyword for the class declaration e.g. 'class' or 'record'.
+ ///
+ public string Keyword { get; init; } = string.Empty;
+
+ ///
+ /// The namespace of the class.
+ ///
+ public string Namespace { get; init; } = string.Empty;
+
+ ///
+ /// The class name.
+ ///
+ public string Name { get; init; } = string.Empty;
+
+ ///
+ /// The parent class if the class is nested.
+ ///
+ public ServiceRegistrationClass? ParentClass { get; set; }
+}
\ No newline at end of file
diff --git a/src/ConfigurationProcessor.SourceGeneration/Parsing/ServiceRegistrationMethod.cs b/src/ConfigurationProcessor.SourceGeneration/Parsing/ServiceRegistrationMethod.cs
new file mode 100644
index 0000000..a6ae342
--- /dev/null
+++ b/src/ConfigurationProcessor.SourceGeneration/Parsing/ServiceRegistrationMethod.cs
@@ -0,0 +1,34 @@
+using Microsoft.CodeAnalysis;
+
+namespace ConfigurationProcessor.SourceGeneration.Parsing;
+
+///
+/// Represents a method to be code generated.
+///
+/// The method name.
+///
+///
+///
+///
+public sealed record class ServiceRegistrationMethod(string Name, string Arguments, string Modifiers, IEnumerable> ConfigurationValues, string ConfigurationSectionName)
+{
+ ///
+ /// The unique method name.
+ ///
+ public string UniqueName { get; set; } = string.Empty;
+
+ ///
+ /// The target field expression.
+ ///
+ public string? TargetField { get; set; }
+
+ ///
+ /// The configuration field expression.
+ ///
+ public string? ConfigurationField { get; set; }
+
+ ///
+ /// The implicit suffixes.
+ ///
+ public string[]? ImplicitSuffixes { get; set; }
+}
\ No newline at end of file
diff --git a/src/ConfigurationProcessor.SourceGeneration/Parsing/SymbolVisibility.cs b/src/ConfigurationProcessor.SourceGeneration/Parsing/SymbolVisibility.cs
new file mode 100644
index 0000000..bff64bb
--- /dev/null
+++ b/src/ConfigurationProcessor.SourceGeneration/Parsing/SymbolVisibility.cs
@@ -0,0 +1,8 @@
+namespace ConfigurationProcessor.SourceGeneration.Parsing;
+
+internal enum SymbolVisibility
+{
+ Public,
+ Private,
+ Internal,
+}
\ No newline at end of file
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/DiagnosticDescriptorHelper.cs b/src/ConfigurationProcessor.SourceGeneration/Utility/DiagnosticDescriptorHelper.cs
similarity index 91%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/DiagnosticDescriptorHelper.cs
rename to src/ConfigurationProcessor.SourceGeneration/Utility/DiagnosticDescriptorHelper.cs
index 44581d2..ca26bdf 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/DiagnosticDescriptorHelper.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Utility/DiagnosticDescriptorHelper.cs
@@ -1,6 +1,6 @@
using Microsoft.CodeAnalysis;
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.Utility;
+namespace ConfigurationProcessor.SourceGeneration.Utility;
///
/// Helper methods for creating instances.
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/DiagnosticDescriptors.cs b/src/ConfigurationProcessor.SourceGeneration/Utility/DiagnosticDescriptors.cs
similarity index 98%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/DiagnosticDescriptors.cs
rename to src/ConfigurationProcessor.SourceGeneration/Utility/DiagnosticDescriptors.cs
index 12ebdd3..84a420e 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/DiagnosticDescriptors.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Utility/DiagnosticDescriptors.cs
@@ -1,6 +1,6 @@
using Microsoft.CodeAnalysis;
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.Utility;
+namespace ConfigurationProcessor.SourceGeneration.Utility;
internal static class DiagnosticDescriptors
{
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/EmitContext.cs b/src/ConfigurationProcessor.SourceGeneration/Utility/EmitContext.cs
similarity index 98%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/EmitContext.cs
rename to src/ConfigurationProcessor.SourceGeneration/Utility/EmitContext.cs
index 3835fa7..cb1c1fe 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/EmitContext.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Utility/EmitContext.cs
@@ -2,7 +2,7 @@
using System.Reflection;
using System.Text;
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.Utility;
+namespace ConfigurationProcessor.SourceGeneration.Utility;
internal record class EmitContext(string Namespace, List References)
{
@@ -15,6 +15,8 @@ internal record class EmitContext(string Namespace, List References)
public Dictionary> TypeMap { get; } = References.SelectMany(x => x.GetExportedTypes()).GroupBy(x => x.FullName).ToDictionary(x => x.Key, x => x.ToList());
+ public string[]? ImplicitSuffixes { get; set; }
+
public void AddNamespace(string ns)
=> namespaces.Add(ns);
diff --git a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/Helpers.cs b/src/ConfigurationProcessor.SourceGeneration/Utility/Helpers.cs
similarity index 81%
rename from src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/Helpers.cs
rename to src/ConfigurationProcessor.SourceGeneration/Utility/Helpers.cs
index d8d68ed..2945aa0 100644
--- a/src/ConfigurationProcessor.DependencyInjection.SourceGeneration/Utility/Helpers.cs
+++ b/src/ConfigurationProcessor.SourceGeneration/Utility/Helpers.cs
@@ -1,10 +1,11 @@
-using System.Reflection;
+using System.Diagnostics;
+using System.Reflection;
using ConfigurationProcessor.Core.Implementation;
-using ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
+using ConfigurationProcessor.SourceGeneration.Parsing;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Configuration;
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.Utility;
+namespace ConfigurationProcessor.SourceGeneration.Utility;
internal static class Helpers
{
@@ -119,7 +120,6 @@ public static void EmitComplex(
string targetVariableName,
IConfigurationSection configSection,
string newSectionName,
- string parentSectionName,
IConfiguration rootConfiguration,
Dictionary paramArgs)
{
@@ -166,6 +166,16 @@ public static void EmitComplex(
emitContext.DecreaseIndent();
emitContext.Write("}");
}
+ else if (StringArgumentValue.TryParseStaticMemberAccessor(configSection.Value!, out var accessorTypeName, out var memberName))
+ {
+ emitContext.Write($$"""
+
+ if ({{configSectionVariableName}}.GetValue("{{configKey}}") == "{{configSection.Value}}")
+ {
+ {{targetVariableName}}.{{methodName}}({{accessorTypeName}}.{{memberName}});
+ }
+ """);
+ }
else
{
emitContext.Write(string.Empty);
@@ -216,7 +226,6 @@ public static void EmitValues(
configSection,
typeArgs,
paramArgs,
- sectionName,
configSectionVariableName,
targetTypeName,
targetVariableName);
@@ -230,15 +239,25 @@ private static void EmitValues(
IConfigurationSection configSection,
TypeResolver[] typeArgs,
Dictionary paramArgs,
- string configSectionName,
string configSectionVariableName,
string targetTypeName,
string targetVariableName)
{
Type targetType = emitContext.TypeMap[targetTypeName].Single();
var resolutionContext = new ResolutionContext(emitContext, rootConfiguration);
+
+ var origCandidateNames = new[] { methodName, $"Add{methodName}", $"set_{methodName}" };
+ var candidateNames = new List(origCandidateNames);
+ if (emitContext.ImplicitSuffixes != null && emitContext.ImplicitSuffixes.Length > 0)
+ {
+ foreach (var suffix in emitContext.ImplicitSuffixes)
+ {
+ candidateNames.AddRange(origCandidateNames.Select(x => $"{x}{suffix}"));
+ }
+ }
+
IEnumerable configurationMethods = resolutionContext
- .FindConfigurationExtensionMethods(methodName, targetType, typeArgs, new[] { methodName, $"Add{methodName}", $"set_{methodName}" }, null);
+ .FindConfigurationExtensionMethods(methodName, targetType, typeArgs, candidateNames.ToArray(), (m, n) => candidateNames.Contains(n));
var suppliedArgumentNames = paramArgs?.Keys.ToArray() ?? Array.Empty();
@@ -361,16 +380,55 @@ private static void EmitValues(
}
}
- if (targetType.GetProperty(methodName) is PropertyInfo propertyInfo)
+ var propertyInfo = targetType.GetProperty(methodName);
+
+ Debug.Assert(propertyInfo != null || configurationMethod != null, "Configuration method not found and not a property");
+
+ if (propertyInfo != null)
{
if (paramArgs?.Count(x => !string.IsNullOrEmpty(x.Key)) > 0)
{
+ // check if the property has an accessible setter and parameterless constructor
+ if (propertyInfo.CanWrite && propertyInfo.PropertyType.GetConstructor(Type.EmptyTypes) != null)
+ {
+ emitContext.Write(
+ $@"{targetVariableName}.{propertyInfo.Name} = new {propertyInfo.PropertyType.GetCSharpFullName()}();");
+ }
+
foreach (var param in paramArgs)
{
if (propertyInfo.PropertyType.GetProperty(param.Key) is PropertyInfo subProperty)
{
- emitContext.Write(
- $@"{targetVariableName}.{propertyInfo.Name}.{subProperty.Name} = {configSectionVariableName}.GetValue<{subProperty.PropertyType.GetCSharpFullName()}>(""{param.Value.ConfigSection.Key}"");");
+ var value = param.Value.ConfigSection.GetSection(param.Key).Value;
+ if (subProperty.PropertyType == typeof(Type))
+ {
+ emitContext.Write($$"""
+ if ({{configSectionVariableName}}.GetValue("{{param.Value.ConfigSection.Key}}:{{param.Key}}") == "{{value}}")
+ {
+ {{targetVariableName}}.{{propertyInfo.Name}}.{{subProperty.Name}} = typeof({{value}});
+ }
+ else
+ {
+ {{targetVariableName}}.{{propertyInfo.Name}}.{{subProperty.Name}} = global::System.Type.GetType({{configSectionVariableName}}.GetValue("{{param.Value.ConfigSection.Key}}:{{param.Key}}"));
+ }
+
+ """);
+ }
+ else if (StringArgumentValue.TryParseStaticMemberAccessor(value!, out var accessorTypeName, out var memberName))
+ {
+ emitContext.Write($$"""
+ if ({{configSectionVariableName}}.GetValue("{{param.Value.ConfigSection.Key}}:{{param.Key}}") == "{{value}}")
+ {
+ {{targetVariableName}}.{{propertyInfo.Name}}.{{subProperty.Name}} = {{accessorTypeName}}.{{memberName}};
+ }
+
+ """);
+ }
+ else
+ {
+ emitContext.Write(
+ $@"{targetVariableName}.{propertyInfo.Name}.{subProperty.Name} = {configSectionVariableName}.GetValue<{subProperty.PropertyType.GetCSharpFullName()}>(""{param.Value.ConfigSection.Key}:{param.Key}"");");
+ }
}
else if (
(propertyInfo.PropertyType.GetMethods().SingleOrDefault(x => x.Name == param.Key) ??
@@ -404,7 +462,6 @@ private static void EmitValues(
targetVariableName,
configSection,
$"section{methodName}",
- configSectionName,
rootConfiguration,
paramArgs.ToDictionary(x => x.Key, x => x.Value.ConfigSection));
}
@@ -422,7 +479,7 @@ public static string GetCSharpFullName(this Type propertyType)
}
else
{
- return propertyType.FullName;
+ return propertyType.FullName.Replace('+', '.');
}
}
}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 6daf169..b46d10b 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -10,7 +10,7 @@
- 0.2.2
+ 0.3.0
$(Version)-beta.1
diff --git a/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests.csproj b/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/ConfigurationProcessor.SourceGeneration.UnitTests.csproj
similarity index 91%
rename from tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests.csproj
rename to tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/ConfigurationProcessor.SourceGeneration.UnitTests.csproj
index 4b93df8..436e21e 100644
--- a/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests.csproj
+++ b/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/ConfigurationProcessor.SourceGeneration.UnitTests.csproj
@@ -31,7 +31,7 @@
-
+
diff --git a/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/DelegateMembers.cs b/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/DelegateMembers.cs
new file mode 100644
index 0000000..61053d4
--- /dev/null
+++ b/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/DelegateMembers.cs
@@ -0,0 +1,25 @@
+namespace ConfigurationProcessor.SourceGeneration.UnitTests;
+
+public class DelegateMembers
+{
+ public void NonStaticTestDelegate()
+ {
+ }
+
+ public static void TestDelegate()
+ {
+ }
+
+ public static void TestDelegateOverload(string value)
+ {
+ }
+
+ public static void TestDelegateOverload(int value)
+ {
+ }
+
+ public static bool TestDelegateOverload(string svalue = null, int ivalue = 0)
+ {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/EmitterTests.cs b/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/EmitterTests.cs
index 7cf5c5f..6fef78a 100644
--- a/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/EmitterTests.cs
+++ b/tests/ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests/EmitterTests.cs
@@ -1,13 +1,18 @@
-using ConfigurationProcessor.DependencyInjection.SourceGeneration.Parsing;
+using ConfigurationProcessor.SourceGeneration.Parsing;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.DependencyModel;
+using Microsoft.Extensions.Options;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
+using TestDummies;
-namespace ConfigurationProcessor.DependencyInjection.SourceGeneration.UnitTests;
+namespace ConfigurationProcessor.SourceGeneration.UnitTests;
public class EmitterTests
{
+ public static DummyDelegate DummyDelegateField = DelegateMembers.TestDelegate;
+
[Fact]
public void WithObjectNotation_MapToExtensionMethodWithSingleStringParameterUsingStringValue_RegistersService()
{
@@ -19,6 +24,194 @@ public void WithObjectNotation_MapToExtensionMethodWithSingleStringParameterUsin
@"services.AddSimpleString(servicesSection.GetValue(""SimpleString""));");
}
+ [Fact]
+ public void WithObjectNotation_MapStringArrayUsingArrayNotationWithSingleStringOverloadUsingArrayWithMultipleElements_RegistersWithArrayOverload()
+ {
+ TestConfig("""
+ {
+ "DummyString": [
+ "hello",
+ "world"
+ ]
+ }
+ """,
+ """
+ var sectionDummyString = servicesSection.GetSection("DummyString");
+ if (sectionDummyString.Exists())
+ {
+ services.AddDummyString(sectionDummyString.Get());
+ }
+ """);
+ }
+
+ [Fact]
+ public void WithObjectNotation_MapStringArrayUsingArrayNotationWithSingleStringOverloadUsingArrayWithSingleElement_RegistersWithArrayOverload()
+ {
+ TestConfig("""
+ {
+ "DummyString": [
+ "hello"
+ ]
+ }
+ """,
+ """
+ var sectionDummyString = servicesSection.GetSection("DummyString");
+ if (sectionDummyString.Exists())
+ {
+ services.AddDummyString(sectionDummyString.Get());
+ }
+ """);
+ }
+
+ [Fact]
+ public void WithObjectNotation_MapIntArrayDirectlyWithOverload_RegistersService()
+ {
+ TestConfig("""
+ {
+ "DummyArray": [
+ 1,
+ 2
+ ]
+ }
+ """,
+ """
+ var sectionDummyArray = servicesSection.GetSection("DummyArray");
+ if (sectionDummyArray.Exists())
+ {
+ services.AddDummyArray(sectionDummyArray.Get());
+ }
+ """);
+ }
+
+ [Fact]
+ public void WithObjectNotation_MapUsingStringDirectlyWithStringArrayOverload_RegistersWithSingleStringOverload()
+ {
+ TestConfig("""
+ {
+ "DummyString": "hello"
+ }
+ """,
+ """
+ services.AddDummyString(servicesSection.GetValue("DummyString"));
+ """);
+ }
+
+ [Fact]
+ public void WithObjectNotation_MapToExtensionMethodWithSingleDelegateParameterDirectly_RegistersService()
+ {
+ TestConfig($$"""
+ {
+ "SimpleDelegate": "{{NameOf()}}::{{nameof(DelegateMembers.TestDelegate)}}"
+ }
+ """,
+ $$"""
+ if (servicesSection.GetValue("SimpleDelegate") == "{{NameOf()}}::{{nameof(DelegateMembers.TestDelegate)}}")
+ {
+ services.AddSimpleDelegate({{NameOf()}}.{{nameof(DelegateMembers.TestDelegate)}});
+ }
+ """);
+ }
+
+ [Theory]
+ [InlineData("Time")]
+ [InlineData("Time2")]
+ public void WithObjectNotation_MapToExtensionMethodAcceptingConfigurationActionDelegate_GeneratesConfigurationActionBasedOnObject(string timeProperty)
+ {
+ TestConfig($$"""
+ {
+ "ConfigurationAction": {
+ "Name": "hello",
+ "Value": {
+ "{{timeProperty}}" : "13:00:10",
+ "Location": "http://www.google.com",
+ "ContextType": "{{NameOf()}}",
+ "OnError": "{{NameOf()}}::{{nameof(DummyDelegateField)}}"
+ }
+ }
+ }
+ """,
+ $$"""
+ var sectionConfigurationAction = servicesSection.GetSection("ConfigurationAction");
+ if (sectionConfigurationAction.Exists())
+ {
+ services.AddConfigurationAction(options =>
+ {
+ options.Value = new TestDummies.ComplexObject.ChildValue();
+ if (sectionConfigurationAction.GetValue("Value:ContextType") == "{{NameOf()}}")
+ {
+ options.Value.ContextType = typeof({{NameOf()}});
+ }
+ else
+ {
+ options.Value.ContextType = global::System.Type.GetType(sectionConfigurationAction.GetValue("Value:ContextType"));
+ }
+
+ options.Value.Location = sectionConfigurationAction.GetValue("Value:Location");
+ if (sectionConfigurationAction.GetValue("Value:OnError") == "{{NameOf()}}::{{nameof(DummyDelegateField)}}")
+ {
+ options.Value.OnError = {{NameOf()}}.{{nameof(DummyDelegateField)}};
+ }
+
+ options.Value.{{timeProperty}} = sectionConfigurationAction.GetValue("Value:{{timeProperty}}");
+ options.Name = sectionConfigurationAction.GetValue("Name");
+ });
+ }
+ """);
+ }
+
+ [Fact]
+ public void WithObjectNotation_MapToExtensionMethodAcceptingConfigurationActionDelegate_CanCallExtensionMethodsWithSingleStringParameterForConfigurationObjectWithString()
+ {
+ TestConfig("""
+ {
+ "ConfigurationAction": {
+ "ConfigureName": "hello"
+ }
+ }
+ """,
+ """
+ var sectionConfigurationAction = servicesSection.GetSection("ConfigurationAction");
+ if (sectionConfigurationAction.Exists())
+ {
+ services.AddConfigurationAction(options =>
+ {
+
+ options.AddConfigureName(sectionConfigurationAction.GetValue("ConfigureName"));
+ });
+ }
+ """);
+ }
+
+
+ [Fact]
+ public void ConfigurationActionWithMethods()
+ {
+ TestConfig("""
+ {
+ "ConfigurationAction": {
+ "SetName": {
+ "Name": "hello"
+ }
+ }
+ }
+ """,
+ """
+ var sectionConfigurationAction = servicesSection.GetSection("ConfigurationAction");
+ if (sectionConfigurationAction.Exists())
+ {
+ services.AddConfigurationAction(options =>
+ {
+
+ var sectionSetName = sectionConfigurationAction.GetSection("SetName");
+ if (sectionSetName.Exists())
+ {
+ options.SetName(sectionSetName.GetValue("Name"));
+ }
+ });
+ }
+ """);
+ }
+
[Fact]
public void WithObjectNotation_MapToExtensionMethodWithSingleIntegerParameterUsingNumberValue_RegistersService()
{
@@ -180,10 +373,8 @@ public void WithObjectNotation_GivenConnectionString_SetsConnectionStringValue(s
""");
}
- private static void TestConfig(string inputJsonFragment, string expectedCsharpFragment)
+ private static void TestConfig([StringSyntax(StringSyntaxAttribute.Json)] string inputJsonFragment, string expectedCsharpFragment)
{
- var emitter = new Emitter();
-
var inputJson = $$"""
{
"Services" : {{inputJsonFragment}}
@@ -201,12 +392,12 @@ private static void TestConfig(string inputJsonFragment, string expectedCsharpFr
rc.Methods.Add(new ServiceRegistrationMethod("Register", "this IServiceCollection services, IConfiguration configuration", "public partial void", configurationValues, "Services")
{
ConfigurationField = "configuration",
- ServiceCollectionField = "services",
+ TargetField = "services",
});
var assemblies = GetLoadedAssemblies();
- var generatedCsharp = emitter.Emit(new[] { rc }, assemblies, default);
+ var generatedCsharp = Emitter.Emit(new[] { rc }, assemblies, default);
var expectedCsharp = $$"""
//
using TestDummies;
@@ -215,7 +406,7 @@ namespace TestApp
{
static partial class Test
{
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("ConfigurationProcessor.DependencyInjection.Generator", "{{Emitter.VersionString}}")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("ConfigurationProcessor.Generator", "{{Emitter.VersionString}}")]
public partial void void Register(this IServiceCollection services, IConfiguration configuration)
{
var servicesSection = configuration.GetSection("Services");
@@ -263,4 +454,6 @@ private static string IndentLines(string input, string indent)
return sb.ToString().TrimEnd();
}
-}
\ No newline at end of file
+
+ internal static string NameOf() => typeof(T).FullName;
+}