// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using SourceGenerators;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
public sealed partial class ConfigurationBindingGenerator
{
private sealed partial class Emitter
{
private int _valueSuffixIndex;
private bool _emitBlankLineBeforeNextStatement;
private static readonly Regex s_arrayBracketsRegex = new(Regex.Escape("[]"));
private bool ShouldEmitMethods(MethodsToGen_CoreBindingHelper methods) => (_bindingHelperInfo.MethodsToGen & methods) != 0;
private void EmitCoreBindingHelpers()
{
Debug.Assert(_emitBlankLineBeforeNextStatement);
EmitBindingExtStartRegion("Core binding");
EmitConfigurationKeyCaches();
EmitGetCoreMethod();
EmitGetValueCoreMethod();
EmitBindCoreMainMethod();
EmitBindCoreMethods();
EmitInitializeMethods();
EmitHelperMethods();
EmitBindingExtEndRegion();
}
private void EmitConfigurationKeyCaches()
{
if (_bindingHelperInfo.TypesForGen_BindCore is not { Count: not 0 } types)
{
return;
}
EmitBlankLineIfRequired();
foreach (TypeSpec type in types)
{
if (type is not ObjectSpec objectType)
{
continue;
}
Debug.Assert(_typeIndex.HasBindableMembers(objectType));
HashSet<string>? keys = null;
static string GetCacheElement(MemberSpec member) => $@"""{member.ConfigurationKeyName}""";
if (objectType.ConstructorParameters?.Select(m => GetCacheElement(m)) is IEnumerable<string> paramNames)
{
keys = new(paramNames);
}
if (objectType.Properties?.Select(m => GetCacheElement(m)) is IEnumerable<string> propNames)
{
if (keys is null)
{
keys = new(propNames);
}
else
{
keys.UnionWith(propNames);
}
}
// Type has bindable members.
Debug.Assert(keys is not null);
string configKeysSource = string.Join(", ", keys);
string fieldName = TypeIndex.GetConfigKeyCacheFieldName(objectType);
_writer.WriteLine($@"private readonly static Lazy<{TypeDisplayString.HashSetOfString}> {fieldName} = new(() => new {TypeDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});");
}
}
private void EmitGetCoreMethod()
{
if (_bindingHelperInfo.TypesForGen_GetCore is not { Count: not 0 } targetTypes)
{
return;
}
EmitBlankLineIfRequired();
EmitStartBlock($"public static object? {nameof(MethodsToGen_CoreBindingHelper.GetCore)}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, Action<{Identifier.BinderOptions}>? {Identifier.configureOptions})");
EmitCheckForNullArgument_WithBlankLine(Identifier.configuration, _emitThrowIfNullMethod);
_writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureOptions});");
_writer.WriteLine();
EmitIConfigurationHasValueOrChildrenCheck(voidReturn: false);
bool isFirstType = true;
foreach (TypeSpec type in targetTypes)
{
TypeSpec effectiveType = _typeIndex.GetEffectiveTypeSpec(type);
Debug.Assert(effectiveType is UnsupportedTypeSpec || _typeIndex.CanBindTo(type.TypeRef));
string conditionKindExpr = GetConditionKindExpr(ref isFirstType);
EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.TypeRef.FullyQualifiedName}))");
switch (effectiveType)
{
case ParsableFromStringSpec stringParsableType:
{
EmitCastToIConfigurationSection();
EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.configuration}, {Identifier.key}: null, out string? {Identifier.value}))");
EmitBindingLogic(
stringParsableType,
Identifier.value,
Expression.sectionPath,
writeOnSuccess: parsedValueExpr => _writer.WriteLine($"return {parsedValueExpr};"),
checkForNullSectionValue: stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue);
EmitEndBlock(); // End if-check for input type.
}
break;
case ConfigurationSectionSpec:
{
EmitCastToIConfigurationSection();
_writer.WriteLine($"return {Identifier.section};");
}
break;
case ComplexTypeSpec complexType:
{
if (_typeIndex.CanInstantiate(complexType))
{
EmitBindingLogic(complexType, Identifier.instance, Identifier.configuration, InitializationKind.Declaration, ValueDefaulting.CallSetter);
_writer.WriteLine($"return {Identifier.instance};");
}
else if (type is ObjectSpec { InitExceptionMessage: string exMsg })
{
_writer.WriteLine($@"throw new {Identifier.InvalidOperationException}(""{exMsg}"");");
}
#if DEBUG
else
{
Debug.Fail($"Complex should not be included for GetCore gen: {complexType.TypeRef.FullyQualifiedName}");
}
#endif
}
break;
}
EmitEndBlock(); // End if-check for input type.
}
_writer.WriteLine();
Emit_NotSupportedException_TypeNotDetectedAsInput();
EmitEndBlock();
_emitBlankLineBeforeNextStatement = true;
void EmitCastToIConfigurationSection() =>
_writer.WriteLine($$"""
if ({{Identifier.configuration}} is not {{Identifier.IConfigurationSection}} {{Identifier.section}})
{
throw new {{Identifier.InvalidOperationException}}();
}
""");
}
private void EmitGetValueCoreMethod()
{
if (_bindingHelperInfo.TypesForGen_GetValueCore is not { Count: not 0 } targetTypes)
{
return;
}
EmitBlankLineIfRequired();
EmitStartBlock($"public static object? {nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key})");
EmitCheckForNullArgument_WithBlankLine(Identifier.configuration, _emitThrowIfNullMethod);
_writer.WriteLine($@"{Identifier.IConfigurationSection} {Identifier.section} = {GetSectionFromConfigurationExpression(Identifier.key, addQuotes: false)};");
_writer.WriteLine();
EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.section}, {Identifier.key}: null, out string? {Identifier.value}) && !string.IsNullOrEmpty({Identifier.value}))");
bool isFirstType = true;
foreach (TypeSpec type in targetTypes)
{
string conditionKindExpr = GetConditionKindExpr(ref isFirstType);
EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.TypeRef.FullyQualifiedName}))");
EmitBindingLogic(
(ParsableFromStringSpec)_typeIndex.GetEffectiveTypeSpec(type),
Identifier.value,
Expression.sectionPath,
writeOnSuccess: (parsedValueExpr) => _writer.WriteLine($"return {parsedValueExpr};"),
checkForNullSectionValue: false);
EmitEndBlock();
}
EmitEndBlock();
_writer.WriteLine();
_writer.WriteLine("return null;");
EmitEndBlock();
_emitBlankLineBeforeNextStatement = true;
}
private void EmitBindCoreMainMethod()
{
if (_bindingHelperInfo.TypesForGen_BindCoreMain is not { Count: not 0 } targetTypes)
{
return;
}
EmitBlankLineIfRequired();
EmitStartBlock($"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCoreMain)}({Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.instance}, Type {Identifier.type}, {TypeDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})");
EmitCheckForNullArgument_WithBlankLine(Identifier.instance, _emitThrowIfNullMethod, voidReturn: true);
EmitIConfigurationHasValueOrChildrenCheck(voidReturn: true);
_writer.WriteLine($"{Identifier.BinderOptions}? {Identifier.binderOptions} = {Identifier.GetBinderOptions}({Identifier.configureOptions});");
_writer.WriteLine();
bool isFirstType = true;
foreach (ComplexTypeSpec type in targetTypes)
{
ComplexTypeSpec effectiveType = (ComplexTypeSpec)_typeIndex.GetEffectiveTypeSpec(type);
Debug.Assert(_typeIndex.HasBindableMembers(effectiveType));
string conditionKindExpr = GetConditionKindExpr(ref isFirstType);
EmitStartBlock($"{conditionKindExpr} ({Identifier.type} == typeof({type.TypeRef.FullyQualifiedName}))");
_writer.WriteLine($"var {Identifier.temp} = ({effectiveType.TypeRef.FullyQualifiedName}){Identifier.instance};");
EmitBindingLogic(type, Identifier.temp, Identifier.configuration, InitializationKind.None, ValueDefaulting.None);
_writer.WriteLine($"return;");
EmitEndBlock();
}
_writer.WriteLine();
Emit_NotSupportedException_TypeNotDetectedAsInput();
EmitEndBlock();
}
private void EmitBindCoreMethods()
{
if (_bindingHelperInfo.TypesForGen_BindCore is not ImmutableEquatableArray<ComplexTypeSpec> types)
{
return;
}
foreach (ComplexTypeSpec type in types)
{
Debug.Assert(_typeIndex.HasBindableMembers(type));
EmitBlankLineIfRequired();
EmitBindCoreMethod(type);
}
}
private void EmitBindCoreMethod(ComplexTypeSpec type)
{
string objParameterExpression = $"ref {type.TypeRef.FullyQualifiedName} {Identifier.instance}";
EmitStartBlock(@$"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCore)}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, bool defaultValueIfNotFound, {Identifier.BinderOptions}? {Identifier.binderOptions})");
ComplexTypeSpec effectiveType = (ComplexTypeSpec)_typeIndex.GetEffectiveTypeSpec(type);
switch (effectiveType)
{
case ArraySpec arrayType:
{
EmitBindCoreImplForArray(arrayType);
}
break;
case EnumerableSpec enumerableType:
{
EmitBindCoreImplForEnumerableWithAdd(enumerableType);
}
break;
case DictionarySpec dictionaryType:
{
EmitBindCoreImplForDictionary(dictionaryType);
}
break;
case ObjectSpec objectType:
{
EmitBindCoreImplForObject(objectType);
}
break;
default:
{
Debug.Fail($"Unsupported spec for bind core gen: {effectiveType.GetType()}");
}
break;
}
EmitEndBlock();
}
private void EmitInitializeMethods()
{
if (_bindingHelperInfo.TypesForGen_Initialize is not ImmutableEquatableArray<ObjectSpec> types)
{
return;
}
foreach (ObjectSpec type in types)
{
EmitBlankLineIfRequired();
EmitInitializeMethod(type);
}
}
private void EmitInitializeMethod(ObjectSpec type)
{
Debug.Assert(type.InstantiationStrategy is ObjectInstantiationStrategy.ParameterizedConstructor);
Debug.Assert(_typeIndex.CanInstantiate(type));
Debug.Assert(
type is { Properties: not null, ConstructorParameters: not null },
$"Expecting type for init method, {type.DisplayString}, to have both properties and ctor params.");
IEnumerable<PropertySpec> initOnlyProps = type.Properties
.Where(prop => prop.SetOnInit && _typeIndex.ShouldBindTo(prop));
List<string> ctorArgList = new();
EmitStartBlock($"public static {type.TypeRef.FullyQualifiedName} {GetInitializeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
_emitBlankLineBeforeNextStatement = false;
foreach (ParameterSpec parameter in type.ConstructorParameters)
{
string name = parameter.Name;
string argExpr = parameter.RefKind switch
{
RefKind.None => name,
RefKind.Ref => $"ref {name}",
RefKind.Out => "out _",
RefKind.In => $"in {name}",
_ => throw new InvalidOperationException()
};
ctorArgList.Add(argExpr);
EmitBindImplForMember(parameter);
}
foreach (PropertySpec property in initOnlyProps)
{
if (property.MatchingCtorParam is null)
{
EmitBindImplForMember(property);
}
}
string returnExpression = $"return new {type.TypeRef.FullyQualifiedName}({string.Join(", ", ctorArgList)})";
if (!initOnlyProps.Any())
{
_writer.WriteLine($"{returnExpression};");
}
else
{
EmitStartBlock(returnExpression);
foreach (PropertySpec property in initOnlyProps)
{
// Properties bound through a matching constructor parameter don't have a local of their
// own; their bound value lives in the local named after the parameter.
string valueExpr = property.MatchingCtorParam?.Name ?? property.Name;
_writer.WriteLine($@"{property.Name} = {valueExpr},");
}
EmitEndBlock(endBraceTrailingSource: ";");
}
// End method.
EmitEndBlock();
_emitBlankLineBeforeNextStatement = true;
void EmitBindImplForMember(MemberSpec member)
{
TypeSpec memberType = _typeIndex.GetTypeSpec(member.TypeRef);
string parsedMemberDeclarationLhs = $"{memberType.TypeRef.FullyQualifiedName} {member.Name}";
string configKeyName = member.ConfigurationKeyName;
switch (memberType)
{
case ParsableFromStringSpec { StringParsableTypeKind: StringParsableTypeKind.AssignFromSectionValue }:
{
if (member is ParameterSpec parameter && parameter.ErrorOnFailedBinding)
{
string condition = $@"if ({Identifier.configuration}[""{configKeyName}""] is not {parsedMemberDeclarationLhs})";
EmitThrowBlock(condition);
_writer.WriteLine();
return;
}
}
break;
case ConfigurationSectionSpec:
{
_writer.WriteLine($"{parsedMemberDeclarationLhs} = {GetSectionFromConfigurationExpression(configKeyName)};");
return;
}
}
string bangExpr = memberType.IsValueType ? string.Empty : "!";
_writer.WriteLine($"{parsedMemberDeclarationLhs} = {member.DefaultValueExpr}{bangExpr};");
_emitBlankLineBeforeNextStatement = false;
bool canBindToMember = this.EmitBindImplForMember(
member,
member.Name,
sectionPathExpr: GetSectionPathFromConfigurationExpression(configKeyName),
// Since we're binding to local variables, we can always get and set
canSet: true,
canGet: true,
InitializationKind.None);
if (canBindToMember)
{
if (member is ParameterSpec parameter && parameter.ErrorOnFailedBinding)
{
// Add exception logic for parameter ctors; must be present in configuration object.
// In case of Arrays and IEnumerable<T>, we emit extra block to handle collection. The throw block will not be `else` case at that time.
TypeSpec typeSpec = _typeIndex.GetEffectiveTypeSpec(member.TypeRef);
EmitThrowBlock(condition: typeSpec is ArraySpec || typeSpec.IsExactIEnumerableOfT ? $"if ({member.Name} is null)" : "else");
}
_writer.WriteLine();
}
void EmitThrowBlock(string condition) =>
_writer.WriteLine($$"""
{{condition}}
{
throw new {{Identifier.InvalidOperationException}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.FullName, member.Name)}}");
}
""");
}
}
private void EmitHelperMethods()
{
// This is used all the time Get, Bind, and GetValue methods.
EmitTryGetConfigurationValueMethod();
// Emitted if we are to bind objects with complex members, or if we're emitting BindCoreMain or GetCore methods.
bool emitAsConfigWithChildren = ShouldEmitMethods(MethodsToGen_CoreBindingHelper.AsConfigWithChildren);
if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCore))
{
EmitBlankLineIfRequired();
EmitValidateConfigurationKeysMethod();
}
if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore))
{
// HasValueOrChildren references this method.
Debug.Assert(emitAsConfigWithChildren);
EmitBlankLineIfRequired();
EmitHasValueOrChildrenMethod();
}
if (emitAsConfigWithChildren)
{
EmitBlankLineIfRequired();
EmitAsConfigWithChildrenMethod();
}
if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreMain | MethodsToGen_CoreBindingHelper.GetCore) ||
ShouldEmitMethods(MethodsToGen.ConfigBinder_Bind_instance_BinderOptions))
{
EmitBlankLineIfRequired();
EmitGetBinderOptionsHelper();
}
if (_emitEnumParseMethod)
{
_writer.WriteLine();
EmitEnumParseMethod();
_emitBlankLineBeforeNextStatement = true;
}
if (_bindingHelperInfo.TypesForGen_ParsePrimitive is { Count: not 0 } stringParsableTypes)
{
foreach (ParsableFromStringSpec type in stringParsableTypes)
{
if (type.StringParsableTypeKind is not StringParsableTypeKind.Enum)
{
EmitBlankLineIfRequired();
EmitPrimitiveParseMethod(type);
}
}
}
}
private void EmitTryGetConfigurationValueMethod()
{
EmitBlankLineIfRequired();
_writer.WriteLine($$"""
/// <summary>Tries to get the configuration value for the specified key.</summary>
public static bool {{Identifier.TryGetConfigurationValue}}({{Identifier.IConfiguration}} {{Identifier.configuration}}, string {{Identifier.key}}, out string? {{Identifier.value}})
{
if ({{Identifier.configuration}} is {{Identifier.ConfigurationSection}} {{Identifier.section}})
{
return {{Identifier.section}}.TryGetValue({{Identifier.key}}, out {{Identifier.value}});
}
{{Identifier.value}} = {{Identifier.key}} != null ? {{Identifier.configuration}}[{{Identifier.key}}] : {{Identifier.configuration}} is {{Identifier.IConfigurationSection}} sec ? sec.Value : null;
return {{Identifier.value}} != null;
}
""");
}
private void EmitValidateConfigurationKeysMethod()
{
const string keysIdentifier = "keys";
string exceptionMessage = string.Format(ExceptionMessages.MissingConfig, Identifier.ErrorOnUnknownConfiguration, Identifier.BinderOptions, $"{{{Identifier.type}}}", $@"{{string.Join("", "", {Identifier.temp})}}");
EmitBlankLineIfRequired();
_writer.WriteLine($$"""
/// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
public static void {{Identifier.ValidateConfigurationKeys}}(Type {{Identifier.type}}, {{TypeDisplayString.LazyHashSetOfString}} {{keysIdentifier}}, {{Identifier.IConfiguration}} {{Identifier.configuration}}, {{Identifier.BinderOptions}}? {{Identifier.binderOptions}})
{
if ({{Identifier.binderOptions}}?.{{Identifier.ErrorOnUnknownConfiguration}} is true)
{
{{TypeDisplayString.ListOfString}}? {{Identifier.temp}} = null;
foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
{
if (!{{keysIdentifier}}.Value.Contains({{Expression.sectionKey}}))
{
({{Identifier.temp}} ??= new {{TypeDisplayString.ListOfString}}()).Add($"'{{{Expression.sectionKey}}}'");
}
}
if ({{Identifier.temp}} is not null)
{
throw new InvalidOperationException($"{{exceptionMessage}}");
}
}
}
""");
}
private void EmitHasValueOrChildrenMethod()
{
_writer.WriteLine($$"""
public static bool {{Identifier.HasValueOrChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
{
if (({{Identifier.configuration}} as {{Identifier.IConfigurationSection}})?.{{Identifier.Value}} is not null)
{
return true;
}
return {{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}}({{Identifier.configuration}}) is not null;
}
""");
}
private void EmitAsConfigWithChildrenMethod()
{
_writer.WriteLine($$"""
public static {{Identifier.IConfiguration}}? {{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
{
foreach ({{Identifier.IConfigurationSection}} _ in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
{
return {{Identifier.configuration}};
}
return null;
}
""");
}
private void EmitGetBinderOptionsHelper()
{
_writer.WriteLine($$"""
public static {{Identifier.BinderOptions}}? {{Identifier.GetBinderOptions}}({{TypeDisplayString.NullableActionOfBinderOptions}} {{Identifier.configureOptions}})
{
if ({{Identifier.configureOptions}} is null)
{
return null;
}
{{Identifier.BinderOptions}} {{Identifier.binderOptions}} = new();
{{Identifier.configureOptions}}({{Identifier.binderOptions}});
if ({{Identifier.binderOptions}}.BindNonPublicProperties)
{
throw new NotSupportedException($"{{string.Format(ExceptionMessages.CannotSpecifyBindNonPublicProperties)}}");
}
return {{Identifier.binderOptions}};
}
""");
}
private void EmitEnumParseMethod()
{
string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.value} ?? \"null\"}}", $"{{{Identifier.path}}}", $"{{typeof(T)}}");
string parseEnumCall = _emitGenericParseEnum ? "Enum.Parse<T>(value, ignoreCase: true)" : "(T)Enum.Parse(typeof(T), value, ignoreCase: true)";
_writer.WriteLine($$"""
public static T ParseEnum<T>(string value, string? path) where T : struct
{
try
{
return {{parseEnumCall}};
}
catch ({{Identifier.Exception}} {{Identifier.exception}})
{
throw new {{Identifier.InvalidOperationException}}($"{{exceptionArg1}}", {{Identifier.exception}});
}
}
""");
}
private void EmitPrimitiveParseMethod(ParsableFromStringSpec type)
{
StringParsableTypeKind typeKind = type.StringParsableTypeKind;
string typeFQN = type.TypeRef.FullyQualifiedName;
string invariantCultureExpression = $"{Identifier.CultureInfo}.InvariantCulture";
string parsedValueExpr;
switch (typeKind)
{
case StringParsableTypeKind.ByteArray:
{
parsedValueExpr = $"Convert.FromBase64String({Identifier.value})";
}
break;
case StringParsableTypeKind.Integer:
{
parsedValueExpr = $"{typeFQN}.{Identifier.Parse}({Identifier.value}, {Identifier.NumberStyles}.Integer, {invariantCultureExpression})";
}
break;
case StringParsableTypeKind.Float:
{
parsedValueExpr = $"{typeFQN}.{Identifier.Parse}({Identifier.value}, {Identifier.NumberStyles}.Float, {invariantCultureExpression})";
}
break;
case StringParsableTypeKind.Parse:
{
parsedValueExpr = $"{typeFQN}.{Identifier.Parse}({Identifier.value})";
}
break;
case StringParsableTypeKind.ParseInvariant:
{
parsedValueExpr = $"{typeFQN}.{Identifier.Parse}({Identifier.value}, {invariantCultureExpression})"; ;
}
break;
case StringParsableTypeKind.CultureInfo:
{
parsedValueExpr = $"{Identifier.CultureInfo}.GetCultureInfo({Identifier.value})";
}
break;
case StringParsableTypeKind.Uri:
{
parsedValueExpr = $"new Uri({Identifier.value}, UriKind.RelativeOrAbsolute)";
}
break;
default:
{
Debug.Fail($"Invalid string parsable kind: {typeKind}");
return;
}
}
string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.value} ?? \"null\"}}", $"{{{Identifier.path}}}", $"{{typeof({typeFQN})}}");
EmitStartBlock($"public static {typeFQN} {TypeIndex.GetParseMethodName(type)}(string {Identifier.value}, string? {Identifier.path})");
EmitEndBlock($$"""
try
{
return {{parsedValueExpr}};
}
catch ({{Identifier.Exception}} {{Identifier.exception}})
{
throw new {{Identifier.InvalidOperationException}}($"{{exceptionArg1}}", {{Identifier.exception}});
}
""");
}
private void EmitBindCoreImplForArray(ArraySpec type)
{
TypeRef elementTypeRef = type.ElementTypeRef;
string elementTypeFQN = type.ElementTypeRef.FullyQualifiedName;
string tempIdentifier = GetIncrementalIdentifier(Identifier.temp);
// Create temp list.
_writer.WriteLine($"var {tempIdentifier} = new List<{elementTypeFQN}>();");
_writer.WriteLine();
// Bind elements to temp list.
EmitBindingLogicForEnumerableWithAdd(elementTypeRef, tempIdentifier);
_writer.WriteLine();
// Resize array and add binded elements.
_writer.WriteLine($$"""
{{Identifier.Int32}} {{Identifier.originalCount}} = {{Identifier.instance}}.{{Identifier.Length}};
{{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.instance}}, {{Identifier.originalCount}} + {{tempIdentifier}}.{{Identifier.Count}});
{{tempIdentifier}}.{{Identifier.CopyTo}}({{Identifier.instance}}, {{Identifier.originalCount}});
""");
}
private void EmitBindCoreImplForEnumerableWithAdd(EnumerableSpec type)
{
EmitCollectionCastIfRequired(type, out string instanceIdentifier);
EmitBindingLogicForEnumerableWithAdd(type.ElementTypeRef, instanceIdentifier);
}
private void EmitBindingLogicForEnumerableWithAdd(TypeRef elementTypeRef, string enumerableIdentifier)
{
Emit_Foreach_Section_In_ConfigChildren_StartBlock();
string addExpr = $"{enumerableIdentifier}.{Identifier.Add}";
switch (_typeIndex.GetEffectiveTypeSpec(elementTypeRef))
{
case ParsableFromStringSpec stringParsableType:
{
EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.section}, {Identifier.key}: null, out string? {Identifier.value}))");
EmitBindingLogic(
stringParsableType,
Identifier.value,
Expression.sectionPath,
(parsedValueExpr) => _writer.WriteLine($"{addExpr}({parsedValueExpr});"),
checkForNullSectionValue: true);
EmitEndBlock(); // End if-check for input type.
}
break;
case ConfigurationSectionSpec:
{
_writer.WriteLine($"{addExpr}({Identifier.section});");
}
break;
case ComplexTypeSpec complexType when _typeIndex.CanInstantiate(complexType):
{
// If a section possesses a null or empty string value and lacks any children, we bind to the default value of the type.
// In the case of a non-null or non-empty string value without any section children, binding cannot be performed at that moment,
// and this section should be skipped.
EmitBindCheckForSectionValue();
EmitBindingLogic(complexType, Identifier.value, Identifier.section, InitializationKind.Declaration, ValueDefaulting.None);
_writer.WriteLine($"{addExpr}({Identifier.value});");
}
break;
}
EmitEndBlock();
}
// EmitBindCheckForSectionValue produce the following code:
// if (!string.IsNullOrEmpty(section.Value) && !section.GetChildren().Any()) { continue; }
//
// If a section possesses a null or empty string value and lacks any children, we bind to the default value of the type.
// In the case of a non-null or non-empty string value without any section children, binding cannot be performed at that moment,
// and this section should be skipped.
private void EmitBindCheckForSectionValue()
{
EmitStartBlock($"if (!string.IsNullOrEmpty({Expression.sectionValue}) && !{Identifier.section}.{Identifier.GetChildren}().{Identifier.Any}())");
_writer.WriteLine($@"continue;");
EmitEndBlock();
}
private void EmitBindCoreImplForDictionary(DictionarySpec type)
{
EmitCollectionCastIfRequired(type, out string instanceIdentifier);
Emit_Foreach_Section_In_ConfigChildren_StartBlock();
ParsableFromStringSpec keyType = (ParsableFromStringSpec)_typeIndex.GetEffectiveTypeSpec(type.KeyTypeRef);
TypeSpec elementType = _typeIndex.GetEffectiveTypeSpec(type.ElementTypeRef);
// Parse key
EmitBindingLogic(
keyType,
Expression.sectionKey,
Expression.sectionPath,
Emit_BindAndAddLogic_ForElement,
checkForNullSectionValue: false);
void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr)
{
switch (elementType)
{
case ParsableFromStringSpec stringParsableElementType:
{
EmitStartBlock($"if ({Identifier.TryGetConfigurationValue}({Identifier.section}, {Identifier.key}: null, out string? {Identifier.value}))");
EmitBindingLogic(
stringParsableElementType,
Identifier.value,
Expression.sectionPath,
writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {parsedValueExpr};"),
checkForNullSectionValue: true);
EmitEndBlock(); // End if-check for input type.
}
break;
case ConfigurationSectionSpec:
{
_writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {Identifier.section};");
}
break;
case ComplexTypeSpec complexElementType:
{
if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue)
{
// Save value to local to avoid parsing twice - during look-up and during add.
_writer.WriteLine($"{keyType.TypeRef.FullyQualifiedName} {Identifier.key} = {parsedKeyExpr};");
parsedKeyExpr = Identifier.key;
}
bool isValueType = complexElementType.IsValueType;
string expressionForElementIsNotNull = $"{Identifier.element} is not null";
string elementTypeDisplayString = complexElementType.TypeRef.FullyQualifiedName + (complexElementType.IsValueType ? string.Empty : "?");
string expressionForElementExists = $"{instanceIdentifier}.{Identifier.TryGetValue}({parsedKeyExpr}, out {elementTypeDisplayString} {Identifier.element})";
string conditionToUseExistingElement = expressionForElementExists;
// If key already exists, bind to existing element instance if not null (for ref types).
if (!isValueType)
{
conditionToUseExistingElement += $" && {expressionForElementIsNotNull}";
}
if (_typeIndex.CanInstantiate(complexElementType))
{
EmitStartBlock($"if (!({conditionToUseExistingElement}))");
EmitObjectInit(complexElementType, Identifier.element, InitializationKind.SimpleAssignment, Identifier.section);
EmitEndBlock();
EmitBindingLogic();
}
else
{
EmitStartBlock($"if ({conditionToUseExistingElement})");
EmitBindingLogic();
EmitEndBlock();
}
void EmitBindingLogic()
{
this.EmitBindingLogic(
complexElementType,
Identifier.element,
Identifier.section,
InitializationKind.None,
ValueDefaulting.None);
_writer.WriteLine($"{instanceIdentifier}[{parsedKeyExpr}] = {Identifier.element};");
}
}
break;
}
}
EmitEndBlock();
}
private void EmitBindCoreImplForObject(ObjectSpec type)
{
Debug.Assert(_typeIndex.HasBindableMembers(type));
string keyCacheFieldName = TypeIndex.GetConfigKeyCacheFieldName(type);
string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.TypeRef.FullyQualifiedName}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});";
_writer.WriteLine(validateMethodCallExpr);
foreach (PropertySpec property in type.Properties!)
{
if (_typeIndex.ShouldBindTo(property))
{
string containingTypeRef = property.IsStatic ? type.TypeRef.FullyQualifiedName : Identifier.instance;
EmitBindImplForMember(
property,
memberAccessExpr: $"{containingTypeRef}.{property.Name}",
GetSectionPathFromConfigurationExpression(property.ConfigurationKeyName),
canSet: property.CanSet,
canGet: property.CanGet,
InitializationKind.Declaration);
}
}
}
private bool EmitBindImplForMember(
MemberSpec member,
string memberAccessExpr,
string sectionPathExpr,
bool canSet,
bool canGet,
InitializationKind initializationKind)
{
string sectionParseExpr = GetSectionFromConfigurationExpression(member.ConfigurationKeyName);
switch (_typeIndex.GetEffectiveTypeSpec(member.TypeRef))
{
case ParsableFromStringSpec stringParsableType:
{
// Reflection binder does not support binding to set-only properties
if (canSet && canGet)
{
EmitBlankLineIfRequired();
string valueIdentifier = GetIncrementalIdentifier(Identifier.value);
EmitStartBlock($@"if ({Identifier.TryGetConfigurationValue}({Identifier.configuration}, {Identifier.key}: ""{member.ConfigurationKeyName}"", out string? {valueIdentifier}))");
// Decide to emit the null check block for nullable types (e.g. int?).
// We don't emit this block for types that can be assigned directly from IConfigurationSection.Value as the valueIdentifier value can assigned
// anyway to the memberAccessExpr regardless of the nullability. This can reduce the emitted code size when assigning objects or strings which
// are common cases.
bool emitNullCheck = member.TypeRef.CanBeNull && stringParsableType.StringParsableTypeKind != StringParsableTypeKind.AssignFromSectionValue;
// Nullable type can be set to null
if (emitNullCheck)
{
EmitStartBlock($"if ({valueIdentifier} is null)");
_writer.WriteLine($"{memberAccessExpr} = null;");
EmitEndBlock(); // End if-check for input type.
EmitStartBlock($"else");
}
EmitBindingLogic(
stringParsableType,
valueIdentifier,
sectionPathExpr,
writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{memberAccessExpr} = {parsedValueExpr};"),
checkForNullSectionValue: true);
if (emitNullCheck)
{
EmitEndBlock(); // end of $"if ({valueIdentifier} is null)"
}
EmitEndBlock(); // End if-check for input type.
if (initializationKind == InitializationKind.Declaration)
{
EmitStartBlock($"else if (defaultValueIfNotFound)");
if (!stringParsableType.TypeRef.CanBeNull)
{
_writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr};");
}
else
{
_writer.WriteLine($"var currentValue = {memberAccessExpr};");
EmitStartBlock($"if (currentValue is not null)");
_writer.WriteLine($"{memberAccessExpr} = currentValue;");
EmitEndBlock();
}
EmitEndBlock();
}
}
return true;
}
case ConfigurationSectionSpec:
{
if (canSet)
{
EmitBlankLineIfRequired();
_writer.WriteLine($"{memberAccessExpr} = {sectionParseExpr};");
}
return true;
}
case ComplexTypeSpec complexType:
{
// Early detection of types we cannot bind to and skip it.
if (!_typeIndex.HasBindableMembers(complexType) &&
!_typeIndex.GetEffectiveTypeSpec(complexType).IsValueType &&
complexType is not CollectionSpec &&
((ObjectSpec)complexType).InstantiationStrategy == ObjectInstantiationStrategy.ParameterizedConstructor)
{
return false;
}
EmitBlankLineIfRequired();
string configSection = GetIncrementalIdentifier(Identifier.value);
_writer.WriteLine($"var {configSection} = {sectionParseExpr};");
string sectionValidationCall = $"{MethodsToGen_CoreBindingHelper.AsConfigWithChildren}({configSection})";
string sectionIdentifier = GetIncrementalIdentifier(Identifier.section);
EmitStartBlock($"if ({sectionValidationCall} is {Identifier.IConfigurationSection} {sectionIdentifier})");
EmitBindingLogicForComplexMember(member, memberAccessExpr, sectionIdentifier, canSet);
EmitEndBlock();
// The current configuration section doesn't have any children, let's check if we are binding to an array and the configuration value is empty string.
// In this case, we will assign an empty array to the member. Otherwise, we will skip the binding logic.
if ((complexType is ArraySpec || complexType.IsExactIEnumerableOfT) && canSet)
{
// Either we have an array or we have an IEnumerable<T> both these types can be assigned an empty array when having empty string configuration value.
Debug.Assert(complexType is ArraySpec || complexType is EnumerableSpec);
string valueIdentifier = GetIncrementalIdentifier(Identifier.value);
EmitStartBlock($@"if ({memberAccessExpr} is null && {Identifier.TryGetConfigurationValue}({configSection}, {Identifier.key}: null, out string? {valueIdentifier}) && {valueIdentifier} == string.Empty)");
_writer.WriteLine($"{memberAccessExpr} = global::System.{Identifier.Array}.Empty<{((CollectionSpec)complexType).ElementTypeRef.FullyQualifiedName}>();");
EmitEndBlock();
}
return _typeIndex.CanInstantiate(complexType);
}
default:
return false;
}
}
private void EmitBindingLogicForComplexMember(
MemberSpec member,
string memberAccessExpr,
string configArgExpr,
bool canSet)
{
TypeSpec memberType = _typeIndex.GetTypeSpec(member.TypeRef);
ComplexTypeSpec effectiveMemberType = (ComplexTypeSpec)_typeIndex.GetEffectiveTypeSpec(memberType);
string tempIdentifier = GetIncrementalIdentifier(Identifier.temp);
InitializationKind initKind;
string targetObjAccessExpr;
if (effectiveMemberType.IsValueType)
{
if (!canSet)
{
return;
}
Debug.Assert(canSet);
string effectiveMemberTypeFQN = effectiveMemberType.TypeRef.FullyQualifiedName;
initKind = InitializationKind.None;
if (memberType is NullableSpec)
{
string nullableTempIdentifier = GetIncrementalIdentifier(Identifier.temp);
_writer.WriteLine($"{memberType.TypeRef.FullyQualifiedName} {nullableTempIdentifier} = {memberAccessExpr};");
_writer.WriteLine(
$"{effectiveMemberTypeFQN} {tempIdentifier} = {nullableTempIdentifier}.{Identifier.HasValue} ? {nullableTempIdentifier}.{Identifier.Value} : new {effectiveMemberTypeFQN}();");
}
else
{
_writer.WriteLine($"{effectiveMemberTypeFQN} {tempIdentifier} = {memberAccessExpr};");
}
targetObjAccessExpr = tempIdentifier;
}
else if (member.CanGet)
{
targetObjAccessExpr = memberAccessExpr;
initKind = InitializationKind.AssignmentWithNullCheck;
}
else
{
targetObjAccessExpr = memberAccessExpr;
initKind = InitializationKind.SimpleAssignment;
}
Action<string, string?>? writeOnSuccess = !canSet
? null
: (bindedValueIdentifier, tempIdentifierStoringExpr) =>
{
if (memberAccessExpr != bindedValueIdentifier)
{
_writer.WriteLine($"{memberAccessExpr} = {bindedValueIdentifier};");
if (tempIdentifierStoringExpr is not null)
{
_writer.WriteLine($"{tempIdentifierStoringExpr}");
}
if (member.CanGet && _typeIndex.CanInstantiate(effectiveMemberType))
{
EmitEndBlock();
EmitStartBlock("else");
_writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr};");
}
}
else
{
_writer.WriteLine($"{tempIdentifierStoringExpr}");
}
};
EmitBindingLogic(
effectiveMemberType,
targetObjAccessExpr,
configArgExpr,
initKind,
ValueDefaulting.None,
writeOnSuccess
);
}
private void EmitBindingLogic(
ComplexTypeSpec type,
string memberAccessExpr,
string configArgExpr,
InitializationKind initKind,
ValueDefaulting valueDefaulting,
Action<string, string?>? writeOnSuccess = null)
{
if (!_typeIndex.HasBindableMembers(type))
{
if (initKind is not InitializationKind.None)
{
if (_typeIndex.CanInstantiate(type))
{
EmitObjectInit(type, memberAccessExpr, initKind, configArgExpr);
}
else if (type is ObjectSpec { InitExceptionMessage: string exMsg })
{
_writer.WriteLine($@"throw new {Identifier.InvalidOperationException}(""{exMsg}"");");
}
}
return;
}
string tempIdentifier = GetIncrementalIdentifier(Identifier.temp);
if (initKind is InitializationKind.AssignmentWithNullCheck)
{
Debug.Assert(!type.IsValueType);
_writer.WriteLine($"{type.TypeRef.FullyQualifiedName}? {tempIdentifier} = {memberAccessExpr};");
EmitBindingLogic(tempIdentifier, InitializationKind.AssignmentWithNullCheck);
}
else if (initKind is InitializationKind.None && type.IsValueType)
{
EmitBindingLogic(tempIdentifier, InitializationKind.Declaration, $"{memberAccessExpr} = {tempIdentifier};");
}
else
{
EmitBindingLogic(memberAccessExpr, initKind);
}
void EmitBindingLogic(string instanceToBindExpr, InitializationKind initKind, string? tempIdentifierStoringExpr = null)
{
string bindCoreCall = $@"{nameof(MethodsToGen_CoreBindingHelper.BindCore)}({configArgExpr}, ref {instanceToBindExpr}, defaultValueIfNotFound: {FormatDefaultValueIfNotFound()}, {Identifier.binderOptions});";
if (_typeIndex.CanInstantiate(type))
{
if (initKind is not InitializationKind.None)
{
EmitObjectInit(type, instanceToBindExpr, initKind, configArgExpr);
}
EmitBindCoreCall();
}
else
{
Debug.Assert(!type.IsValueType);
EmitStartBlock($"if ({instanceToBindExpr} is not null)");
EmitBindCoreCall();
EmitEndBlock();
if (type is ObjectSpec { InitExceptionMessage: string exMsg })
{
EmitStartBlock("else");
_writer.WriteLine($@"throw new {Identifier.InvalidOperationException}(""{exMsg}"");");
EmitEndBlock();
}
}
void EmitBindCoreCall()
{
_writer.WriteLine(bindCoreCall);
writeOnSuccess?.Invoke(instanceToBindExpr, tempIdentifierStoringExpr);
}
string FormatDefaultValueIfNotFound() => valueDefaulting == ValueDefaulting.CallSetter ? "true" : "false";
}
}
private void EmitBindingLogic(
ParsableFromStringSpec type,
string sectionValueExpr,
string sectionPathExpr,
Action<string>? writeOnSuccess,
bool checkForNullSectionValue)
{
StringParsableTypeKind typeKind = type.StringParsableTypeKind;
Debug.Assert(typeKind is not StringParsableTypeKind.None);
string parsedValueExpr = typeKind switch
{
StringParsableTypeKind.AssignFromSectionValue => sectionValueExpr,
StringParsableTypeKind.Enum => $"ParseEnum<{type.TypeRef.FullyQualifiedName}>({sectionValueExpr}, {sectionPathExpr})",
_ => $"{TypeIndex.GetParseMethodName(type)}({sectionValueExpr}, {sectionPathExpr})",
};
// Usually assigning the configuration value to string or object
if (!checkForNullSectionValue || typeKind == StringParsableTypeKind.AssignFromSectionValue)
{
writeOnSuccess?.Invoke(parsedValueExpr);
}
else
{
// call parsing methods
string conditionPrefix = string.Empty;
// Special case ByteArray when having empty string configuration value as we need to assign empty byte array at that time.
if (typeKind == StringParsableTypeKind.ByteArray)
{
EmitStartBlock($"if ({sectionValueExpr} == string.Empty)");
writeOnSuccess?.Invoke(parsedValueExpr);
EmitEndBlock();
conditionPrefix = "else ";
}
EmitStartBlock($"{conditionPrefix}if (!string.IsNullOrEmpty({sectionValueExpr}))");
writeOnSuccess?.Invoke(parsedValueExpr);
EmitEndBlock();
}
}
private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, InitializationKind initKind, string configArgExpr)
{
CollectionSpec? collectionType = type as CollectionSpec;
ObjectSpec? objectType = type as ObjectSpec;
string? castExpr = null;
string initExpr;
string typeFQN = type.TypeRef.FullyQualifiedName;
if (collectionType is not null)
{
if (collectionType is ArraySpec)
{
initExpr = $"new {s_arrayBracketsRegex.Replace(typeFQN, "[0]", 1)}";
}
else
{
CollectionWithCtorInitSpec collectionWithCtorInitType = (CollectionWithCtorInitSpec)collectionType;
if (collectionWithCtorInitType.InstantiationConcreteType is not CollectionInstantiationConcreteType.Self)
{
castExpr = $"({collectionWithCtorInitType.TypeRef.FullyQualifiedName})";
}
typeFQN = TypeIndex.GetInstantiationTypeDisplayString(collectionWithCtorInitType);
initExpr = $"{castExpr}new {typeFQN}()";
}
}
else
{
Debug.Assert(objectType is not null);
ObjectInstantiationStrategy strategy = objectType.InstantiationStrategy;
if (strategy is ObjectInstantiationStrategy.ParameterlessConstructor)
{
// value tuple types will be declared with syntax like:
// (int, int) value = default;
// This is to avoid using invalid syntax calling the parameterless constructor
initExpr = type.IsValueTuple ? "default" : $"new {typeFQN}()";
}
else
{
Debug.Assert(strategy is ObjectInstantiationStrategy.ParameterizedConstructor);
string initMethodIdentifier = GetInitializeMethodDisplayString(((ObjectSpec)type));
initExpr = $"{initMethodIdentifier}({configArgExpr}, {Identifier.binderOptions})";
}
}
switch (initKind)
{
case InitializationKind.Declaration:
{
Debug.Assert(!memberAccessExpr.Contains('.'));
// value tuple will be declared with syntax like:
// (int, int) value = default;
// We need to specify the typeFQN as we assign the variable to default value.
string declarationType = type.IsValueTuple ? typeFQN : $"var";
_writer.WriteLine($"{declarationType} {memberAccessExpr} = {initExpr};");
}
break;
case InitializationKind.AssignmentWithNullCheck:
{
if (collectionType is CollectionWithCtorInitSpec
{
InstantiationStrategy: CollectionInstantiationStrategy.CopyConstructor or CollectionInstantiationStrategy.LinqToDictionary
} collectionWithCtorInitType)
{
string assignmentValueIfMemberNull = collectionWithCtorInitType.InstantiationStrategy is CollectionInstantiationStrategy.CopyConstructor
? $"new {typeFQN}({memberAccessExpr})"
: $"{memberAccessExpr}.ToDictionary(pair => pair.Key, pair => pair.Value)";
Debug.Assert(castExpr is not null || collectionWithCtorInitType.InstantiationConcreteType is CollectionInstantiationConcreteType.Self);
assignmentValueIfMemberNull = $"{castExpr}{assignmentValueIfMemberNull}";
_writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : {assignmentValueIfMemberNull};");
}
else
{
_writer.WriteLine($"{memberAccessExpr} ??= {initExpr};");
}
}
break;
case InitializationKind.SimpleAssignment:
{
_writer.WriteLine($"{memberAccessExpr} = {initExpr};");
}
break;
default:
{
Debug.Fail($"Invalid initialization kind: {initKind}");
}
break;
}
return true;
}
private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn)
{
string returnPostfix = voidReturn ? string.Empty : " null";
_writer.WriteLine($$"""
if (!{{Identifier.HasValueOrChildren}}({{Identifier.configuration}}))
{
return{{returnPostfix}};
}
""");
_writer.WriteLine();
}
private void EmitCollectionCastIfRequired(CollectionWithCtorInitSpec type, out string instanceIdentifier)
{
if (type.PopulationCastType is CollectionPopulationCastType.NotApplicable)
{
instanceIdentifier = Identifier.instance;
return;
}
instanceIdentifier = Identifier.temp;
string castExpression = $"{TypeIndex.GetPopulationCastTypeDisplayString(type)} {instanceIdentifier}";
if (type.ShouldTryCast)
{
_writer.WriteLine($$"""
if ({{Identifier.instance}} is not {{castExpression}})
{
return;
}
""");
}
else
{
_writer.WriteLine($$"""
{{castExpression}} = {{Identifier.instance}};
""");
}
_writer.WriteLine();
}
private void Emit_Foreach_Section_In_ConfigChildren_StartBlock() =>
EmitStartBlock($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");
private void Emit_NotSupportedException_TypeNotDetectedAsInput() =>
_writer.WriteLine(@$"throw new NotSupportedException($""{string.Format(ExceptionMessages.TypeNotDetectedAsInput, "{type}")}"");");
private static string GetSectionPathFromConfigurationExpression(string configurationKeyName)
=> $@"{GetSectionFromConfigurationExpression(configurationKeyName)}.{Identifier.Path}";
private static string GetSectionFromConfigurationExpression(string configurationKeyName, bool addQuotes = true)
{
string argExpr = addQuotes ? $@"""{configurationKeyName}""" : configurationKeyName;
return $@"{Identifier.configuration}.{Identifier.GetSection}({argExpr})";
}
private static string GetConditionKindExpr(ref bool isFirstType)
{
if (isFirstType)
{
isFirstType = false;
return "if";
}
return "else if";
}
}
}
}