From 707fa4b04e3e610e08c162befcdae1ad3f151090 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Sun, 28 May 2023 22:55:44 -0700 Subject: [PATCH 1/2] Add individual compilation tests for each config binding overload --- .../ConfigurationBindingGenerator.Emitter.cs | 4 +- .../gen/Model/BinderMethodSpecifier.cs | 3 +- ...llGen.generated.txt => Bind.generated.txt} | 0 .../Baselines/Bind_Instance.generated.txt | 180 +++++ .../Bind_Instance_BinderOptions.generated.txt | 195 +++++ .../Baselines/Bind_Key_Instance.generated.txt | 180 +++++ ...enerated.txt => Collections.generated.txt} | 15 + ....generated.txt => Configure.generated.txt} | 0 ...allGen.generated.txt => Get.generated.txt} | 0 ...n.generated.txt => GetValue.generated.txt} | 2 +- .../Baselines/GetValue_T_Key.generated.txt | 52 ++ .../GetValue_T_Key_DefaultValue.generated.txt | 52 ++ .../GetValue_TypeOf_Key.generated.txt | 52 ++ ...alue_TypeOf_Key_DefaultValue.generated.txt | 52 ++ .../Baselines/Get_T.generated.txt | 214 +++++ .../Get_T_BinderOptions.generated.txt | 214 +++++ .../Baselines/Get_TypeOf.generated.txt | 124 +++ .../Get_TypeOf_BinderOptions.generated.txt | 124 +++ ...generated.txt => Primitives.generated.txt} | 0 ...gurationBindingGeneratorTests.Baselines.cs | 731 ++++++++++++++++++ .../ConfigurationBindingGeneratorTests.cs | 49 ++ ...ation.Binder.SourceGeneration.Tests.csproj | 8 +- 22 files changed, 2244 insertions(+), 7 deletions(-) rename src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/{TestBindCallGen.generated.txt => Bind.generated.txt} (100%) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Instance.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Instance_BinderOptions.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Key_Instance.generated.txt rename src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/{TestCollectionsGen.generated.txt => Collections.generated.txt} (93%) rename src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/{TestConfigureCallGen.generated.txt => Configure.generated.txt} (100%) rename src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/{TestGetCallGen.generated.txt => Get.generated.txt} (100%) rename src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/{TestGetValueCallGen.generated.txt => GetValue.generated.txt} (96%) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_T_Key.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_T_Key_DefaultValue.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_TypeOf_Key.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_TypeOf_Key_DefaultValue.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_T.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_T_BinderOptions.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_TypeOf.generated.txt create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_TypeOf_BinderOptions.generated.txt rename src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/{TestPrimitivesGen.generated.txt => Primitives.generated.txt} (100%) create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.cs create mode 100644 src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs index f309f203486bf7..911a00d91fdbd5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs @@ -176,7 +176,7 @@ private void EmitGetValueMethods() if (_generationSpec.ShouldEmitMethods(BinderMethodSpecifier.GetValue_TypeOf_key_defaultValue)) { EmitBlankLineIfRequired(); - _writer.WriteLine($"public static object? {Identifier.GetValue}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayName.Type} {Identifier.type}, string {Identifier.key}, object? {Identifier.defaultValue}) =>" + + _writer.WriteLine($"public static object? {Identifier.GetValue}(this {FullyQualifiedDisplayName.IConfiguration} {Identifier.configuration}, {FullyQualifiedDisplayName.Type} {Identifier.type}, string {Identifier.key}, object? {Identifier.defaultValue}) => " + $"{expressionForGetValueCore}({Identifier.configuration}, {Identifier.type}, {Identifier.key}) ?? {Identifier.defaultValue};"); } } @@ -527,7 +527,7 @@ private void EmitHelperMethods() _precedingBlockExists = true; } - if (_generationSpec.ShouldEmitMethods(BinderMethodSpecifier.RootMethodsWithConfigOptions)) + if (_generationSpec.ShouldEmitMethods(BinderMethodSpecifier.MethodsThatAssessBinderOptions)) { _writer.WriteBlankLine(); EmitGetBinderOptionsHelper(); diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/BinderMethodSpecifier.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/BinderMethodSpecifier.cs index 33b2859a867bfa..dafbd0bee3c019 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/BinderMethodSpecifier.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/BinderMethodSpecifier.cs @@ -80,6 +80,7 @@ internal enum BinderMethodSpecifier Bind = Bind_instance | Bind_instance_BinderOptions | Bind_key_instance, Get = Get_T | Get_T_BinderOptions | Get_TypeOf | Get_TypeOf_BinderOptions, GetValue = GetValue_T_key | GetValue_T_key_defaultValue | GetValue_TypeOf_key | GetValue_TypeOf_key_defaultValue, - RootMethodsWithConfigOptions = Bind_instance_BinderOptions | Get_T_BinderOptions | Get_TypeOf_BinderOptions, + + MethodsThatAssessBinderOptions = Bind_instance_BinderOptions | Get } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestBindCallGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestBindCallGen.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Instance.generated.txt new file mode 100644 index 00000000000000..280ad976830a54 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Instance.generated.txt @@ -0,0 +1,180 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj, binderOptions: null); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + int element; + if (section.Value is string stringValue0) + { + element = ParseInt(stringValue0, () => section.Path); + obj.Add(element); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + string key = section.Key; + if (section.Value is string stringValue1) + { + obj[key] = stringValue1; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + string key = section.Key; + if (!(obj.TryGetValue(key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + BindCore(section, ref element!, binderOptions); + obj[key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + switch (section.Key) + { + case "MyString": + { + if (configuration["MyString"] is string stringValue3) + { + obj.MyString = stringValue3; + } + } + break; + case "MyInt": + { + if (configuration["MyInt"] is string stringValue4) + { + obj.MyInt = ParseInt(stringValue4, () => section.Path); + } + } + break; + case "MyList": + { + if (HasChildren(section)) + { + List temp5 = obj.MyList; + temp5 ??= new List(); + BindCore(section, ref temp5, binderOptions); + obj.MyList = temp5; + } + } + break; + case "MyDictionary": + { + if (HasChildren(section)) + { + Dictionary temp6 = obj.MyDictionary; + temp6 ??= new Dictionary(); + BindCore(section, ref temp6, binderOptions); + obj.MyDictionary = temp6; + } + } + break; + case "MyComplexDictionary": + { + if (HasChildren(section)) + { + Dictionary temp7 = obj.MyComplexDictionary; + temp7 ??= new Dictionary(); + BindCore(section, ref temp7, binderOptions); + obj.MyComplexDictionary = temp7; + } + } + break; + default: + { + if (binderOptions?.ErrorOnUnknownConfiguration == true) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + break; + } + } + + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + } + } + + public static bool HasChildren(IConfiguration configuration) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Instance_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Instance_BinderOptions.generated.txt new file mode 100644 index 00000000000000..bf816b9543cad1 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Instance_BinderOptions.generated.txt @@ -0,0 +1,195 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass obj, global::System.Action? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration, ref obj, global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetBinderOptions(configureOptions)); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + int element; + if (section.Value is string stringValue0) + { + element = ParseInt(stringValue0, () => section.Path); + obj.Add(element); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + string key = section.Key; + if (section.Value is string stringValue1) + { + obj[key] = stringValue1; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + string key = section.Key; + if (!(obj.TryGetValue(key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + BindCore(section, ref element!, binderOptions); + obj[key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + switch (section.Key) + { + case "MyString": + { + if (configuration["MyString"] is string stringValue3) + { + obj.MyString = stringValue3; + } + } + break; + case "MyInt": + { + if (configuration["MyInt"] is string stringValue4) + { + obj.MyInt = ParseInt(stringValue4, () => section.Path); + } + } + break; + case "MyList": + { + if (HasChildren(section)) + { + List temp5 = obj.MyList; + temp5 ??= new List(); + BindCore(section, ref temp5, binderOptions); + obj.MyList = temp5; + } + } + break; + case "MyDictionary": + { + if (HasChildren(section)) + { + Dictionary temp6 = obj.MyDictionary; + temp6 ??= new Dictionary(); + BindCore(section, ref temp6, binderOptions); + obj.MyDictionary = temp6; + } + } + break; + case "MyComplexDictionary": + { + if (HasChildren(section)) + { + Dictionary temp7 = obj.MyComplexDictionary; + temp7 ??= new Dictionary(); + BindCore(section, ref temp7, binderOptions); + obj.MyComplexDictionary = temp7; + } + } + break; + default: + { + if (binderOptions?.ErrorOnUnknownConfiguration == true) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + break; + } + } + + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + } + } + + public static bool HasChildren(IConfiguration configuration) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; + } + + public static BinderOptions? GetBinderOptions(System.Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + if (binderOptions.BindNonPublicProperties) + { + throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + return binderOptions; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Key_Instance.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Key_Instance.generated.txt new file mode 100644 index 00000000000000..466637a43bbb9d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Bind_Key_Instance.generated.txt @@ -0,0 +1,180 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.BindCore(configuration.GetSection(key), ref obj, binderOptions: null); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + int element; + if (section.Value is string stringValue0) + { + element = ParseInt(stringValue0, () => section.Path); + obj.Add(element); + } + } + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + string key = section.Key; + if (section.Value is string stringValue1) + { + obj[key] = stringValue1; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + string key = section.Key; + if (!(obj.TryGetValue(key, out Program.MyClass2? element) && element is not null)) + { + element = new Program.MyClass2(); + } + BindCore(section, ref element!, binderOptions); + obj[key] = element; + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + switch (section.Key) + { + case "MyString": + { + if (configuration["MyString"] is string stringValue3) + { + obj.MyString = stringValue3; + } + } + break; + case "MyInt": + { + if (configuration["MyInt"] is string stringValue4) + { + obj.MyInt = ParseInt(stringValue4, () => section.Path); + } + } + break; + case "MyList": + { + if (HasChildren(section)) + { + List temp5 = obj.MyList; + temp5 ??= new List(); + BindCore(section, ref temp5, binderOptions); + obj.MyList = temp5; + } + } + break; + case "MyDictionary": + { + if (HasChildren(section)) + { + Dictionary temp6 = obj.MyDictionary; + temp6 ??= new Dictionary(); + BindCore(section, ref temp6, binderOptions); + obj.MyDictionary = temp6; + } + } + break; + case "MyComplexDictionary": + { + if (HasChildren(section)) + { + Dictionary temp7 = obj.MyComplexDictionary; + temp7 ??= new Dictionary(); + BindCore(section, ref temp7, binderOptions); + obj.MyComplexDictionary = temp7; + } + } + break; + default: + { + if (binderOptions?.ErrorOnUnknownConfiguration == true) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + break; + } + } + + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + } + } + + public static bool HasChildren(IConfiguration configuration) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestCollectionsGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt similarity index 93% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestCollectionsGen.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt index e10a7f436cf755..96233267179cc5 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestCollectionsGen.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt @@ -213,6 +213,21 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration return false; } + public static BinderOptions? GetBinderOptions(System.Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + if (binderOptions.BindNonPublicProperties) + { + throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + return binderOptions; + } + public static int ParseInt(string stringValue, Func getPath) { try diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestConfigureCallGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Configure.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestConfigureCallGen.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Configure.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetCallGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetCallGen.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetValueCallGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue.generated.txt similarity index 96% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetValueCallGen.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue.generated.txt index ed3c062c6729de..753a62bb0bbf76 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestGetValueCallGen.generated.txt +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue.generated.txt @@ -9,7 +9,7 @@ internal static class GeneratedConfigurationBinder public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetValueCore(configuration, type, key); - public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key, object? defaultValue) =>global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetValueCore(configuration, type, key) ?? defaultValue; + public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key, object? defaultValue) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetValueCore(configuration, type, key) ?? defaultValue; } namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_T_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_T_Key.generated.txt new file mode 100644 index 00000000000000..d57c4e65db6edf --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_T_Key.generated.txt @@ -0,0 +1,52 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static T? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetValueCore(configuration, typeof(T), key) ?? default(T)); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + IConfigurationSection section = configuration.GetSection(key); + object? obj; + + if (type == typeof(int)) + { + if (section.Value is string stringValue0) + { + obj = ParseInt(stringValue0, () => section.Path); + return obj; + } + } + + return null; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_T_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_T_Key_DefaultValue.generated.txt new file mode 100644 index 00000000000000..c4dac1baa51599 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_T_Key_DefaultValue.generated.txt @@ -0,0 +1,52 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static T? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, T defaultValue) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetValueCore(configuration, typeof(T), key) ?? defaultValue); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + IConfigurationSection section = configuration.GetSection(key); + object? obj; + + if (type == typeof(int)) + { + if (section.Value is string stringValue0) + { + obj = ParseInt(stringValue0, () => section.Path); + return obj; + } + } + + return null; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_TypeOf_Key.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_TypeOf_Key.generated.txt new file mode 100644 index 00000000000000..944ea17b4311be --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_TypeOf_Key.generated.txt @@ -0,0 +1,52 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetValueCore(configuration, type, key); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + IConfigurationSection section = configuration.GetSection(key); + object? obj; + + if (type == typeof(bool?)) + { + if (section.Value is string stringValue0) + { + obj = ParseBool(stringValue0, () => section.Path); + return obj; + } + } + + return null; + } + + public static bool ParseBool(string stringValue, Func getPath) + { + try + { + return bool.Parse(stringValue); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_TypeOf_Key_DefaultValue.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_TypeOf_Key_DefaultValue.generated.txt new file mode 100644 index 00000000000000..c622af422b23ae --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/GetValue_TypeOf_Key_DefaultValue.generated.txt @@ -0,0 +1,52 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static object? GetValue(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, string key, object? defaultValue) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetValueCore(configuration, type, key) ?? defaultValue; +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static object? GetValueCore(this IConfiguration configuration, Type type, string key) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + IConfigurationSection section = configuration.GetSection(key); + object? obj; + + if (type == typeof(CultureInfo)) + { + if (section.Value is string stringValue0) + { + obj = ParseCultureInfo(stringValue0, () => section.Path); + return obj; + } + } + + return null; + } + + public static CultureInfo ParseCultureInfo(string stringValue, Func getPath) + { + try + { + return CultureInfo.GetCultureInfo(stringValue); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(CultureInfo)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_T.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_T.generated.txt new file mode 100644 index 00000000000000..896d7e9669ee28 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_T.generated.txt @@ -0,0 +1,214 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static T? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, typeof(T), configureOptions: null) ?? default(T)); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass)) + { + var obj = new Program.MyClass(); + BindCore(configuration, ref obj, binderOptions); + return obj; + } + + throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + int element; + if (section.Value is string stringValue1) + { + element = ParseInt(stringValue1, () => section.Path); + obj.Add(element); + } + } + } + + public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + var temp2 = new List(); + BindCore(configuration, ref temp2, binderOptions); + int originalCount = obj.Length; + Array.Resize(ref obj, originalCount + temp2.Count); + temp2.CopyTo(obj, originalCount); + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + string key = section.Key; + if (section.Value is string stringValue4) + { + obj[key] = stringValue4; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + switch (section.Key) + { + case "MyString": + { + if (configuration["MyString"] is string stringValue5) + { + obj.MyString = stringValue5; + } + } + break; + case "MyInt": + { + if (configuration["MyInt"] is string stringValue6) + { + obj.MyInt = ParseInt(stringValue6, () => section.Path); + } + } + break; + case "MyList": + { + if (HasChildren(section)) + { + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section, ref temp7, binderOptions); + obj.MyList = temp7; + } + } + break; + case "MyArray": + { + if (HasChildren(section)) + { + int[] temp8 = obj.MyArray; + temp8 ??= new int[0]; + BindCore(section, ref temp8, binderOptions); + obj.MyArray = temp8; + } + } + break; + case "MyDictionary": + { + if (HasChildren(section)) + { + Dictionary temp9 = obj.MyDictionary; + temp9 ??= new Dictionary(); + BindCore(section, ref temp9, binderOptions); + obj.MyDictionary = temp9; + } + } + break; + default: + { + if (binderOptions?.ErrorOnUnknownConfiguration == true) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + break; + } + } + + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return HasChildren(configuration); + } + + public static bool HasChildren(IConfiguration configuration) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; + } + + public static BinderOptions? GetBinderOptions(System.Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + if (binderOptions.BindNonPublicProperties) + { + throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + return binderOptions; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_T_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_T_BinderOptions.generated.txt new file mode 100644 index 00000000000000..0d5a4c3136c86f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_T_BinderOptions.generated.txt @@ -0,0 +1,214 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static T? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action? configureOptions) => (T?)(global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, typeof(T), configureOptions) ?? default(T)); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass)) + { + var obj = new Program.MyClass(); + BindCore(configuration, ref obj, binderOptions); + return obj; + } + + throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref List obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + int element; + if (section.Value is string stringValue1) + { + element = ParseInt(stringValue1, () => section.Path); + obj.Add(element); + } + } + } + + public static void BindCore(IConfiguration configuration, ref int[] obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + var temp2 = new List(); + BindCore(configuration, ref temp2, binderOptions); + int originalCount = obj.Length; + Array.Resize(ref obj, originalCount + temp2.Count); + temp2.CopyTo(obj, originalCount); + } + + public static void BindCore(IConfiguration configuration, ref Dictionary obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + foreach (IConfigurationSection section in configuration.GetChildren()) + { + string key = section.Key; + if (section.Value is string stringValue4) + { + obj[key] = stringValue4; + } + } + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + switch (section.Key) + { + case "MyString": + { + if (configuration["MyString"] is string stringValue5) + { + obj.MyString = stringValue5; + } + } + break; + case "MyInt": + { + if (configuration["MyInt"] is string stringValue6) + { + obj.MyInt = ParseInt(stringValue6, () => section.Path); + } + } + break; + case "MyList": + { + if (HasChildren(section)) + { + List temp7 = obj.MyList; + temp7 ??= new List(); + BindCore(section, ref temp7, binderOptions); + obj.MyList = temp7; + } + } + break; + case "MyArray": + { + if (HasChildren(section)) + { + int[] temp8 = obj.MyArray; + temp8 ??= new int[0]; + BindCore(section, ref temp8, binderOptions); + obj.MyArray = temp8; + } + } + break; + case "MyDictionary": + { + if (HasChildren(section)) + { + Dictionary temp9 = obj.MyDictionary; + temp9 ??= new Dictionary(); + BindCore(section, ref temp9, binderOptions); + obj.MyDictionary = temp9; + } + } + break; + default: + { + if (binderOptions?.ErrorOnUnknownConfiguration == true) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + break; + } + } + + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}"); + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return HasChildren(configuration); + } + + public static bool HasChildren(IConfiguration configuration) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; + } + + public static BinderOptions? GetBinderOptions(System.Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + if (binderOptions.BindNonPublicProperties) + { + throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + return binderOptions; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_TypeOf.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_TypeOf.generated.txt new file mode 100644 index 00000000000000..023bf6283e3833 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_TypeOf.generated.txt @@ -0,0 +1,124 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, type, configureOptions: null); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass2)) + { + var obj = new Program.MyClass2(); + BindCore(configuration, ref obj, binderOptions); + return obj; + } + + throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + switch (section.Key) + { + case "MyInt": + { + if (configuration["MyInt"] is string stringValue1) + { + obj.MyInt = ParseInt(stringValue1, () => section.Path); + } + } + break; + default: + { + if (binderOptions?.ErrorOnUnknownConfiguration == true) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + break; + } + } + + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return HasChildren(configuration); + } + + public static bool HasChildren(IConfiguration configuration) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; + } + + public static BinderOptions? GetBinderOptions(System.Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + if (binderOptions.BindNonPublicProperties) + { + throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + return binderOptions; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_TypeOf_BinderOptions.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_TypeOf_BinderOptions.generated.txt new file mode 100644 index 00000000000000..f599117afe8a81 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Get_TypeOf_BinderOptions.generated.txt @@ -0,0 +1,124 @@ +// +#nullable enable + +internal static class GeneratedConfigurationBinder +{ + public static object? Get(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Type type, global::System.Action? configureOptions) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.Helpers.GetCore(configuration, type, configureOptions); +} + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + using System.Globalization; + + internal static class Helpers + { + public static object? GetCore(this IConfiguration configuration, Type type, Action? configureOptions) + { + if (configuration is null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + BinderOptions? binderOptions = GetBinderOptions(configureOptions); + + if (!HasValueOrChildren(configuration)) + { + return null; + } + + if (type == typeof(Program.MyClass2)) + { + var obj = new Program.MyClass2(); + BindCore(configuration, ref obj, binderOptions); + return obj; + } + + throw new global::System.NotSupportedException($"Unable to bind to type '{type}': generator did not detect the type as input."); + } + + public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions) + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + List? temp = null; + foreach (IConfigurationSection section in configuration.GetChildren()) + { + switch (section.Key) + { + case "MyInt": + { + if (configuration["MyInt"] is string stringValue1) + { + obj.MyInt = ParseInt(stringValue1, () => section.Path); + } + } + break; + default: + { + if (binderOptions?.ErrorOnUnknownConfiguration == true) + { + (temp ??= new List()).Add($"'{section.Key}'"); + } + } + break; + } + } + + if (temp is not null) + { + throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}"); + } + } + + public static bool HasValueOrChildren(IConfiguration configuration) + { + if ((configuration as IConfigurationSection)?.Value is not null) + { + return true; + } + return HasChildren(configuration); + } + + public static bool HasChildren(IConfiguration configuration) + { + foreach (IConfigurationSection section in configuration.GetChildren()) + { + return true; + } + return false; + } + + public static BinderOptions? GetBinderOptions(System.Action? configureOptions) + { + if (configureOptions is null) + { + return null; + } + BinderOptions binderOptions = new(); + configureOptions(binderOptions); + if (binderOptions.BindNonPublicProperties) + { + throw new global::System.NotSupportedException($"The configuration binding source generator does not support 'BinderOptions.BindNonPublicProperties'."); + } + return binderOptions; + } + + public static int ParseInt(string stringValue, Func getPath) + { + try + { + return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (Exception exception) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestPrimitivesGen.generated.txt b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt similarity index 100% rename from src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/TestPrimitivesGen.generated.txt rename to src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.cs new file mode 100644 index 00000000000000..2ac4ea12c1dc59 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.Baselines.cs @@ -0,0 +1,731 @@ +// 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.Collections.Immutable; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using SourceGenerators.Tests; +using Xunit; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests +{ + public partial class ConfigurationBindingGeneratorTests + { + private const string BindCallSampleCode = @" +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; + +public class Program +{ + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind(configObj); + config.Bind(configObj, options => { }); + config.Bind(""key"", configObj); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + } + + public class MyClass2 { } +}"; + + [Theory] + [InlineData(LanguageVersion.Preview)] + [InlineData(LanguageVersion.CSharp11)] + public async Task Bind(LanguageVersion langVersion) => + await VerifyAgainstBaselineUsingFile("Bind.generated.txt", BindCallSampleCode, langVersion); + + [Fact] + public async Task Bind_Instance() + { + string source = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind(configObj); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + } + + public class MyClass2 { } + } + """; + + await VerifyAgainstBaselineUsingFile("Bind_Instance.generated.txt", source); + } + + [Fact] + public async Task Bind_Instance_BinderOptions() + { + string source = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind(configObj, options => { }); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + } + + public class MyClass2 { } + } + """; + + await VerifyAgainstBaselineUsingFile("Bind_Instance_BinderOptions.generated.txt", source); + } + + [Fact] + public async Task Bind_Key_Instance() + { + string source = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = new(); + config.Bind("key", configObj); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public Dictionary MyDictionary { get; set; } + public Dictionary MyComplexDictionary { get; set; } + } + + public class MyClass2 { } + } + """; + + await VerifyAgainstBaselineUsingFile("Bind_Key_Instance.generated.txt", source); + } + + [Fact] + public async Task Get() + { + string source = @" +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; + +public class Program +{ + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = config.Get(); + configObj = config.Get(typeof(MyClass2)); + configObj = config.Get(binderOptions => { }); + configObj = config.Get(typeof(MyClass2), binderOptions => { }); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public int[] MyArray { get; set; } + public Dictionary MyDictionary { get; set; } + } + + public class MyClass2 + { + public int MyInt { get; set; } + } + + public class MyClass3 + { + public int MyInt { get; set; } + } + + public class MyClass4 + { + public int MyInt { get; set; } + } +}"; + + await VerifyAgainstBaselineUsingFile("Get.generated.txt", source); + } + + [Fact] + public async Task Get_T() + { + string source = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = config.Get(); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public int[] MyArray { get; set; } + public Dictionary MyDictionary { get; set; } + } + + public class MyClass2 + { + public int MyInt { get; set; } + } + + public class MyClass3 + { + public int MyInt { get; set; } + } + + public class MyClass4 + { + public int MyInt { get; set; } + } + } + """; + + await VerifyAgainstBaselineUsingFile("Get_T.generated.txt", source); + } + + [Fact] + public async Task Get_T_BinderOptions() + { + string source = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = config.Get(binderOptions => { }); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public int[] MyArray { get; set; } + public Dictionary MyDictionary { get; set; } + } + + public class MyClass2 + { + public int MyInt { get; set; } + } + + public class MyClass3 + { + public int MyInt { get; set; } + } + + public class MyClass4 + { + public int MyInt { get; set; } + } + } + """; + + await VerifyAgainstBaselineUsingFile("Get_T_BinderOptions.generated.txt", source); + } + + [Fact] + public async Task Get_TypeOf() + { + string source = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass configObj = config.Get(typeof(MyClass2)); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public int[] MyArray { get; set; } + public Dictionary MyDictionary { get; set; } + } + + public class MyClass2 + { + public int MyInt { get; set; } + } + + public class MyClass3 + { + public int MyInt { get; set; } + } + + public class MyClass4 + { + public int MyInt { get; set; } + } + } + """; + + await VerifyAgainstBaselineUsingFile("Get_TypeOf.generated.txt", source); + } + + [Fact] + public async Task Get_TypeOf_BinderOptions() + { + string source = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + config.Get(typeof(MyClass2), binderOptions => { }); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public int[] MyArray { get; set; } + public Dictionary MyDictionary { get; set; } + } + + public class MyClass2 + { + public int MyInt { get; set; } + } + + public class MyClass3 + { + public int MyInt { get; set; } + } + + public class MyClass4 + { + public int MyInt { get; set; } + } + } + """; + + await VerifyAgainstBaselineUsingFile("Get_TypeOf_BinderOptions.generated.txt", source); + } + + [Fact] + public async Task GetValue() + { + string source = @" +using System.Collections.Generic; +using System.Globalization; +using Microsoft.Extensions.Configuration; + +public class Program +{ + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + config.GetValue(""key""); + config.GetValue(typeof(bool?), ""key""); + config.GetValue(""key"", new MyClass()); + config.GetValue(""key"", new byte[] { }); + config.GetValue(typeof(CultureInfo), ""key"", CultureInfo.InvariantCulture); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public int[] MyArray { get; set; } + public Dictionary MyDictionary { get; set; } + } +}"; + + await VerifyAgainstBaselineUsingFile("GetValue.generated.txt", source); + } + + [Fact] + public async Task GetValue_T_Key() + { + string source = """ + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + config.GetValue("key"); + } + } + """; + + await VerifyAgainstBaselineUsingFile("GetValue_T_Key.generated.txt", source); + } + + [Fact] + public async Task GetValue_T_Key_DefaultValue() + { + string source = """ + using System.Collections.Generic; + using System.Globalization; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + config.GetValue("key", 5); + } + } + """; + + await VerifyAgainstBaselineUsingFile("GetValue_T_Key_DefaultValue.generated.txt", source); + } + + [Fact] + public async Task GetValue_TypeOf_Key() + { + string source = """ + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + config.GetValue(typeof(bool?), "key"); + } + } + """; + + await VerifyAgainstBaselineUsingFile("GetValue_TypeOf_Key.generated.txt", source); + } + + [Fact] + public async Task GetValue_TypeOf_Key_DefaultValue() + { + string source = """ + using System.Globalization; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + config.GetValue(typeof(CultureInfo), "key", CultureInfo.InvariantCulture); + } + } + """; + + await VerifyAgainstBaselineUsingFile("GetValue_TypeOf_Key_DefaultValue.generated.txt", source); + } + + [Fact] + public async Task Configure() + { + string source = @" +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +public class Program +{ + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfiguration config = configurationBuilder.Build(); + IConfigurationSection section = config.GetSection(""MySection""); + + ServiceCollection services = new(); + services.Configure(section); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public List MyList2 { get; set; } + public Dictionary MyDictionary { get; set; } + } + + public class MyClass2 + { + public int MyInt { get; set; } + } +}"; + + await VerifyAgainstBaselineUsingFile("Configure.generated.txt", source); + } + + [Fact] + public async Task None() + { + string source = @" +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +public class Program +{ + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfiguration config = configurationBuilder.Build(); + IConfigurationSection section = config.GetSection(""MySection""); + } + + public class MyClass + { + public string MyString { get; set; } + public int MyInt { get; set; } + public List MyList { get; set; } + public List MyList2 { get; set; } + public Dictionary MyDictionary { get; set; } + } + + public class MyClass2 + { + public int MyInt { get; set; } + } +}" + ; + + var (d, r) = await RunGenerator(source); + Assert.Empty(r); + Assert.Empty(d); + } + + [Fact] + public async Task PrimitivesGen() + { + string source = """ + using System; + using System.Globalization; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfigurationRoot config = configurationBuilder.Build(); + + MyClass obj = new(); + config.Bind(obj); + } + + public class MyClass + { + public bool Prop0 { get; set; } + public byte Prop1 { get; set; } + public sbyte Prop2 { get; set; } + public char Prop3 { get; set; } + public double Prop4 { get; set; } + public string Prop5 { get; set; } + public int Prop6 { get; set; } + public short Prop8 { get; set; } + public long Prop9 { get; set; } + public float Prop10 { get; set; } + public ushort Prop13 { get; set; } + public uint Prop14 { get; set; } + public ulong Prop15 { get; set; } + public object Prop16 { get; set; } + public CultureInfo Prop17 { get; set; } + public DateTime Prop19 { get; set; } + public DateTimeOffset Prop20 { get; set; } + public decimal Prop21 { get; set; } + public TimeSpan Prop23 { get; set; } + public Guid Prop24 { get; set; } + public Uri Prop25 { get; set; } + public Version Prop26 { get; set; } + public DayOfWeek Prop27 { get; set; } + public Int128 Prop7 { get; set; } + public Half Prop11 { get; set; } + public UInt128 Prop12 { get; set; } + public DateOnly Prop18 { get; set; } + public TimeOnly Prop22 { get; set; } + public byte[] Prop22 { get; set; } + public int Prop23 { get; set; } + public DateTime Prop24 { get; set; } + } + } + """; + + await VerifyAgainstBaselineUsingFile("Primitives.generated.txt", source); + } + + [Fact] + public async Task TestCollectionsGen() + { + string source = """ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfiguration config = configurationBuilder.Build(); + IConfigurationSection section = config.GetSection(""MySection""); + + section.Get(); + } + + public class MyClassWithCustomCollections + { + public CustomDictionary CustomDictionary { get; set; } + public CustomList CustomList { get; set; } + public ICustomDictionary ICustomDictionary { get; set; } + public ICustomSet ICustomCollection { get; set; } + public IReadOnlyList IReadOnlyList { get; set; } + public IReadOnlyDictionary UnsupportedIReadOnlyDictionaryUnsupported { get; set; } + public IReadOnlyDictionary IReadOnlyDictionary { get; set; } + } + + public class CustomDictionary : Dictionary + { + } + + public class CustomList : List + { + } + + public interface ICustomDictionary : IDictionary + { + } + + public interface ICustomSet : ISet + { + } + } + """; + + await VerifyAgainstBaselineUsingFile("Collections.generated.txt", source, assessDiagnostics: (d) => + { + Assert.Equal(6, d.Length); + Test(d.Where(diagnostic => diagnostic.Id is "SYSLIB1100"), "Did not generate binding logic for a type"); + Test(d.Where(diagnostic => diagnostic.Id is "SYSLIB1101"), "Did not generate binding logic for a property on a type"); + + static void Test(IEnumerable d, string expectedTitle) + { + Assert.Equal(3, d.Count()); + foreach (Diagnostic diagnostic in d) + { + Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); + Assert.Contains(expectedTitle, diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); + } + } + }); + } + + private async Task VerifyAgainstBaselineUsingFile( + string filename, + string testSourceCode, + LanguageVersion languageVersion = LanguageVersion.Preview, + Action>? assessDiagnostics = null) + { + string baseline = LineEndingsHelper.Normalize(await File.ReadAllTextAsync(Path.Combine("Baselines", filename)).ConfigureAwait(false)); + string[] expectedLines = baseline.Replace("%VERSION%", typeof(ConfigurationBindingGenerator).Assembly.GetName().Version?.ToString()) + .Split(Environment.NewLine); + + var (d, r) = await RunGenerator(testSourceCode, languageVersion); + + Assert.Single(r); + (assessDiagnostics ?? ((d) => Assert.Empty(d))).Invoke(d); + + Assert.True(RoslynTestUtils.CompareLines(expectedLines, r[0].SourceText, + out string errorMessage), errorMessage); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs new file mode 100644 index 00000000000000..5b878c566a2929 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs @@ -0,0 +1,49 @@ +// 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; +using System.Collections.Immutable; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.DependencyInjection; +using SourceGenerators.Tests; +using Xunit; + +namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests +{ + public partial class ConfigurationBindingGeneratorTests + { + [Fact] + public async Task LangVersionMustBeCharp11OrHigher() + { + var (d, r) = await RunGenerator(BindCallSampleCode, LanguageVersion.CSharp10); + Assert.Empty(r); + + Diagnostic diagnostic = Assert.Single(d); + Assert.True(diagnostic.Id == "SYSLIB1102"); + Assert.Contains("C# 11", diagnostic.Descriptor.Title.ToString(CultureInfo.InvariantCulture)); + Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity); + } + + private async Task<(ImmutableArray, ImmutableArray)> RunGenerator( + string testSourceCode, + LanguageVersion langVersion = LanguageVersion.CSharp11) => + await RoslynTestUtils.RunGenerator( + new ConfigurationBindingGenerator(), + new[] { + typeof(ConfigurationBinder).Assembly, + typeof(CultureInfo).Assembly, + typeof(IConfiguration).Assembly, + typeof(IServiceCollection).Assembly, + typeof(IDictionary).Assembly, + typeof(ServiceCollection).Assembly, + typeof(OptionsConfigurationServiceCollectionExtensions).Assembly, + typeof(Uri).Assembly, + }, + new[] { testSourceCode }, + langVersion: langVersion).ConfigureAwait(false); + } +} diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj index 99de2ce9bb67ee..4485cfb1402c70 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj @@ -2,7 +2,6 @@ $(NetCoreAppCurrent);$(NetFrameworkMinimum) true - true $(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS;ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER SYSLIB1100,SYSLIB1101,SYSLIB1103,SYSLIB1104 @@ -16,6 +15,7 @@ + @@ -23,7 +23,6 @@ - @@ -38,10 +37,13 @@ - + PreserveNewest + + + From a7f8074ca3d9d05c222c030762b30327aa8f883c Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Thu, 1 Jun 2023 11:24:12 -0700 Subject: [PATCH 2/2] Update ConfigurationBindingGeneratorTests.cs Restore exclusion of browser/wasm for test runs. --- .../SourceGenerationTests/ConfigurationBindingGeneratorTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs index 5b878c566a2929..82a2e97890afed 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests { + [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)] public partial class ConfigurationBindingGeneratorTests { [Fact]